Compare commits

..

416 Commits
0.4.0 ... 0.8.0

Author SHA1 Message Date
fe88a3fa81 chore: 0.8.0 2019-08-29 11:56:57 +02:00
02e43a38d2 Merge pull request #439 from jbeich/fmt
Unbreak with fmtlib 6.0.0
2019-08-29 11:29:54 +02:00
f592e3d38b Merge branch 'master' into fmt 2019-08-29 11:29:48 +02:00
c6d7a779b8 Merge pull request #442 from xPMo/master
sway/mode: enable pango markup on supported modes
2019-08-29 11:06:01 +02:00
ceedc689ec Merge branch 'master' into master 2019-08-29 11:05:55 +02:00
b314cb16a9 Merge pull request #437 from alebastr/asan-fixes
fix(network): stack-use-after-return found by address sanitizer
2019-08-29 11:04:30 +02:00
a257126685 Merge branch 'master' into asan-fixes 2019-08-29 11:04:24 +02:00
baaeda0ab7 Merge pull request #438 from alebastr/tray-fixes
Tray fixes
2019-08-29 11:03:09 +02:00
e3dff6e0d8 Merge branch 'master' into tray-fixes 2019-08-29 11:02:50 +02:00
e8a1e6ae35 Merge pull request #431 from MichaelAquilina/man_waybar
Add support for man pages
2019-08-29 11:00:59 +02:00
3a86125495 Merge pull request #1 from xPMo/man_waybar
Add supported xdg-spec directory
2019-08-29 09:39:51 +01:00
da43336409 man: add supported xdg-spec directory
$XDG_CONFIG_HOME
2019-08-28 22:05:03 -05:00
712424f9a8 Unbreak build with fmtlib 6.0.0
In file included from ../src/factory.cpp:1:
In file included from ../include/factory.hpp:4:
../include/modules/clock.hpp:5:10: fatal error: 'fmt/time.h' file not found
 #include "fmt/time.h"
          ^~~~~~~~~~~~
In file included from ../src/bar.cpp:4:
In file included from ../include/factory.hpp:4:
In file included from ../include/modules/clock.hpp:3:
In file included from /usr/include/fmt/chrono.h:12:
/usr/include/fmt/locale.h:19:35: error: parameter type 'fmt::v5::internal::buffer' (aka 'basic_buffer<char>') is an abstract class
    const std::locale& loc, buffer<Char>& buf,
                                  ^
/usr/include/spdlog/fmt/bundled/core.h:238:16: note: unimplemented pure virtual method 'grow' in 'basic_buffer'
  virtual void grow(std::size_t capacity) = 0;
               ^
In file included from ../src/modules/sni/host.cpp:3:
/usr/include/fmt/ostream.h:22:9: error: expected member name or ';' after declaration specifiers
  buffer<Char>& buffer_;
  ~~~~~~^
/usr/include/fmt/ostream.h:25:19: error: expected ')'
  formatbuf(buffer<Char>& buf) : buffer_(buf) {}
                  ^
/usr/include/fmt/ostream.h:25:12: note: to match this '('
  formatbuf(buffer<Char>& buf) : buffer_(buf) {}
           ^
/usr/include/fmt/ostream.h:25:42: error: use of undeclared identifier 'buf'; did you mean 'prettify_handler::buf'?
  formatbuf(buffer<Char>& buf) : buffer_(buf) {}
                                         ^~~
                                         prettify_handler::buf
/usr/include/spdlog/fmt/bundled/format-inl.h:551:11: note: 'prettify_handler::buf' declared here
  buffer &buf;
          ^
2019-08-29 01:38:54 +00:00
e38df047fd sway/mode: enable pango markup on supported modes
IPC reports {"mode": "mode_string", "pango_markup": bool}.
Use this to conditionally enable pango markup.
2019-08-28 19:48:33 -05:00
0b237246f0 Fixes to man pages 2019-08-27 10:40:19 +01:00
01ad3d96d8 fix(tray): pre-create dbusmenu for tray items
It seems that dbusmenu is not ready to display menu immediately and
needs some time to sync data via DBus.
Fixes LIBDBUSMENU-GLIB-CRITICAL: dbusmenu_menuitem_send_about_to_show:
assertion 'DBUSMENU_IS_MENUITEM(mi)' failed.
Also fixes initial render of the menu with layer shell popups support patch.
2019-08-27 00:34:00 -07:00
642fd48af1 fix(tray): restore Activate support for compliant SNI implementation
Set ItemIsMenu to true by default because libappindicator supports
neither ItemIsMenu nor Activate method and compiant SNI implementations
are expected to reset the flag during initial property fetch.
To be revisited if anyone finds the implementation that has Activate
but does not set ItemIsMenu.
2019-08-27 00:33:59 -07:00
8f9e6c132d fix(network): stack-use-after-return found by address sanitizer
Fixes compilation with clang.
2019-08-27 00:11:40 -07:00
ba5592c86a Add waybar-states(5) 2019-08-26 22:49:04 +01:00
2a3f40bc02 Minor fixes to waybar(5) 2019-08-26 22:34:19 +01:00
c60b905831 replace `` with ** 2019-08-26 22:31:05 +01:00
6536f7adb6 Consistent header names 2019-08-26 22:29:10 +01:00
8134215839 s/config/configuration where it makes sense 2019-08-26 22:29:05 +01:00
e6fa37164c List supported modules in waybar(5) 2019-08-26 18:20:22 +01:00
436fc94549 Add waybar-tray(5) 2019-08-26 18:15:58 +01:00
4f9c3d2413 Add waybar-temperature(5) 2019-08-26 18:13:48 +01:00
c19a63e85e Add waybar-sway-workspaces(5) 2019-08-26 18:08:59 +01:00
05e212f67a Add waybar-sway-window(5) 2019-08-26 18:00:46 +01:00
e01e3be488 Add waybar-sway-mode(5) 2019-08-26 17:55:20 +01:00
f380844d61 Add waybar-pulseaudio(5) 2019-08-26 17:50:16 +01:00
7f6e4801eb Add waybar-network(5) 2019-08-26 17:42:30 +01:00
ac461f38f4 Add waybar-mpd(5) 2019-08-26 15:27:32 +01:00
30efd28b6a Add waybar-memory(5) 2019-08-26 15:12:42 +01:00
87392ef653 Add waybar-idle-inhibitor(5) 2019-08-26 15:11:07 +01:00
db85224d59 Add waybar-custom(5) 2019-08-26 13:44:09 +01:00
07d6a8e936 Use tabs not spaces in scd files 2019-08-26 13:31:57 +01:00
53614ab50c Add waybar-cpu(5) 2019-08-26 13:23:11 +01:00
fba1f5c8af Move scd files to man/ folder 2019-08-26 13:11:06 +01:00
df83404c8c Format headers 2019-08-24 21:46:56 +01:00
dd3da7b6ef Minor fixes to waybar(5) 2019-08-24 21:44:11 +01:00
cda9eb683f Add waybar-clock(5) 2019-08-24 18:09:08 +01:00
f0ad918feb Add waybar-battery(5) 2019-08-24 17:53:56 +01:00
8696ac77f9 Replace table with simpler format in waybar.5.scd 2019-08-24 17:19:32 +01:00
9484cdff7d Add waybar-backlight.5.scd 2019-08-24 15:39:46 +01:00
9b9818e95d Set scdoc dependency to required: false 2019-08-22 17:13:04 +01:00
cf72dee60c Add scdoc dependency to Dockerfiles 2019-08-22 17:11:24 +01:00
2e037df045 Add scdoc to meson build process 2019-08-22 17:04:09 +01:00
798fe1a622 Add initial man 5 waybar 2019-08-20 19:58:33 +01:00
9d0842db48 Merge pull request #429 from tufteddeer/#420_mute_bt
add support for muted bluetooth audio, fix #420
2019-08-19 10:00:30 +02:00
d8b1b0d0af add support for muted bluetooth audio, fix #420 2019-08-18 16:15:21 +02:00
e9b6380c18 chore: 0.7.2 2019-08-08 12:25:31 +02:00
43beefb00d Merge pull request #421 from SibrenVasse/media_fix
fix(custom): ignore selected_player if not defined. Fixes #419
2019-07-31 12:13:45 +02:00
12f869ccba chore: add player filter example in config 2019-07-31 11:59:33 +02:00
7e9207d75c fix(custom): ignore selected_player if not defined. Fixes #419 2019-07-31 11:53:59 +02:00
7a2dee7377 Fix typo in log warning (#411)
Fix typo in log warning
2019-07-15 13:38:23 +02:00
21a89ac46d Typo 2019-07-15 12:21:31 +02:00
bb99e6cf5b fix: check before destroy 2019-07-15 10:06:01 +02:00
0834551161 chore: 0.7.1 2019-07-11 17:02:47 +02:00
ccd1586c65 Merge pull request #405 from crwxrws/fix-400
fix(pulseaudio): connect scroll event handler
2019-07-09 09:46:05 +02:00
617b370104 fix(pulseaudio): connect scroll event handler
Reconnect the event handler that was removed in commit 527144a.

Fixes Alexays#400
2019-07-06 15:51:00 +02:00
d607a4e33f Fix deadlock on workspace scrolling (#403)
Fix deadlock on workspace scrolling
2019-07-06 10:30:57 +02:00
a6c0bc5a52 Fix deadlock on workspace scrolling
Make the mutex guard lifecycle finish before the send ipc command
by adding scope around the code.

Fixes #395 .
2019-07-05 20:47:44 -07:00
67ad0e69ce Merge pull request #397 from Jamedjo/jej/add-css-classes-for-multiple-bars
Allow CSS to select specific waybars when multiple are in use
2019-07-04 14:20:03 +02:00
ae88d6bc3c feat(bar): individual bars can be named for CSS
Allows CSS to select individual waybars when multiple are configured
2019-07-04 02:15:56 +01:00
4f3c38c542 feat(bar): add CSS class for window position 2019-07-04 02:15:50 +01:00
a6980fca7f feat: ellipsize modules 2019-06-28 14:16:09 +02:00
bd5146fdcf fix(Label): ELLIPSIZE all label modules 2019-06-28 13:49:04 +02:00
22ddbde394 style(workspaces): update hover style 2019-06-28 13:41:53 +02:00
c916fe258e fix(network): no need to check family here 2019-06-25 07:55:55 +02:00
9c8e39c30c chore: 0.7.0 2019-06-22 18:17:16 +02:00
5b270dae0d refactor: AModule (#387)
refactor: AModule
2019-06-22 18:15:50 +02:00
c621afb0c4 Merge branch 'master' into clean 2019-06-22 18:15:34 +02:00
bcf4725349 Cleanup on clean branch (#391)
Cleanup on clean branch
2019-06-18 09:43:34 +02:00
12b30ca25f AModule::getScrollDir: convert reset if-else into switch 2019-06-17 20:42:19 +02:00
86d6668ed4 AModule::getScrollDir: convert if-else chain into switch statement 2019-06-17 20:40:13 +02:00
7c85aec8e0 AModule::getScrollDir: get deltas in a more C++ way 2019-06-17 20:29:37 +02:00
2c038d1977 AModule::getScrollDir: move dir inside the only scope it is relevant 2019-06-17 20:09:53 +02:00
ff9d598c16 fix: add proper mutex 2019-06-17 11:39:45 +02:00
71a9a75aad refactor: remove fix workaround 2019-06-16 15:14:31 +02:00
05f796158b fix: typo 2019-06-16 15:13:40 +02:00
1d2dd953e7 revert: default config 2019-06-16 15:09:26 +02:00
527144a440 refactor(pulseaudio): proper scroll override 2019-06-16 15:08:08 +02:00
cda6282277 Merge pull request #389 from Synthetica9/x-scroll
AModule: handle X axis scrolling
2019-06-16 14:54:53 +02:00
7f13478396 AModule: handle X axis scrolling 2019-06-16 13:17:34 +02:00
90a9c0e25f refactor: get rid of some mutex 2019-06-15 15:01:03 +02:00
340ec7be91 refactor: AModule 2019-06-15 14:57:52 +02:00
e7eef6b493 Merge pull request #385 from nenad/patch/add-total-memory
Expose total memory to the formatting directive
2019-06-15 11:16:30 +02:00
1b7068e61d Expose total memory to the formatting directive 2019-06-14 22:48:16 +02:00
dabe2bebbb feat(sway/window): handle floating nodes 2019-06-14 11:27:40 +02:00
486b5a5d38 fix(sway/window): check output recursively 2019-06-14 10:57:22 +02:00
11bbc3b24d Fix twitchy scrolling on touchpads (#381)
Fix twitchy scrolling on touchpads
2019-06-14 10:38:25 +02:00
7f74de977c chore: 0.6.9 2019-06-14 10:27:41 +02:00
028b184f7b fix(workspaces): persistant class with empty outputs 2019-06-12 09:50:33 +02:00
564fdcb369 fix(custom): exit status 2019-06-11 22:09:47 +02:00
396f7d4525 Workspaces: implement horizontal continuous scrolling 2019-06-11 18:44:54 +02:00
3c9b533997 fix(window): avoid hexpand 2019-06-11 17:57:17 +02:00
ae397c8fa2 ALabel: add smooth-scrolling-threshold 2019-06-11 17:56:10 +02:00
ec75be0bc3 fix(Tray): click behaviour 2019-06-11 17:53:16 +02:00
ed4521d113 Workspaces: fix twitchy scrolling on touchpads
Previously, any and all scroll events were interpreted as reason to switch
workspaces. This resulted in twitchy behaviour, where the scrolling was
practically unusable.

Now, we pool all scroll values, and only scroll if the value is larger than the
new config option "smooth-scrolling-threshold". If this option is not set, the
behaviour is unchanged.
2019-06-11 17:22:24 +02:00
c2e9ed6091 feat(workspaces): add class to persistant workspaces 2019-06-11 14:08:48 +02:00
a37b4687ff Revert "refactor(window): we don't need to subscribe workspace events"
This reverts commit 648eecdd83.
2019-06-11 14:06:31 +02:00
ee29a35aa5 Update README.md 2019-06-11 13:55:35 +02:00
0be8e200ce adds the wl output name as a css class (#373)
adds the wl output name as a css class
2019-06-10 12:15:05 +02:00
46e5dd93d4 adds the wl output name as a css class
now you can have a custom styling for each bar
2019-06-08 11:04:34 -07:00
2ee4a51546 chore: 0.6.8 2019-06-08 18:33:17 +02:00
91996a85c1 Merge pull request #372 from rianadon/patch-1
Increase specificity of media stylings
2019-06-08 18:28:42 +02:00
460d25ac45 Increase specificity of media stylings
Because of CSS specificity rules, the `#custom-media` style will always override the `custom-spotify` and `custom-vlc` styles, so the background of the media element is always green rather than sometimes orange when VLC is running. I added `#custom-media` in front of each of the class selectors to increase their specificity so they take precedence.
2019-06-07 22:18:06 -07:00
648eecdd83 refactor(window): we don't need to subscribe workspace events 2019-06-07 15:08:33 +02:00
f04ff38567 Merge pull request #370 from toke/bugfix/upstream-369
Prevent zombie apocalypse by ignoring SIGCHLD
2019-06-06 12:07:03 +02:00
d20a586734 Prevent zombie apocalypse by ignoring SIGCHLD
Fixes Issue #369
2019-06-05 14:35:25 +02:00
1962caf144 refactor(window): gtk stuff in update method 2019-06-04 17:34:00 +02:00
9dbf057f58 fix(custom): hide on empty format 2019-06-03 09:50:35 +02:00
918146c16b style: prefer background-color property 2019-05-31 17:20:14 +02:00
0b01b35c76 refactor(pulseaudio): only watch changes 2019-05-31 16:21:01 +02:00
f3fb955d75 chore: 0.6.7 2019-05-31 12:15:01 +02:00
fcf2d18a01 refactor: destroy threads first 2019-05-29 17:53:22 +02:00
b05d4cd413 fix(network): retry around all getExternalInterface 2019-05-29 16:17:40 +02:00
9b89fc6470 refactor: disable bar scroll by default 2019-05-28 16:11:33 +02:00
c06725aa69 fix(network): better disconnect handler 2019-05-28 11:21:59 +02:00
5ae5821929 refactor(network): re-add MAX_RETRY in order to detect external interface 2019-05-28 11:08:48 +02:00
74e40432e5 fix(network): linked state 2019-05-28 09:58:48 +02:00
6e69af8967 refactor(custon): avoid useless logic 2019-05-27 00:08:16 +02:00
be2fa743eb refactor(custon): hide on empty text 2019-05-27 00:05:29 +02:00
5fdb122829 Create FUNDING.yml 2019-05-26 23:55:46 +02:00
6e73c6db61 refactor(network): remove last_ext_iface_ 2019-05-26 23:16:09 +02:00
253366baf4 refactor(network): remove useless assignment 2019-05-26 22:40:29 +02:00
ecec02c8be refactor(network): better events handler 2019-05-26 22:36:26 +02:00
070619fa34 revert: restore idle fix 2019-05-26 20:09:05 +02:00
d4ace4b4d8 fix(network): subscribe only wanted family 2019-05-26 20:06:27 +02:00
5fd92b3c28 fix(network): don't check IFF_UP 2019-05-26 19:53:10 +02:00
c0a39f34cd refactor(network): don't clear ipaddr 2019-05-25 18:02:36 +02:00
2a9fa1a4b9 refactor(bar): onRealize, onMap 2019-05-25 17:50:45 +02:00
07147878a9 refactor(network): code cleaning 2019-05-24 09:49:56 +02:00
ffadd5c1a7 refactor: avoid useless class vars 2019-05-24 09:49:09 +02:00
2b34f3a30f refactor: parse args before app creation 2019-05-23 10:13:49 +02:00
85d60f95c4 refactor(network): const methods 2019-05-22 22:20:50 +02:00
755d38d4d6 fix(custom): bad alloc 2019-05-22 19:46:56 +02:00
b673279a43 style: remove non wanted comment 2019-05-22 12:22:56 +02:00
9e1200ae32 refactor: also pass id to custom modules 2019-05-22 12:20:13 +02:00
e999cca7a6 style: don't specify included namespaces 2019-05-22 12:15:59 +02:00
d24d85bebf refactor: move label name and id to label contructor 2019-05-22 12:06:24 +02:00
97bd637f5d refactor(clock): avoid usless time_point_cast 2019-05-22 11:51:33 +02:00
23d4a811db refactor(clock): avoid usless duration_cast 2019-05-22 11:48:02 +02:00
bf5c00ff2a chore: 0.6.6 2019-05-22 10:16:14 +02:00
14ace24a26 style(battery): format 2019-05-22 10:09:05 +02:00
2fa581c7ea fix(battery): multiple paths 2019-05-22 10:06:54 +02:00
6ff013e25b Estimate of time remaining until empty|full on tooltip (#331)
Estimate of time remaining until empty|full on tooltip
2019-05-22 09:44:55 +02:00
cf3cb4c61f feat(Battery) Format argument for time to full|empty 2019-05-21 13:44:05 -04:00
00ada46dfc feat(Battery) Time remaining on tooltip 2019-05-21 13:36:14 -04:00
2db81a6107 fix(Battery) "current" unused and removed 2019-05-21 13:35:39 -04:00
3e76984ce7 chore: update network config 2019-05-21 17:44:46 +02:00
48a58cd979 fix(network): switch between ifaces upon disconnection 2019-05-21 17:38:47 +02:00
296b448d06 chore: update pulseaudio config 2019-05-21 14:58:03 +02:00
7a3febf6a7 fix(pulseaudio): default source format 2019-05-21 14:55:17 +02:00
bb4af295bc feat(pulseaudio): source info 2019-05-21 14:53:31 +02:00
cf7663153d fix(pulseaudio): allow to scroll to 0 2019-05-21 09:29:39 +02:00
12a251c3a4 fix(mode): escape text 2019-05-20 20:51:19 +02:00
4accdd4524 fix(Workspace): ordering 2019-05-20 20:46:59 +02:00
032ad925af Merge pull request #339 from alebastr/master
Tray error handling and logging improvements
2019-05-20 20:34:29 +02:00
50bfe78aed refactor(tray): improve error handling and add debug logs 2019-05-20 08:00:07 -07:00
afd291a566 fix(tray): Fix glib assertion when old property value is missing
xembedsniproxy sets WindowId as 'i' instead of 'u' and DBus::Proxy
ignores incorrectly typed value.
2019-05-20 07:16:08 -07:00
316a9be656 refactor(tray): Use spdlog for SNI::Item error messages 2019-05-20 07:16:08 -07:00
cdb347aaca Add --log-level command line option 2019-05-20 06:48:44 -07:00
ed240ac105 feat: add warning about tray requirements 2019-05-20 15:21:13 +02:00
232073ca17 Fix clock is always a second off (#333)
Fix clock is always a second off
2019-05-20 14:45:34 +02:00
5314b74dae fix: remove workaround 2019-05-20 14:39:49 +02:00
e3879559a2 Merge pull request #330 from Organic-Code/master
Adding sway/workspaces:persistant_workspaces
2019-05-20 14:33:31 +02:00
0ec8774a08 Fixing: missing argument for fmt, workspace order
Persistant workspaces would reorder upon their first creation
2019-05-20 08:23:42 -04:00
071b4928dc fix(workspaces): order 2019-05-20 13:31:02 +02:00
7c4d75d428 feat: create new workspace on the wanted output 2019-05-20 13:21:22 +02:00
67593b8c0f Merge pull request #332 from Organic-Code/enhancement/spdlog
Adding spdlog
2019-05-20 12:07:58 +02:00
cff39bc7cf fix: remove watcher_id fixme comment as fixed on master 2019-05-20 11:56:55 +02:00
f2edc8f965 feat(Watcher): define watcher_id 2019-05-20 11:47:52 +02:00
7b11283b73 Allow formatting memory with used and available memory (#334)
Allow formatting memory with used and available memory
2019-05-20 11:30:19 +02:00
03e43fb31d refactor: remove wlroots dependency 2019-05-20 09:49:54 +02:00
913d0f7ad0 backlight: fix for percentage output for amdgpu_bl0 device (#336)
backlight: fix for percentage output for amdgpu_bl0 device
2019-05-20 09:40:03 +02:00
5feb478611 Merge branch 'master' into amd_fix 2019-05-19 22:40:47 +03:00
6bf64cd04d Allow formatting memory with used and available memory 2019-05-19 16:34:42 +01:00
5e43b4f587 Fix clock is always a second off 2019-05-19 13:30:19 +01:00
e8dd1e2d2c Adding missing ; and _
I'll admit I don't have libmpd on my computer
2019-05-18 20:10:42 -04:00
51be97f9aa Adding spdlog 2019-05-18 19:44:45 -04:00
a00f812cd1 Typo 2019-05-18 18:21:01 -04:00
863e0babd8 Adding break when sorted_workspaces is filled 2019-05-18 12:09:30 -04:00
8ba3052dd1 Adding comments & fixing code style 2019-05-18 12:04:09 -04:00
1a76aa0c8c Improving ordering 2019-05-18 11:58:01 -04:00
85f177a213 Adding sway/workspaces:persistant_workspaces in config file
c.f. https://github.com/Alexays/Waybar/issues/210
2019-05-18 10:58:55 -04:00
6ffc7ee3b3 chore: 0.6.5 2019-05-18 16:12:19 +02:00
ff28de0482 feat(custom): update on click/scroll 2019-05-18 16:07:55 +02:00
d5e8a37e63 Adding bandwidth support for network module (#328)
Adding bandwidth support for network module
2019-05-18 15:52:37 +02:00
67786c32a8 fix(merge): re-add missing code 2019-05-18 15:45:18 +02:00
93a644eec4 Merge branch 'master' into master 2019-05-18 15:42:27 +02:00
aa385e28b6 refactor: execute update on idle 2019-05-18 15:32:40 +02:00
2c1a3d0430 Adding logging 2019-05-18 09:27:47 -04:00
b31a64ad00 Displaying in ko/s and kb/s instead of interval dependant unit 2019-05-18 09:13:00 -04:00
4865a9ad6c fix(network): reset frequency 2019-05-18 13:57:50 +02:00
e70d8aff73 Merge pull request #327 from RX14/feature/disable-scroll-wraparound-option
Add option to disable scroll wraparound on workspaces
2019-05-18 13:50:35 +02:00
3e1c77d158 Add option to disable scroll wraparound on workspaces 2019-05-18 12:15:35 +01:00
d34c3a801c fix(Network): less updates 2019-05-18 12:27:10 +02:00
794fb12e8c Adding bandwidth support for network module [linux only] 2019-05-17 23:39:51 -04:00
f743882baa Merge branch 'master' into amd_fix 2019-05-17 22:33:02 +03:00
9fa0eb7068 more elegant amd fix 2019-05-17 22:30:45 +03:00
43d724ebad Merge pull request #326 from RX14/rename-bar-scroll
Rename the "disable-workspace-scroll" option to "disable-bar-scroll"
2019-05-17 20:24:15 +02:00
c862fde986 Merge pull request #325 from RX14/bugfix/scroll-broken
Fix workspace scroll wrapping off the end of the list
2019-05-17 20:23:51 +02:00
0d59f7b7d1 Rename the "disable-workspace-scroll" option to "disable-bar-scroll" 2019-05-17 17:42:11 +01:00
1e95f5d9b6 Fix workspace scroll wrapping off the end of the list 2019-05-17 17:37:24 +01:00
9234be8544 revert: re-add rountrip before widgets setup 2019-05-17 14:45:02 +02:00
9d3255fe9f fix: remove redundant roundtrip 2019-05-17 14:41:12 +02:00
d2d9db23b5 fix: uninitialized bool 2019-05-17 14:23:52 +02:00
d8be72e4b6 refactor: unexport tray watcher 2019-05-17 13:51:55 +02:00
f8a47598ba fix: roundtrip before bar creation 2019-05-17 13:40:04 +02:00
cb2d6e1997 feat(Network): frequency 2019-05-17 11:27:38 +02:00
17291dffdf fix(Battery): plugged state 2019-05-17 10:59:54 +02:00
9a091d7740 chore: 0.6.4 2019-05-17 10:18:05 +02:00
2cb70c7324 Merge pull request #322 from RX14/feature/generic-scroll
Allow scrolling on the entire bar surface
2019-05-17 10:03:52 +02:00
4d4cadb5ae refactor: simpler code 2019-05-17 09:59:37 +02:00
cddee2626a Change scroll-step unit to percent (#316)
Change scroll-step unit to percent
2019-05-17 09:44:34 +02:00
d5c1e6f312 Change scroll-step unit to percent 2019-05-17 11:19:58 +08:00
b45dcdf74e Allow scrolling on the entire bar surface 2019-05-16 22:18:43 +01:00
a1ffa7fa9f chore: enable alpine on Travis 2019-05-16 17:20:27 +02:00
4b4b74db0c feat(Battery): get icon by state 2019-05-16 17:18:27 +02:00
8901df9702 chore: add alpine dockerfile 2019-05-16 17:10:35 +02:00
e12766a656 fix: compilation on some os 2019-05-16 17:09:25 +02:00
31f63398dc Merge pull request #319 from Alexays/network
Network improvements
2019-05-16 14:11:32 +02:00
0a14e7f3ab feat: ipv6 family 2019-05-16 12:22:08 +02:00
31416ffae6 fix: bar removed 2019-05-16 12:19:47 +02:00
fbe19d886a fix: drop memberships 2019-05-16 12:16:44 +02:00
9c67150884 refactor: prepare ipv6 2019-05-16 12:14:12 +02:00
f3c467cc46 refactor: remove non wanted headers 2019-05-16 11:27:22 +02:00
a09d2222be style: remove non wanted tags 2019-05-16 11:26:48 +02:00
45ebee52a6 fix: typo 2019-05-16 11:26:06 +02:00
841576497a refactor: cleaner events 2019-05-16 11:22:22 +02:00
e730105950 Merge pull request #318 from unresolvedsymbol/patch
Fix state behavior
2019-05-16 09:40:09 +02:00
963d4f68e4 refactor: remove useless param 2019-05-16 09:39:06 +02:00
7e8eee0571 fix state behavior 2019-05-15 22:14:51 -05:00
37f87be9dd Merge pull request #317 from jorgenbele/master
fix(battery): change comparison expr. to assignment
2019-05-15 21:02:04 +02:00
380fc58f3c fix(battery): change comparison expr. to assignment 2019-05-15 19:19:00 +02:00
22bf0b161a fix(Network): do not stop thread 2019-05-15 10:24:35 +02:00
5b3402e110 feat(Battery): plugged status 2019-05-14 15:43:57 +02:00
d209d350fe style(media): reduce min-width 2019-05-13 15:48:18 +02:00
0968170074 style(media): min-width 2019-05-13 15:40:02 +02:00
362c393b1d refactor: try/catch, sigc trackable 2019-05-13 15:15:50 +02:00
0c3c548bc0 fix(Window): avoid concurrency 2019-05-13 14:35:45 +02:00
b54160e02f fix(Tray): add item if not exist 2019-05-13 14:27:01 +02:00
db14fac038 style: remove chromium style 2019-05-13 13:23:32 +02:00
4f1defe6d5 fix(Pulseaudio): avoid handleScroll override 2019-05-13 11:46:12 +02:00
92967c7c06 fix(Label): reverse only battery states 2019-05-13 11:36:34 +02:00
fcb23d3104 feat(temperature): format-icons 2019-05-13 11:31:05 +02:00
62f8af8a39 fix(Window): avoid multiple same classes 2019-05-13 10:56:48 +02:00
d5a9eccb7b chore: v0.6.3 2019-05-12 20:02:53 +02:00
80e9ea746b fix(battery): use path for the / operator 2019-05-12 19:53:22 +02:00
84728f6fab Merge pull request #313 from apiote/master
remove empty and solo classes when they do not apply
2019-05-12 12:13:07 +02:00
10a17187a1 Merge pull request #309 from dlasky/btaudio
fix(btformat): fixes an issue where btformat was not detected
2019-05-12 12:12:16 +02:00
7cdde05568 remove empty and solo classes when they do not apply 2019-05-11 20:36:10 +02:00
Dan
e343cf4b00 fix(btformat): fixes an issue where btformat was not being correctly detected 2019-05-10 12:07:17 -04:00
e4756cf24e refactor: don't print an error when a watcher is already present 2019-05-10 14:56:28 +02:00
131dae5818 chore: v0.6.2 2019-05-10 13:40:45 +02:00
4688002f23 feat: margins 2019-05-09 15:10:13 +02:00
5bf0ca85ac refactor: try/catch around json parse 2019-05-09 10:30:54 +02:00
fd9b34adf8 chore: add fedora to travis 2019-05-07 14:04:45 +02:00
8a011e6d90 refactor(Dockerfile): fix fedora 2019-05-07 13:45:49 +02:00
5a44c8c6de refactor: avoid unneeded json parsing 2019-05-07 13:43:48 +02:00
74137befba fix(window): title flickers 2019-05-07 13:31:41 +02:00
b75e0bb0d0 refactor: remove useless bar param 2019-05-07 13:21:18 +02:00
77d9dd06af chore: fedora dockerfile 2019-05-07 13:15:59 +02:00
e5d5735e9d feat(percent): adds a percent class to numeric modules (#297)
feat(percent): adds a percent class to numeric modules
2019-05-03 17:35:21 +02:00
Dan
dd0ebe117c chore(cr): cr cleanup 2019-05-03 08:08:55 -04:00
Dan
5f0a3063d1 feat(states): add getState to other percent based modules 2019-05-02 22:24:29 -04:00
Dan
3bac96945c Revert "feat(percent): adds a percent class to numeric modules"
This reverts commit 82302e58f3b611f7ff6d686d1783b1c32914f7c9.
2019-05-02 22:24:29 -04:00
Dan
e158a3e132 feat(states): add generic 'states' to all labels 2019-05-02 22:24:29 -04:00
Dan
472363a623 feat(percent): adds a percent class to numeric modules 2019-05-02 22:24:29 -04:00
1a024db03c fix(idle_inhibitor): overload 2019-05-02 17:51:01 +02:00
5623bbecfe feat(idle_inhibitor): pass status to click events exec 2019-05-02 16:56:45 +02:00
1e871b2353 refactor: propagate the event further 2019-05-02 16:46:53 +02:00
82bed9dd5e chore: v0.6.1 2019-05-02 14:31:02 +02:00
d027243a19 fix: json thread safe 2019-05-02 14:24:54 +02:00
e6d59f05cc fix s/hidded/hidden/ typo (#295)
fix s/hidded/hidden/ typo
2019-05-02 13:59:57 +02:00
4d4562aade fix s/hidded/hidden/ typo 2019-05-01 12:40:12 +01:00
e8f31a0c4f revert: infinite seconds for once custom modules 2019-04-26 21:57:15 +02:00
f8c06b27ae Revert "feat(Cpu): dynamic fmt args"
This reverts commit 2d9bcb1a2d.
2019-04-26 21:49:16 +02:00
717a07d584 refactor(Window): simpler conditions 2019-04-26 15:29:54 +02:00
2d9bcb1a2d feat(Cpu): dynamic fmt args 2019-04-26 14:07:31 +02:00
4dd36890c1 style: background color transition 2019-04-26 12:37:35 +02:00
66acaeca7f style: workspaces button color for chromium class 2019-04-26 12:01:42 +02:00
20cf7592aa Merge pull request #287 from jonvaldes/master
Allow rotating label contents by specifying a new "rotate" property
2019-04-26 09:39:52 +02:00
9fe29c37b4 Fix indentation 2019-04-25 22:56:14 +02:00
f8ae1534db Allow rotating label contents by specifying a new "rotate" property in the label config 2019-04-25 22:47:58 +02:00
46c91a26ac style: workspaces button color for chromium class 2019-04-25 17:14:16 +02:00
07c592cc86 chore: v0.6.0 2019-04-25 16:59:22 +02:00
bb8ff5a99f feat(Bar): add class depend of window in the workspace 2019-04-25 16:47:51 +02:00
79a5e9ecee feat: multiple bar with same process 2019-04-25 13:25:06 +02:00
9504b7ac03 fix(Bar): typo 2019-04-24 12:42:16 +02:00
311c34ecbc feat(Bar): handle widget size changes 2019-04-24 12:37:24 +02:00
90d89fe974 refactor: kill custom modules scripts en destroy 2019-04-23 15:56:38 +02:00
cccf60c30e fix(Workspaces): fix concurrence and move json parser to ipc client 2019-04-23 11:42:08 +02:00
07dba791cf Update fmt build dependency (#284)
Update fmt build dependency
2019-04-23 09:46:03 +02:00
3ee99946c7 chore: update fmt build dependency
Closes #279
2019-04-23 04:40:27 +02:00
0d0f5ed7db chore: update fmt wrap 2019-04-21 19:19:38 +02:00
263a32c9af Merge pull request #278 from minijackson/mpd-timeout
fix(mpd): regularly timeout the event listener to prevent timeout
2019-04-21 19:13:38 +02:00
b50650f63f fix(mpd): regularly timeout the event listener to prevent timeout
The MPD server has a connection_timeout that defaults to 60. If no data
is transferred in this timespan, the connection is closed. If the
connection is closed while the event listener is listening for events,
it will block forever. By timing out the event listening and
re-connecting regularly, we prevent this issue. An option "timeout" has
been added for users that have a lower server connection_timeout than
default. Fixes #277
2019-04-21 10:58:40 +02:00
768bc29bc1 Merge pull request #275 from cole-h/issue-273
Ensure no NULL tags are set
2019-04-20 23:32:13 +02:00
12e1233d38 Fix compile-time warning of catch by value 2019-04-20 09:16:11 -07:00
160837b900 Ensure no NULL tags are set
Because `mpd_song_get_tag` from libmpdclient can return NULL, verify the
value of tag is valid. Otherwise, set a default string of "N/A". Also
adds configuration to specify what this default string should be.
2019-04-20 09:12:30 -07:00
d03997bdaa Merge pull request #272 from Alexays/refactoring
Refactoring
2019-04-19 17:33:49 +02:00
471b5b1ea1 Merge branch 'master' into refactoring 2019-04-19 17:33:18 +02:00
29d8f365f8 refactor(Tray): proper lookup in the default theme 2019-04-19 17:30:40 +02:00
8cf19826aa fix(Tray): Unexport on exit 2019-04-19 17:03:46 +02:00
cbb6f2a307 refactor(Workspaces, IPC): no more mutex in the workspaces modules, moved to the IPC client for a proper handling 2019-04-19 16:48:02 +02:00
e77c155ede fix(workspaces): avoid mutex block 2019-04-19 12:11:55 +02:00
811468c0ab Merge pull request #274 from minijackson/mpd-escape
fix(mpd): Escape MPD values in the label
2019-04-19 11:57:36 +02:00
171ecd53aa refactor(Bar): roundtrip before setup widgets 2019-04-19 11:56:40 +02:00
66b0420391 fix(mpd): Escape MPD values in the label 2019-04-19 11:11:44 +02:00
42dc0c4c71 fix(ipc): typo 2019-04-19 11:10:48 +02:00
bb1cf7570e refactor(IPC): use sigc signal 2019-04-19 11:09:06 +02:00
a14b933d3e fix(config): add missing comma 2019-04-18 17:55:02 +02:00
4c8f4f82dc fix(config): add missing comma 2019-04-18 17:54:38 +02:00
6ed8f94dab refactor: format code 2019-04-18 17:52:00 +02:00
807ef32357 refactor: format && better output management 2019-04-18 17:47:40 +02:00
817c42841b Merge pull request #268 from minijackson/mpd
Add MPD support
2019-04-18 16:11:16 +02:00
3e54c3c669 fix(mpd): better sample theme 2019-04-18 15:57:58 +02:00
3656035c89 fix(mpd): slightly better and safer error handling 2019-04-18 15:57:57 +02:00
0ce8821aec feat(mpd): Add playing / paused classes 2019-04-18 15:57:57 +02:00
38e37f3680 chore(mpd): add sample MPD config 2019-04-18 15:57:57 +02:00
ab43d34a1e refactor(mpd): Add module name to log messages 2019-04-18 15:57:57 +02:00
22eccc2ac2 feat(mpd): reset player state when connection drops 2019-04-18 15:57:57 +02:00
cd92b475ad chore: Add clang-format configuration and format MPD module 2019-04-18 15:57:57 +02:00
235997fa73 feat(mpd): Add support for elapsed and total time 2019-04-18 15:55:46 +02:00
80a12d0238 feat(mpd): play/pause on click & stop on right-click 2019-04-18 15:55:46 +02:00
07dab2baec feat(mpd): Add support for options (random, repeat, etc.) 2019-04-18 15:55:45 +02:00
cbfcec4867 feat(mpd): Add support for play/pause icons 2019-04-18 15:55:45 +02:00
557b786ce0 feat(mpd): Allow for specifying the reconnect interval 2019-04-18 15:55:45 +02:00
8c9dd94670 feat(mpd): Add support for setting tooltip label when disconnected 2019-04-18 15:55:45 +02:00
06aff70e2e feat: Add basic support for MPD 2019-04-18 15:55:45 +02:00
85b6e6f709 chore: Add EditorConfig file 2019-04-18 15:55:45 +02:00
6d6df4be00 refactor(sni-item): better way to search in default theme 2019-04-17 22:15:18 +02:00
9564adb5b4 refactor(Bar): avoid reinterpret_cast 2019-04-17 19:33:49 +02:00
aeaa1927d9 fix: add default_paths on init 2019-04-17 19:23:52 +02:00
346ec68578 refactor: format tray && partial fix for #235 2019-04-17 14:19:04 +02:00
93a1bafb46 chore: add clang-format 2019-04-17 13:47:34 +02:00
5f37abbd3f Update README.md 2019-04-16 13:42:49 +02:00
3273ee8b42 fix(Tray): icon size lookup 2019-04-15 12:10:37 +02:00
d05b8398fa fix: prefer to hold running even when no window is open 2019-04-15 11:42:16 +02:00
ecc5f48dd7 feat: partially hide waybar on toggle 2019-04-15 11:11:16 +02:00
316b948d86 Merge pull request #265 from Alexays/custom-multiple-classes
Custom: Ability to add multiple classes
2019-04-15 10:58:34 +02:00
5828d34fa0 Merge branch 'master' into custom-multiple-classes 2019-04-15 10:58:27 +02:00
bc9a49787a feat: enable pango markup on sway workspaces 2019-04-15 10:55:44 +02:00
6aee51479d feat: ability to add multiple classes 2019-04-15 10:18:27 +02:00
57c99dc526 refactor(Tray): also search in default theme 2019-04-11 15:28:38 +02:00
78067462be fix(Tray): icons update 2019-04-11 15:20:39 +02:00
5870421f84 refactor(temperature): check if file exist on init 2019-04-11 15:08:23 +02:00
24684ca71b chore: v0.5.1 2019-04-04 12:01:00 +02:00
8351dea292 refactor(network): process all messages 2019-04-04 11:58:27 +02:00
a68011bce6 style(workspaces): urgent style 2019-04-02 09:31:40 +02:00
1f6f443c48 Revert "refactor(network): fix skipped messages"
This reverts commit 1ccf372f8e.
2019-04-01 11:41:43 +02:00
7fac483530 fix: don't bind RTMGRP_IPV(4|6)_ROUTE 2019-03-31 16:33:01 +02:00
0ad9a03d3d Merge pull request #256 from SibrenVasse/spotify-json
Convert spotify module to generic json version
2019-03-31 16:30:16 +02:00
0f689b733d feat(custom): make spotify module generic
Set class via json attribute
Choose icon via alt attribute
2019-03-31 16:20:43 +02:00
618fe80670 chore(custom): fix typo and quoting style 2019-03-31 16:10:42 +02:00
3a8cd91cc0 Revert "refactor: partial revert of 1ccf372f8e9d74cb18e92220c18a0729832fe69e"
This reverts commit 949a4ecf2e.
2019-03-30 09:20:28 +01:00
30725824d0 Merge pull request #251 from DanielVoogsgerd/feature-select-player
Feature select player
2019-03-30 09:19:24 +01:00
6522a7acb4 chore: let compile sway modules even if we can't find the binary 2019-03-30 09:15:51 +01:00
fd3e4ab2d4 Merge branch 'master' into feature-select-player 2019-03-30 09:07:31 +01:00
949a4ecf2e refactor: partial revert of 1ccf372f8e 2019-03-30 09:03:31 +01:00
860a6a1b81 Merge pull request #252 from alebastr/tray-items-refresh
Process tray icon updates
2019-03-30 08:40:55 +01:00
5a2b5624dc feat(tray): process tray icon update signals 2019-03-29 18:40:28 -07:00
82fcee33b3 refactor(tray): use Gio::DBus bindings in SNI Item class 2019-03-29 18:28:29 -07:00
842e8501f9 fix(tray): free icon data on pixbuf update 2019-03-28 10:52:25 -07:00
7bd5454e43 Merge pull request #250 from DanielVoogsgerd/feature-arguments-logging
Feature: Command line arguments/Logging
2019-03-28 09:38:33 +01:00
8340e88795 Merge pull request #249 from DanielVoogsgerd/refactor-main_method
refactor: Add main method / Clean global scope
2019-03-28 09:38:07 +01:00
095294389b feat(Spotify): Add option to select player 2019-03-27 22:27:23 +01:00
3bcf3befec chore(Spotify): Add logging for events 2019-03-27 17:28:02 +01:00
db69b449ba feat(Spotify): Add logger 2019-03-27 16:37:59 +01:00
ed358ef024 feat(Spotify): Add argument parser 2019-03-27 16:33:15 +01:00
faa79ea216 refactor: Add main method / Clean global scope 2019-03-27 16:10:36 +01:00
b17984f75e Merge pull request #248 from ndrewtl/patch-1
Note availability in Arch community repository
2019-03-27 08:15:44 +01:00
4521db66ce Note availability in Arch community repository 2019-03-26 18:20:09 -07:00
7912b7f119 Merge pull request #245 from SibrenVasse/json-icon
[custom] Allow icon selection based on json alt attribute
2019-03-26 09:26:58 +01:00
55a6e4907b feat(custom): allow icon selection based on json alt attribute 2019-03-26 00:35:49 +01:00
dda0cc793e fix: check vertical after parsing multiple outputs 2019-03-25 21:02:00 +01:00
5144426f0e fix(workspace): scroll direction 2019-03-25 11:55:01 +01:00
98c028226d Merge pull request #241 from jlbribeiro/fix/essid-unescaped-input
Fix #240: Escape ESSID text before interpreting as pango
2019-03-24 10:28:00 +01:00
3eb901f800 Escape ESSID text before interpreting as pango
Fixes #240.
2019-03-24 03:11:54 +00:00
47142a61ae feat: allow waybar to be positioned on left/right 2019-03-22 12:25:05 +01:00
f700319d7f chore: v0.5.0 2019-03-20 10:51:40 +01:00
418767c405 Merge pull request #228 from hoellen/feat-rtsignal
Add pkill signals to custom modules
2019-03-18 19:53:02 +01:00
1f924c9c06 Merge branch 'master' into feat-rtsignal 2019-03-18 19:04:11 +01:00
38fa7ceab1 add signalhandler for module update 2019-03-18 18:46:44 +01:00
22cddc5e26 refactor(workspaces): scroll output aware 2019-03-18 14:44:07 +01:00
1ccf372f8e refactor(network): fix skipped messages 2019-03-18 11:07:36 +01:00
3257968a28 Merge pull request #222 from alebastr/pulseaudio-ci-icon-lookup
fix(pulseaudio) use case-insensitive comparison for icon lookup
2019-03-15 09:43:02 +01:00
6fc06fe9db Merge branch 'master' into pulseaudio-ci-icon-lookup 2019-03-15 09:41:40 +01:00
75afcd3ad1 Merge pull request #221 from alebastr/sway-ipc-string-assertion
fix(sway): ipc client crash when compiled with -D_GLIBCXX_ASSERTIONS
2019-03-15 09:40:41 +01:00
9ad80849b1 fix(pulseaudio): Avoid allocation of string vector on every call of getPortIcon() 2019-03-14 18:35:16 -07:00
492d151079 fix(pulseaudio) use case-insensitive comparison for icon lookup 2019-03-14 18:08:12 -07:00
00176c9514 fix(sway): ipc client crash when compiled with -D_GLIBCXX_ASSERTIONS
reserve() does not change string size and any access beyond data() + size() is UB
2019-03-14 17:53:45 -07:00
6d2dcd8ac7 fix(temperature): default thermal zone 2019-03-14 14:01:10 +01:00
7d6079bb06 style: update default temperature style/config 2019-03-13 22:39:39 +01:00
c820ed3495 Merge pull request #216 from Groggy/temperature
Add temperature module
2019-03-13 22:21:11 +01:00
0314bf70b3 feat(temperature): update README 2019-03-13 16:56:13 +01:00
7ae549dc9e Add temperature module 2019-03-13 13:35:43 +01:00
cd13180199 Merge pull request #213 from jubalh/rdmos
Add available on openSUSE
2019-03-12 11:41:05 +01:00
8d6d8e8210 Add available on openSUSE 2019-03-12 11:01:18 +01:00
2995da845d fix: config per output 2019-03-10 10:34:56 +01:00
973cec1713 feat(idle): add status class 2019-03-10 10:29:06 +01:00
94d7b083c5 fix(Pulseaudio): switch case 2019-03-08 15:30:41 +01:00
ef88f0a223 fix: clock rounding 2019-03-07 12:34:21 +01:00
ab0dcbfb2e Merge pull request #204 from Organic-Code/master
Improving mouse buttons support
2019-03-05 10:16:19 +01:00
1974766125 Merge branch 'master' into master 2019-03-05 10:15:05 +01:00
9c0c0d262e Using "inclusive or" for format-alt-click and other click events
c.f. https://github.com/Alexays/Waybar/pull/204#discussion_r262009635

Co-Authored-By: Organic-Code <Lazarelucas@yahoo.fr>
2019-03-04 15:00:44 -05:00
768e57dd37 Merge pull request #208 from jonfin/idle_inhibitor_bugfix
[bugfix] idle_inhibitor: handle click events correctly
2019-03-04 11:20:23 +01:00
67c756b28e Merge branch 'master' into idle_inhibitor_bugfix 2019-03-04 11:19:14 +01:00
584750d0fe Merge pull request #207 from jonfin/tooltip-network
Add custom tooltip format for network module
2019-03-04 11:17:52 +01:00
80ef63791d [bugfix] idle_inhibitor handles click events correctly
- Declare event handler in ALabel virtual so the idle_inhibitor can
  overriding them
- Handle the right click event in idle_inhibitor and call ALabel handler if needed
2019-03-03 22:02:34 +01:00
a9f680b06e Add custom tooltip format for network module 2019-03-03 21:35:35 +01:00
7e4fed8218 Merge pull request #206 from dikeert/issue/205-add-option-to-show-current-workspace-only
resolves #205
2019-03-03 18:04:03 +01:00
737da3615f resolves #205 2019-03-03 21:35:32 +11:00
d0f56b7bdd Improving mouse buttons support
Adding support for middle, backward, and forward mouse buttons click events, adds config keys : "on-click-middle", "on-click-forward" and "on-click-backward"
Adding the key "format-alt-click" to choose what mouse clicks toggles the alternative format, when present. Possible values (in config): "click-right", "click-left", "click-middle", "click-forward", "click-backward". Other values have the same effect than "click-left". Previous behaviour was to toggle it whenever any click was registered and any click that was not handled by "on-click-right" or "on-click-left" occurred
2019-03-02 14:07:12 -05:00
87 changed files with 5861 additions and 2264 deletions

6
.clang-format Normal file
View File

@ -0,0 +1,6 @@
---
BasedOnStyle: Google
AlignConsecutiveDeclarations: true
BinPackArguments: false
ColumnLimit: 100
...

19
.editorconfig Normal file
View File

@ -0,0 +1,19 @@
# EditorConfig configuration for Waybar
# http://EditorConfig.org
# Top-most EditorConfig file
root = true
[*]
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
charset = utf-8
[*.{build,css}]
indent_style = space
indent_size = 4
[*.{hpp,cpp}]
indent_style = space
indent_size = 2

3
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,3 @@
# These are supported funding model platforms
custom: https://paypal.me/ARouillard

View File

@ -6,7 +6,9 @@ services:
env: env:
- distro: debian - distro: debian
- distro: archlinux - distro: archlinux
# - distro: opensuse - distro: opensuse
- distro: fedora
- distro: alpine
before_install: before_install:
- docker pull alexays/waybar:${distro} - docker pull alexays/waybar:${distro}
@ -15,4 +17,4 @@ script:
- echo FROM alexays/waybar:${distro} > Dockerfile - echo FROM alexays/waybar:${distro} > Dockerfile
- echo ADD . /root >> Dockerfile - echo ADD . /root >> Dockerfile
- docker build -t waybar . - docker build -t waybar .
- docker run waybar /bin/sh -c "cd /root && git clone https://github.com/swaywm/wlroots subprojects/wlroots && meson build && ninja -C build" - docker run waybar /bin/sh -c "cd /root && meson build && ninja -C build"

3
Dockerfiles/alpine Normal file
View File

@ -0,0 +1,3 @@
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 libnl3-dev pulseaudio-dev libmpdclient-dev scdoc

View File

@ -1,4 +1,4 @@
FROM archlinux/base:latest FROM archlinux/base:latest
RUN pacman -Syu --noconfirm && \ RUN pacman -Syu --noconfirm && \
pacman -S git meson base-devel libinput wayland wayland-protocols pixman libxkbcommon mesa gtkmm3 jsoncpp --noconfirm pacman -S git meson base-devel libinput wayland wayland-protocols pixman libxkbcommon mesa gtkmm3 jsoncpp scdoc --noconfirm

View File

@ -1,5 +1,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 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 && \ apt-get install -y build-essential meson ninja-build git pkg-config libinput10 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 && \
apt-get clean apt-get clean

5
Dockerfiles/fedora Normal file
View File

@ -0,0 +1,5 @@
FROM fedora:30
RUN dnf install sway meson git libinput-devel wayland-devel wayland-protocols-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 && \
dnf group install "C Development Tools and Libraries" -y && \
dnf clean all -y

View File

@ -2,4 +2,4 @@ FROM opensuse/tumbleweed:latest
RUN zypper -n up && \ RUN zypper -n up && \
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 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 zypper -n install git meson clang libinput10 libinput-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

View File

@ -1,18 +1,21 @@
# 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 [![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)
**Proof of concept**
> Highly customizable Wayland bar for Sway and Wlroots based compositors.<br> > Highly customizable Wayland bar for Sway and Wlroots based compositors.<br>
> Available on [AUR](https://aur.archlinux.org/packages/waybar-git/) > Available in Arch [community](https://www.archlinux.org/packages/community/x86_64/waybar/) or
[AUR](https://aur.archlinux.org/packages/waybar-git/) and [openSUSE](https://build.opensuse.org/package/show/X11:Wayland/waybar)<br>
> *Waybar [examples](https://github.com/Alexays/Waybar/wiki/Examples)*
**Current features** **Current features**
- Sway (Workspaces, Binding mode, Focused window name) - Sway (Workspaces, Binding mode, Focused window name)
- Tray (Beta) [#21](https://github.com/Alexays/Waybar/issues/21) - Tray [#21](https://github.com/Alexays/Waybar/issues/21)
- Local time - Local time
- Battery - Battery
- Network - Network
- Pulseaudio - Pulseaudio
- Memory - Memory
- Cpu load average - Cpu load average
- Temperature
- MPD
- Custom scripts - Custom scripts
- Multiple output configuration - Multiple output configuration
- And much more customizations - And much more customizations
@ -40,6 +43,7 @@ $ waybar
gtkmm3 gtkmm3
jsoncpp jsoncpp
libinput libinput
libsigc++ libsigc++
fmt fmt
wayland wayland
@ -48,6 +52,7 @@ libpulse [Pulseaudio module]
libnl [Network module] libnl [Network module]
sway [Sway modules] sway [Sway modules]
libdbusmenu-gtk3 [Tray module] libdbusmenu-gtk3 [Tray module]
libmpdclient [MPD module]
``` ```
Contributions welcome! - have fun :)<br> Contributions welcome! - have fun :)<br>

View File

@ -1,36 +1,30 @@
#pragma once #pragma once
#include <json/json.h>
#include "IModule.hpp"
#include <glibmm/markup.h> #include <glibmm/markup.h>
#include <gtkmm/eventbox.h>
#include <gtkmm/label.h> #include <gtkmm/label.h>
#include <json/json.h>
#include "AModule.hpp"
namespace waybar { namespace waybar {
class ALabel : public IModule { class ALabel : public AModule {
public: public:
ALabel(const Json::Value &, const std::string format, uint16_t interval = 0); ALabel(const Json::Value &, const std::string &, const std::string &, const std::string &format,
uint16_t interval = 0, bool ellipsize = false);
virtual ~ALabel() = default; virtual ~ALabel() = default;
virtual auto update() -> void; virtual auto update() -> void;
virtual std::string getIcon(uint16_t, const std::string &alt = ""); virtual std::string getIcon(uint16_t, const std::string &alt = "", uint16_t max = 0);
virtual operator Gtk::Widget &();
protected: protected:
bool tooltipEnabled(); Gtk::Label label_;
std::string format_;
Gtk::EventBox event_box_; std::string click_param;
Gtk::Label label_;
const Json::Value &config_;
std::string format_;
std::mutex mutex_;
const std::chrono::seconds interval_; const std::chrono::seconds interval_;
bool alt_ = false; bool alt_ = false;
std::string default_format_; std::string default_format_;
private: virtual bool handleToggle(GdkEventButton *const &e);
bool handleToggle(GdkEventButton *const &ev); virtual std::string getState(uint8_t value, bool lesser = false);
bool handleScroll(GdkEventScroll *);
}; };
} // namespace waybar } // namespace waybar

40
include/AModule.hpp Normal file
View File

@ -0,0 +1,40 @@
#pragma once
#include <glibmm/dispatcher.h>
#include <glibmm/markup.h>
#include <gtkmm/eventbox.h>
#include <json/json.h>
#include "IModule.hpp"
namespace waybar {
class AModule : public IModule {
public:
AModule(const Json::Value &, const std::string &, const std::string &,
bool enable_click = false, bool enable_scroll = false);
virtual ~AModule();
virtual auto update() -> void;
virtual operator Gtk::Widget &();
Glib::Dispatcher dp;
protected:
enum SCROLL_DIR { NONE, UP, DOWN, LEFT, RIGHT };
SCROLL_DIR getScrollDir(GdkEventScroll *e);
bool tooltipEnabled();
const Json::Value &config_;
Gtk::EventBox event_box_;
std::string click_param_;
virtual bool handleToggle(GdkEventButton *const &ev);
virtual bool handleScroll(GdkEventScroll *);
private:
std::vector<int> pid_;
gdouble distance_scrolled_y_;
gdouble distance_scrolled_x_;
};
} // namespace waybar

View File

@ -1,17 +1,14 @@
#pragma once #pragma once
#include <glibmm/dispatcher.h>
#include <gtkmm/box.h>
#include <gtkmm/widget.h> #include <gtkmm/widget.h>
namespace waybar { namespace waybar {
class IModule { class IModule {
public: public:
virtual ~IModule() = default; virtual ~IModule() = default;
virtual auto update() -> void = 0; virtual auto update() -> void = 0;
virtual operator Gtk::Widget &() = 0; virtual operator Gtk::Widget &() = 0;
Glib::Dispatcher dp; // Hmmm Maybe I should create an abstract class ?
}; };
} } // namespace waybar

View File

@ -1,71 +1,82 @@
#pragma once #pragma once
#include <json/json.h>
#include <glibmm/refptr.h> #include <glibmm/refptr.h>
#include <gtkmm/main.h> #include <gtkmm/box.h>
#include <gtkmm/cssprovider.h> #include <gtkmm/cssprovider.h>
#include <gtkmm/main.h>
#include <gtkmm/window.h> #include <gtkmm/window.h>
#include <json/json.h>
#include "AModule.hpp"
#include "idle-inhibit-unstable-v1-client-protocol.h"
#include "wlr-layer-shell-unstable-v1-client-protocol.h" #include "wlr-layer-shell-unstable-v1-client-protocol.h"
#include "xdg-output-unstable-v1-client-protocol.h" #include "xdg-output-unstable-v1-client-protocol.h"
#include "idle-inhibit-unstable-v1-client-protocol.h"
#include "IModule.hpp"
namespace waybar { namespace waybar {
class Client;
class Factory; class Factory;
struct waybar_output {
class Bar { struct wl_output * output = nullptr;
public: std::string name;
Bar(const Client&, std::unique_ptr<struct wl_output *>&&, uint32_t); uint32_t wl_name;
Bar(const Bar&) = delete; struct zxdg_output_v1 *xdg_output = nullptr;
~Bar() = default;
auto toggle() -> void;
const Client& client;
Gtk::Window window;
struct wl_surface *surface;
struct zwlr_layer_surface_v1 *layer_surface;
std::unique_ptr<struct wl_output *> output;
std::string output_name;
uint32_t wl_name;
bool visible = true;
private:
static void handleLogicalPosition(void *, struct zxdg_output_v1 *, int32_t,
int32_t);
static void handleLogicalSize(void *, struct zxdg_output_v1 *, int32_t,
int32_t);
static void handleDone(void *, struct zxdg_output_v1 *);
static void handleName(void *, struct zxdg_output_v1 *, const char *);
static void handleDescription(void *, struct zxdg_output_v1 *,
const char *);
static void layerSurfaceHandleConfigure(void *,
struct zwlr_layer_surface_v1 *, uint32_t, uint32_t, uint32_t);
static void layerSurfaceHandleClosed(void *,
struct zwlr_layer_surface_v1 *);
void initBar();
bool isValidOutput(const Json::Value &config);
void destroyOutput();
auto setupConfig() -> void;
auto setupWidgets() -> void;
auto setupCss() -> void;
void getModules(const Factory&, const std::string&);
uint32_t width_ = 0;
uint32_t height_ = 30;
Json::Value config_;
Glib::RefPtr<Gtk::StyleContext> style_context_;
Glib::RefPtr<Gtk::CssProvider> css_provider_;
struct zxdg_output_v1 *xdg_output_;
Gtk::Box left_;
Gtk::Box center_;
Gtk::Box right_;
Gtk::Box box_;
std::vector<std::unique_ptr<waybar::IModule>> modules_left_;
std::vector<std::unique_ptr<waybar::IModule>> modules_center_;
std::vector<std::unique_ptr<waybar::IModule>> modules_right_;
}; };
} class Bar {
public:
Bar(struct waybar_output *w_output, const Json::Value &);
Bar(const Bar &) = delete;
~Bar() = default;
auto toggle() -> void;
void handleSignal(int);
struct waybar_output * output;
Json::Value config;
Gtk::Window window;
struct wl_surface * surface;
struct zwlr_layer_surface_v1 *layer_surface;
bool visible = true;
bool vertical = false;
private:
static constexpr const char *MIN_HEIGHT_MSG =
"Requested height: {} exceeds the minimum height: {} required by the modules";
static constexpr const char *MIN_WIDTH_MSG =
"Requested width: {} exceeds the minimum width: {} required by the modules";
static constexpr const char *BAR_SIZE_MSG =
"Bar configured (width: {}, height: {}) for output: {}";
static constexpr const char *SIZE_DEFINED =
"{} size is defined in the config file so it will stay like that";
static void layerSurfaceHandleConfigure(void *, struct zwlr_layer_surface_v1 *, uint32_t,
uint32_t, uint32_t);
static void layerSurfaceHandleClosed(void *, struct zwlr_layer_surface_v1 *);
void destroyOutput();
void onConfigure(GdkEventConfigure *ev);
void onRealize();
void onMap(GdkEventAny *ev);
void setMarginsAndZone(uint32_t height, uint32_t width);
auto setupWidgets() -> void;
void getModules(const Factory &, const std::string &);
void setupAltFormatKeyForModule(const std::string &module_name);
void setupAltFormatKeyForModuleList(const char *module_list_name);
struct margins {
int top = 0;
int right = 0;
int bottom = 0;
int left = 0;
} margins_;
uint32_t width_ = 0;
uint32_t height_ = 1;
uint8_t anchor_;
Gtk::Box left_;
Gtk::Box center_;
Gtk::Box right_;
Gtk::Box box_;
std::vector<std::unique_ptr<waybar::AModule>> modules_left_;
std::vector<std::unique_ptr<waybar::AModule>> modules_center_;
std::vector<std::unique_ptr<waybar::AModule>> modules_right_;
};
} // namespace waybar

View File

@ -1,41 +1,55 @@
#pragma once #pragma once
#include <unistd.h>
#include <wordexp.h>
#include <fmt/format.h> #include <fmt/format.h>
#include <gdk/gdk.h> #include <gdk/gdk.h>
#include <wayland-client.h>
#include <gdk/gdkwayland.h> #include <gdk/gdkwayland.h>
#include <unistd.h>
#include <wayland-client.h>
#include <wordexp.h>
#include "bar.hpp" #include "bar.hpp"
namespace waybar { namespace waybar {
class Client { class Client {
public: public:
Client(int argc, char *argv[]); static Client *inst();
int main(int argc, char *argv[]); int main(int argc, char *argv[]);
Gtk::Main gtk_main; Glib::RefPtr<Gtk::Application> gtk_app;
std::string css_file; Glib::RefPtr<Gdk::Display> gdk_display;
std::string config_file; struct wl_display * wl_display = nullptr;
Glib::RefPtr<Gdk::Display> gdk_display; struct wl_registry * registry = nullptr;
struct wl_display *wl_display = nullptr; struct zwlr_layer_shell_v1 * layer_shell = nullptr;
struct wl_registry *registry = nullptr; struct zxdg_output_manager_v1 * xdg_output_manager = nullptr;
struct zwlr_layer_shell_v1 *layer_shell = nullptr; struct zwp_idle_inhibit_manager_v1 *idle_inhibit_manager = nullptr;
struct zxdg_output_manager_v1 *xdg_output_manager = nullptr; std::vector<std::unique_ptr<Bar>> bars;
struct wl_seat *seat = nullptr;
struct zwp_idle_inhibit_manager_v1 *idle_inhibit_manager = nullptr;
std::vector<std::unique_ptr<Bar>> bars;
private: private:
void setupConfigs(const std::string& config, const std::string& style); Client() = default;
void bindInterfaces(); std::tuple<const std::string, const std::string> getConfigs(const std::string &config,
const std::string getValidPath(std::vector<std::string> paths); const std::string &style) const;
void bindInterfaces();
const std::string getValidPath(const std::vector<std::string> &paths) const;
void handleOutput(std::unique_ptr<struct waybar_output> &output);
bool isValidOutput(const Json::Value &config, std::unique_ptr<struct waybar_output> &output);
auto setupConfig(const std::string &config_file) -> void;
auto setupCss(const std::string &css_file) -> void;
std::unique_ptr<struct waybar_output> &getOutput(uint32_t wl_name);
std::vector<Json::Value> getOutputConfigs(std::unique_ptr<struct waybar_output> &output);
static void handleGlobal(void *data, struct wl_registry *registry, static void handleGlobal(void *data, struct wl_registry *registry, uint32_t name,
uint32_t name, const char *interface, uint32_t version); const char *interface, uint32_t version);
static void handleGlobalRemove(void *data, static void handleGlobalRemove(void *data, struct wl_registry *registry, uint32_t name);
struct wl_registry *registry, uint32_t name); static void handleLogicalPosition(void *, struct zxdg_output_v1 *, int32_t, int32_t);
static void handleLogicalSize(void *, struct zxdg_output_v1 *, int32_t, int32_t);
static void handleDone(void *, struct zxdg_output_v1 *);
static void handleName(void *, struct zxdg_output_v1 *, const char *);
static void handleDescription(void *, struct zxdg_output_v1 *, const char *);
Json::Value config_;
Glib::RefPtr<Gtk::StyleContext> style_context_;
Glib::RefPtr<Gtk::CssProvider> css_provider_;
std::vector<std::unique_ptr<struct waybar_output>> outputs_;
}; };
} } // namespace waybar

View File

@ -4,14 +4,16 @@
#include "modules/clock.hpp" #include "modules/clock.hpp"
#ifdef HAVE_SWAY #ifdef HAVE_SWAY
#include "modules/sway/mode.hpp" #include "modules/sway/mode.hpp"
#include "modules/sway/workspaces.hpp"
#include "modules/sway/window.hpp" #include "modules/sway/window.hpp"
#include "modules/sway/workspaces.hpp"
#endif #endif
#include "modules/idle_inhibitor.hpp" #ifndef NO_FILESYSTEM
#include "modules/battery.hpp" #include "modules/battery.hpp"
#include "modules/memory.hpp" #endif
#include "modules/cpu.hpp" #include "modules/cpu.hpp"
#ifdef HAVE_DBUSMENU #include "modules/idle_inhibitor.hpp"
#include "modules/memory.hpp"
#if defined(HAVE_DBUSMENU) && !defined(NO_FILESYSTEM)
#include "modules/sni/tray.hpp" #include "modules/sni/tray.hpp"
#endif #endif
#ifdef HAVE_LIBNL #ifdef HAVE_LIBNL
@ -23,19 +25,23 @@
#ifdef HAVE_LIBPULSE #ifdef HAVE_LIBPULSE
#include "modules/pulseaudio.hpp" #include "modules/pulseaudio.hpp"
#endif #endif
#ifdef HAVE_LIBMPDCLIENT
#include "modules/mpd.hpp"
#endif
#include "bar.hpp"
#include "modules/custom.hpp" #include "modules/custom.hpp"
#include "modules/temperature.hpp"
namespace waybar { namespace waybar {
class Bar;
class Factory { class Factory {
public: public:
Factory(const Bar& bar, const Json::Value& config); Factory(const Bar& bar, const Json::Value& config);
IModule* makeModule(const std::string &name) const; AModule* makeModule(const std::string& name) const;
private:
const Bar& bar_; private:
const Json::Value& config_; const Bar& bar_;
const Json::Value& config_;
}; };
} } // namespace waybar

View File

@ -16,52 +16,46 @@ namespace waybar::modules {
class Backlight : public ALabel { class Backlight : public ALabel {
class BacklightDev { class BacklightDev {
public: public:
BacklightDev() = default; BacklightDev() = default;
BacklightDev(std::string name, int actual, int max); BacklightDev(std::string name, int actual, int max);
std::string_view name() const; std::string_view name() const;
int get_actual() const; int get_actual() const;
void set_actual(int actual); void set_actual(int actual);
int get_max() const; int get_max() const;
void set_max(int max); void set_max(int max);
friend inline bool operator==(const BacklightDev &lhs, friend inline bool operator==(const BacklightDev &lhs, const BacklightDev &rhs) {
const BacklightDev &rhs) { return lhs.name_ == rhs.name_ && lhs.actual_ == rhs.actual_ && lhs.max_ == rhs.max_;
return lhs.name_ == rhs.name_ && lhs.actual_ == rhs.actual_ &&
lhs.max_ == rhs.max_;
} }
private: private:
std::string name_; std::string name_;
int actual_ = 1; int actual_ = 1;
int max_ = 1; int max_ = 1;
}; };
public: public:
Backlight(const std::string &, const Json::Value &); Backlight(const std::string &, const Json::Value &);
~Backlight(); ~Backlight();
auto update() -> void; auto update() -> void;
private: private:
template <class ForwardIt> template <class ForwardIt>
static const BacklightDev *best_device(ForwardIt first, ForwardIt last, static const BacklightDev *best_device(ForwardIt first, ForwardIt last, std::string_view);
std::string_view);
template <class ForwardIt, class Inserter> template <class ForwardIt, class Inserter>
static void upsert_device(ForwardIt first, ForwardIt last, Inserter inserter, static void upsert_device(ForwardIt first, ForwardIt last, Inserter inserter, udev_device *dev);
udev_device *dev);
template <class ForwardIt, class Inserter> template <class ForwardIt, class Inserter>
static void enumerate_devices(ForwardIt first, ForwardIt last, static void enumerate_devices(ForwardIt first, ForwardIt last, Inserter inserter, udev *udev);
Inserter inserter, udev *udev);
const std::string name_; const std::string preferred_device_;
const std::string preferred_device_;
static constexpr int EPOLL_MAX_EVENTS = 16; static constexpr int EPOLL_MAX_EVENTS = 16;
std::optional<BacklightDev> previous_best_; std::optional<BacklightDev> previous_best_;
std::string previous_format_; std::string previous_format_;
std::mutex udev_thread_mutex_; std::mutex udev_thread_mutex_;
std::vector<BacklightDev> devices_; std::vector<BacklightDev> devices_;
// thread must destruct before shared data // thread must destruct before shared data
waybar::util::SleeperThread udev_thread_; util::SleeperThread udev_thread_;
}; };
} // namespace waybar::modules } // namespace waybar::modules

View File

@ -5,13 +5,14 @@
#else #else
#include <filesystem> #include <filesystem>
#endif #endif
#include <fstream>
#include <iostream>
#include <fmt/format.h> #include <fmt/format.h>
#include <sys/inotify.h> #include <sys/inotify.h>
#include <algorithm> #include <algorithm>
#include "util/sleeper_thread.hpp" #include <fstream>
#include <string>
#include <vector>
#include "ALabel.hpp" #include "ALabel.hpp"
#include "util/sleeper_thread.hpp"
namespace waybar::modules { namespace waybar::modules {
@ -22,26 +23,28 @@ namespace fs = std::filesystem;
#endif #endif
class Battery : public ALabel { class Battery : public ALabel {
public: public:
Battery(const std::string&, const Json::Value&); Battery(const std::string&, const Json::Value&);
~Battery(); ~Battery();
auto update() -> void; auto update() -> void;
private:
static inline const fs::path data_dir_ = "/sys/class/power_supply/";
void getBatteries(); private:
void worker(); static inline const fs::path data_dir_ = "/sys/class/power_supply/";
const std::string getAdapterStatus(uint8_t capacity) const;
const std::tuple<uint8_t, std::string> getInfos() const;
const std::string getState(uint8_t) const;
util::SleeperThread thread_; void getBatteries();
util::SleeperThread thread_timer_; void worker();
std::vector<fs::path> batteries_; const std::string getAdapterStatus(uint8_t capacity) const;
fs::path adapter_; const std::tuple<uint8_t, float, std::string> getInfos() const;
int fd_; const std::string formatTimeRemaining(float hoursRemaining);
std::vector<int> wds_;
std::string old_status_; std::vector<fs::path> batteries_;
fs::path adapter_;
int fd_;
std::vector<int> wds_;
std::string old_status_;
util::SleeperThread thread_;
util::SleeperThread thread_timer_;
}; };
} } // namespace waybar::modules

View File

@ -1,19 +1,24 @@
#pragma once #pragma once
#include <fmt/format.h> #include <fmt/format.h>
#include "fmt/time.h" #if FMT_VERSION < 60000
#include "util/sleeper_thread.hpp" #include <fmt/time.h>
#else
#include <fmt/chrono.h>
#endif
#include "ALabel.hpp" #include "ALabel.hpp"
#include "util/sleeper_thread.hpp"
namespace waybar::modules { namespace waybar::modules {
class Clock : public ALabel { class Clock : public ALabel {
public: public:
Clock(const std::string&, const Json::Value&); Clock(const std::string&, const Json::Value&);
~Clock() = default; ~Clock() = default;
auto update() -> void; auto update() -> void;
private:
waybar::util::SleeperThread thread_; private:
util::SleeperThread thread_;
}; };
} } // namespace waybar::modules

View File

@ -2,28 +2,32 @@
#include <fmt/format.h> #include <fmt/format.h>
#include <sys/sysinfo.h> #include <sys/sysinfo.h>
#include <cstdint>
#include <fstream> #include <fstream>
#include <vector>
#include <numeric> #include <numeric>
#include <iostream> #include <string>
#include "util/sleeper_thread.hpp" #include <utility>
#include <vector>
#include "ALabel.hpp" #include "ALabel.hpp"
#include "util/sleeper_thread.hpp"
namespace waybar::modules { namespace waybar::modules {
class Cpu : public ALabel { class Cpu : public ALabel {
public: public:
Cpu(const std::string&, const Json::Value&); Cpu(const std::string&, const Json::Value&);
~Cpu() = default; ~Cpu() = default;
auto update() -> void; auto update() -> void;
private:
static inline const std::string data_dir_ = "/proc/stat";
uint16_t getCpuLoad();
std::tuple<uint16_t, std::string> getCpuUsage();
std::vector<std::tuple<size_t, size_t>> parseCpuinfo();
std::vector<std::tuple<size_t, size_t>> prev_times_; private:
waybar::util::SleeperThread thread_; static inline const std::string data_dir_ = "/proc/stat";
uint16_t getCpuLoad();
std::tuple<uint16_t, std::string> getCpuUsage();
std::vector<std::tuple<size_t, size_t>> parseCpuinfo();
std::vector<std::tuple<size_t, size_t>> prev_times_;
util::SleeperThread thread_;
}; };
} } // namespace waybar::modules

View File

@ -1,36 +1,42 @@
#pragma once #pragma once
#include <fmt/format.h> #include <fmt/format.h>
#include <iostream> #include <csignal>
#include "util/sleeper_thread.hpp" #include <string>
#include "ALabel.hpp"
#include "util/command.hpp" #include "util/command.hpp"
#include "util/json.hpp" #include "util/json.hpp"
#include "ALabel.hpp" #include "util/sleeper_thread.hpp"
namespace waybar::modules { namespace waybar::modules {
class Custom : public ALabel { class Custom : public ALabel {
public: public:
Custom(const std::string&, const Json::Value&); Custom(const std::string&, const std::string&, const Json::Value&);
~Custom(); ~Custom();
auto update() -> void; auto update() -> void;
private: void refresh(int /*signal*/);
void delayWorker();
void continuousWorker();
void parseOutputRaw();
void parseOutputJson();
const std::string name_; private:
std::string text_; void delayWorker();
std::string alt_; void continuousWorker();
std::string tooltip_; void parseOutputRaw();
std::string class_; void parseOutputJson();
std::string prevclass_; bool handleScroll(GdkEventScroll* e);
int percentage_; bool handleToggle(GdkEventButton* const& e);
waybar::util::SleeperThread thread_;
waybar::util::command::res output_; const std::string name_;
waybar::util::JsonParser parser_; std::string text_;
FILE* fp_; std::string alt_;
std::string tooltip_;
std::vector<std::string> class_;
int percentage_;
FILE* fp_;
int pid_;
util::command::res output_;
util::JsonParser parser_;
util::SleeperThread thread_;
}; };
} } // namespace waybar::modules

View File

@ -1,23 +1,25 @@
#pragma once #pragma once
#include <fmt/format.h> #include <fmt/format.h>
#include "ALabel.hpp"
#include "bar.hpp" #include "bar.hpp"
#include "client.hpp" #include "client.hpp"
#include "ALabel.hpp"
namespace waybar::modules { namespace waybar::modules {
class IdleInhibitor: public ALabel { class IdleInhibitor : public ALabel {
public: public:
IdleInhibitor(const std::string&, const waybar::Bar&, const Json::Value&); IdleInhibitor(const std::string&, const waybar::Bar&, const Json::Value&);
~IdleInhibitor(); ~IdleInhibitor();
auto update() -> void; auto update() -> void;
private:
bool onClick(GdkEventButton* const& ev);
const Bar& bar_; private:
std::string status_; bool handleToggle(GdkEventButton* const& e);
struct zwp_idle_inhibitor_v1 *idle_inhibitor_;
const Bar& bar_;
std::string status_;
struct zwp_idle_inhibitor_v1* idle_inhibitor_;
int pid_;
}; };
} } // namespace waybar::modules

View File

@ -2,22 +2,25 @@
#include <fmt/format.h> #include <fmt/format.h>
#include <fstream> #include <fstream>
#include "util/sleeper_thread.hpp"
#include "ALabel.hpp" #include "ALabel.hpp"
#include "util/sleeper_thread.hpp"
namespace waybar::modules { namespace waybar::modules {
class Memory : public ALabel { class Memory : public ALabel {
public: public:
Memory(const std::string&, const Json::Value&); Memory(const std::string&, const Json::Value&);
~Memory() = default; ~Memory() = default;
auto update() -> void; auto update() -> void;
private:
static inline const std::string data_dir_ = "/proc/meminfo"; private:
unsigned long memtotal_; static inline const std::string data_dir_ = "/proc/meminfo";
unsigned long memfree_; void parseMeminfo();
void parseMeminfo();
waybar::util::SleeperThread thread_; unsigned long memtotal_;
unsigned long memfree_;
util::SleeperThread thread_;
}; };
} } // namespace waybar::modules

70
include/modules/mpd.hpp Normal file
View File

@ -0,0 +1,70 @@
#pragma once
#include <fmt/format.h>
#include <mpd/client.h>
#include <condition_variable>
#include <thread>
#include "ALabel.hpp"
namespace waybar::modules {
class MPD : public ALabel {
public:
MPD(const std::string&, const Json::Value&);
auto update() -> void;
private:
std::thread periodic_updater();
std::string getTag(mpd_tag_type type, unsigned idx = 0);
void setLabel();
std::string getStateIcon();
std::string getOptionIcon(std::string optionName, bool activated);
std::thread event_listener();
// Assumes `connection_lock_` is locked
void tryConnect();
// If checking errors on the main connection, make sure to lock it using
// `connection_lock_` before calling checkErrors
void checkErrors(mpd_connection* conn);
// Assumes `connection_lock_` is locked
void fetchState();
void waitForEvent();
bool handlePlayPause(GdkEventButton* const&);
bool stopped();
bool playing();
const std::string module_name_;
using unique_connection = std::unique_ptr<mpd_connection, decltype(&mpd_connection_free)>;
using unique_status = std::unique_ptr<mpd_status, decltype(&mpd_status_free)>;
using unique_song = std::unique_ptr<mpd_song, decltype(&mpd_song_free)>;
// Not using unique_ptr since we don't manage the pointer
// (It's either nullptr, or from the config)
const char* server_;
const unsigned port_;
unsigned timeout_;
// We need a mutex here because we can trigger updates from multiple thread:
// the event based updates, the periodic updates needed for the elapsed time,
// and the click play/pause feature
std::mutex connection_lock_;
unique_connection connection_;
// The alternate connection will be used to wait for events: since it will
// be blocking (idle) we can't send commands via this connection
//
// No lock since only used in the event listener thread
unique_connection alternate_connection_;
// Protect them using the `connection_lock_`
unique_status status_;
mpd_state state_;
unique_song song_;
};
} // namespace waybar::modules

View File

@ -1,61 +1,76 @@
#pragma once #pragma once
#include <net/if.h>
#include <arpa/inet.h> #include <arpa/inet.h>
#include <ifaddrs.h>
#include <netlink/netlink.h>
#include <netlink/genl/genl.h>
#include <netlink/genl/ctrl.h>
#include <linux/nl80211.h>
#include <sys/epoll.h>
#include <fmt/format.h> #include <fmt/format.h>
#include "util/sleeper_thread.hpp" #include <ifaddrs.h>
#include <linux/nl80211.h>
#include <net/if.h>
#include <netlink/genl/ctrl.h>
#include <netlink/genl/genl.h>
#include <netlink/netlink.h>
#include <sys/epoll.h>
#include "ALabel.hpp" #include "ALabel.hpp"
#include "util/sleeper_thread.hpp"
namespace waybar::modules { namespace waybar::modules {
class Network : public ALabel { class Network : public ALabel {
public: public:
Network(const std::string&, const Json::Value&); Network(const std::string&, const Json::Value&);
~Network(); ~Network();
auto update() -> void; auto update() -> void;
private:
static const uint8_t MAX_RETRY = 5;
static int handleEvents(struct nl_msg*, void*); private:
static int handleScan(struct nl_msg*, void*); static const uint8_t MAX_RETRY = 5;
static const uint8_t EPOLL_MAX = 200;
void worker(); static int handleEvents(struct nl_msg*, void*);
void disconnected(); static int handleScan(struct nl_msg*, void*);
void createInfoSocket();
void createEventSocket();
int getExternalInterface();
void getInterfaceAddress();
int netlinkRequest(void*, uint32_t, uint32_t groups = 0);
int netlinkResponse(void*, uint32_t, uint32_t groups = 0);
void parseEssid(struct nlattr**);
void parseSignal(struct nlattr**);
bool associatedOrJoined(struct nlattr**);
auto getInfo() -> void;
waybar::util::SleeperThread thread_; void worker();
waybar::util::SleeperThread thread_timer_; void createInfoSocket();
int ifid_; void createEventSocket();
sa_family_t family_; int getExternalInterface(int skip_idx = -1) const;
struct sockaddr_nl nladdr_ = {0}; void getInterfaceAddress();
struct nl_sock* sk_ = nullptr; int netlinkRequest(void*, uint32_t, uint32_t groups = 0) const;
struct nl_sock* info_sock_ = nullptr; int netlinkResponse(void*, uint32_t, uint32_t groups = 0) const;
int efd_; void parseEssid(struct nlattr**);
int ev_fd_; void parseSignal(struct nlattr**);
int nl80211_id_; void parseFreq(struct nlattr**);
bool associatedOrJoined(struct nlattr**);
bool checkInterface(struct ifinfomsg* rtif, std::string name);
int getPreferredIface(int skip_idx = -1) const;
auto getInfo() -> void;
void checkNewInterface(struct ifinfomsg* rtif);
const std::string getNetworkState() const;
void clearIface();
bool wildcardMatch(const std::string& pattern, const std::string& text) const;
std::string essid_; int ifid_;
std::string ifname_; sa_family_t family_;
std::string ipaddr_; struct sockaddr_nl nladdr_ = {0};
std::string netmask_; struct nl_sock* sock_ = nullptr;
int cidr_; struct nl_sock* ev_sock_ = nullptr;
int32_t signal_strength_dbm_; int efd_;
uint8_t signal_strength_; int ev_fd_;
int nl80211_id_;
std::mutex mutex_;
unsigned long long bandwidth_down_total_;
unsigned long long bandwidth_up_total_;
std::string state_;
std::string essid_;
std::string ifname_;
std::string ipaddr_;
std::string netmask_;
int cidr_;
int32_t signal_strength_dbm_;
uint8_t signal_strength_;
uint32_t frequency_;
util::SleeperThread thread_;
util::SleeperThread thread_timer_;
}; };
} } // namespace waybar::modules

View File

@ -9,31 +9,39 @@
namespace waybar::modules { namespace waybar::modules {
class Pulseaudio : public ALabel { class Pulseaudio : public ALabel {
public: public:
Pulseaudio(const std::string&, const Json::Value&); Pulseaudio(const std::string&, const Json::Value&);
~Pulseaudio(); ~Pulseaudio();
auto update() -> void; auto update() -> void;
private:
static void subscribeCb(pa_context*, pa_subscription_event_type_t,
uint32_t, void*);
static void contextStateCb(pa_context*, void*);
static void sinkInfoCb(pa_context*, const pa_sink_info*, int, void*);
static void serverInfoCb(pa_context*, const pa_server_info*, void*);
static void volumeModifyCb(pa_context*, int, void*);
bool handleScroll(GdkEventScroll* e);
const std::string getPortIcon() const; private:
static void subscribeCb(pa_context*, pa_subscription_event_type_t, uint32_t, void*);
static void contextStateCb(pa_context*, void*);
static void sinkInfoCb(pa_context*, const pa_sink_info*, int, void*);
static void sourceInfoCb(pa_context*, const pa_source_info* i, int, void* data);
static void serverInfoCb(pa_context*, const pa_server_info*, void*);
static void volumeModifyCb(pa_context*, int, void*);
pa_threaded_mainloop* mainloop_; bool handleScroll(GdkEventScroll* e);
pa_mainloop_api* mainloop_api_; const std::string getPortIcon() const;
pa_context* context_;
uint32_t sink_idx_{0}; pa_threaded_mainloop* mainloop_;
uint16_t volume_; pa_mainloop_api* mainloop_api_;
pa_cvolume pa_volume_; pa_context* context_;
bool muted_; // SINK
std::string port_name_; uint32_t sink_idx_{0};
std::string desc_; uint16_t volume_;
bool scrolling_; pa_cvolume pa_volume_;
bool muted_;
std::string port_name_;
std::string desc_;
std::string monitor_;
// SOURCE
uint32_t source_idx_{0};
uint16_t source_volume_;
bool source_muted_;
std::string source_port_name_;
std::string source_desc_;
}; };
} // namespace waybar::modules } // namespace waybar::modules

View File

@ -1,42 +1,43 @@
#pragma once #pragma once
#include <glibmm/refptr.h> #include <dbus-status-notifier-watcher.h>
#include <giomm.h> #include <giomm.h>
#include <glibmm/refptr.h>
#include <json/json.h> #include <json/json.h>
#include <tuple> #include <tuple>
#include <dbus-status-notifier-watcher.h>
#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&, Host(const std::size_t id, const Json::Value&, const std::function<void(std::unique_ptr<Item>&)>&,
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();
private:
void busAcquired(const Glib::RefPtr<Gio::DBus::Connection>&, Glib::ustring);
void nameAppeared(const Glib::RefPtr<Gio::DBus::Connection>&, Glib::ustring, const Glib::ustring&);
void nameVanished(const Glib::RefPtr<Gio::DBus::Connection>&, Glib::ustring);
static void proxyReady(GObject*, GAsyncResult*, gpointer);
static void registerHost(GObject*, GAsyncResult*, gpointer);
static void itemRegistered(SnWatcher*, const gchar*, gpointer);
static void itemUnregistered(SnWatcher*, const gchar*, gpointer);
std::tuple<std::string, std::string> getBusNameAndObjectPath(const std::string); private:
void addRegisteredItem(std::string service); void busAcquired(const Glib::RefPtr<Gio::DBus::Connection>&, Glib::ustring);
void nameAppeared(const Glib::RefPtr<Gio::DBus::Connection>&, Glib::ustring,
const Glib::ustring&);
void nameVanished(const Glib::RefPtr<Gio::DBus::Connection>&, Glib::ustring);
static void proxyReady(GObject*, GAsyncResult*, gpointer);
static void registerHost(GObject*, GAsyncResult*, gpointer);
static void itemRegistered(SnWatcher*, const gchar*, gpointer);
static void itemUnregistered(SnWatcher*, const gchar*, gpointer);
std::vector<std::unique_ptr<Item>> items_; std::tuple<std::string, std::string> getBusNameAndObjectPath(const std::string);
const std::string bus_name_; void addRegisteredItem(std::string service);
const std::string object_path_;
std::size_t bus_name_id_; std::vector<std::unique_ptr<Item>> items_;
std::size_t watcher_id_; const std::string bus_name_;
GCancellable* cancellable_ = nullptr; const std::string object_path_;
SnWatcher* watcher_ = nullptr; std::size_t bus_name_id_;
const Json::Value &config_; std::size_t watcher_id_;
const std::function<void(std::unique_ptr<Item>&)> on_add_; GCancellable* cancellable_ = nullptr;
const std::function<void(std::unique_ptr<Item>&)> on_remove_; SnWatcher* watcher_ = nullptr;
const Json::Value& config_;
const std::function<void(std::unique_ptr<Item>&)> on_add_;
const std::function<void(std::unique_ptr<Item>&)> on_remove_;
}; };
} } // namespace waybar::modules::SNI

View File

@ -1,13 +1,15 @@
#pragma once #pragma once
#include <dbus-status-notifier-item.h> #include <dbus-status-notifier-item.h>
#include <giomm/dbusproxy.h>
#include <glibmm/refptr.h> #include <glibmm/refptr.h>
#include <gtkmm/eventbox.h> #include <gtkmm/eventbox.h>
#include <gtkmm/image.h>
#include <gtkmm/icontheme.h> #include <gtkmm/icontheme.h>
#include <gtkmm/image.h>
#include <gtkmm/menu.h> #include <gtkmm/menu.h>
#include <json/json.h> #include <json/json.h>
#include <libdbusmenu-gtk/dbusmenu-gtk.h> #include <libdbusmenu-gtk/dbusmenu-gtk.h>
#include <sigc++/trackable.h>
#ifdef FILESYSTEM_EXPERIMENTAL #ifdef FILESYSTEM_EXPERIMENTAL
#include <experimental/filesystem> #include <experimental/filesystem>
#else #else
@ -16,48 +18,59 @@
namespace waybar::modules::SNI { namespace waybar::modules::SNI {
class Item { class Item : public sigc::trackable {
public: public:
Item(std::string, std::string, const Json::Value&); Item(const std::string&, const std::string&, const Json::Value&);
~Item() = default; ~Item() = default;
std::string bus_name; std::string bus_name;
std::string object_path; std::string object_path;
int icon_size; int icon_size;
int effective_icon_size; int effective_icon_size;
Gtk::Image image; Gtk::Image image;
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 status;
std::string title; std::string title;
int32_t window_id; int32_t window_id;
std::string icon_name; std::string icon_name;
Glib::RefPtr<Gdk::Pixbuf> icon_pixmap; Glib::RefPtr<Gdk::Pixbuf> icon_pixmap;
std::string overlay_icon_name; Glib::RefPtr<Gtk::IconTheme> icon_theme;
std::string attention_icon_name; std::string overlay_icon_name;
std::string attention_movie_name; std::string attention_icon_name;
std::string icon_theme_path; std::string attention_movie_name;
std::string menu; std::string icon_theme_path;
DbusmenuGtkMenu *dbus_menu = nullptr; std::string menu;
Gtk::Menu *gtk_menu = nullptr; DbusmenuGtkMenu* dbus_menu = nullptr;
bool item_is_menu; Gtk::Menu* gtk_menu = nullptr;
/**
* ItemIsMenu flag means that the item only supports the context menu.
* Default value is true because libappindicator supports neither ItemIsMenu nor Activate method
* while compliant SNI implementation would always reset the flag to desired value.
*/
bool item_is_menu = true;
private: private:
static void proxyReady(GObject *obj, GAsyncResult *res, gpointer data); void proxyReady(Glib::RefPtr<Gio::AsyncResult>& result);
static void getAll(GObject *obj, GAsyncResult *res, gpointer data); void setProperty(const Glib::ustring& name, Glib::VariantBase& value);
void getUpdatedProperties();
void processUpdatedProperties(Glib::RefPtr<Gio::AsyncResult>& result);
void onSignal(const Glib::ustring& sender_name, const Glib::ustring& signal_name,
const Glib::VariantContainerBase& arguments);
void updateImage(); void updateImage();
Glib::RefPtr<Gdk::Pixbuf> extractPixBuf(GVariant *variant); Glib::RefPtr<Gdk::Pixbuf> extractPixBuf(GVariant* variant);
Glib::RefPtr<Gdk::Pixbuf> getIconByName(std::string name, int size); Glib::RefPtr<Gdk::Pixbuf> getIconByName(const std::string& name, int size);
static void onMenuDestroyed(Item *self); static void onMenuDestroyed(Item* self, GObject* old_menu_pointer);
bool makeMenu(GdkEventButton *const &ev); void makeMenu();
bool handleClick(GdkEventButton *const & /*ev*/); bool handleClick(GdkEventButton* const& /*ev*/);
GCancellable *cancellable_ = nullptr; Glib::RefPtr<Gio::DBus::Proxy> proxy_;
SnItem *proxy_ = nullptr; Glib::RefPtr<Gio::Cancellable> cancellable_;
bool update_pending_;
}; };
} // namespace waybar::modules::SNI } // namespace waybar::modules::SNI

View File

@ -1,29 +1,28 @@
#pragma once #pragma once
#include <fmt/format.h> #include <fmt/format.h>
#include "AModule.hpp"
#include "bar.hpp" #include "bar.hpp"
#include "util/json.hpp"
#include "IModule.hpp"
#include "modules/sni/watcher.hpp"
#include "modules/sni/host.hpp" #include "modules/sni/host.hpp"
#include "modules/sni/watcher.hpp"
#include "util/json.hpp"
namespace waybar::modules::SNI { namespace waybar::modules::SNI {
class Tray : public IModule { class Tray : public AModule {
public: public:
Tray(const std::string&, const Json::Value&); Tray(const std::string&, const Bar&, const Json::Value&);
~Tray() = default; ~Tray() = default;
auto update() -> void; auto update() -> void;
operator Gtk::Widget &();
private:
void onAdd(std::unique_ptr<Item>& item);
void onRemove(std::unique_ptr<Item>& item);
static inline std::size_t nb_hosts_ = 0; private:
const Json::Value& config_; void onAdd(std::unique_ptr<Item>& item);
Gtk::Box box_; void onRemove(std::unique_ptr<Item>& item);
SNI::Watcher watcher_ ;
SNI::Host host_; static inline std::size_t nb_hosts_ = 0;
Gtk::Box box_;
SNI::Watcher watcher_;
SNI::Host host_;
}; };
} } // namespace waybar::modules::SNI

View File

@ -1,48 +1,43 @@
#pragma once #pragma once
#include <dbus-status-notifier-watcher.h>
#include <giomm.h> #include <giomm.h>
#include <glibmm/refptr.h> #include <glibmm/refptr.h>
#include <dbus-status-notifier-watcher.h>
namespace waybar::modules::SNI { namespace waybar::modules::SNI {
class Watcher { class Watcher {
public: public:
Watcher(); Watcher(std::size_t id);
~Watcher() = default; ~Watcher();
private: private:
typedef enum { GF_WATCH_TYPE_HOST, GF_WATCH_TYPE_ITEM } GfWatchType; typedef enum { GF_WATCH_TYPE_HOST, GF_WATCH_TYPE_ITEM } GfWatchType;
typedef struct { typedef struct {
GfWatchType type; GfWatchType type;
Watcher *watcher; Watcher * watcher;
gchar *service; gchar * service;
gchar *bus_name; gchar * bus_name;
gchar *object_path; gchar * object_path;
guint watch_id; guint watch_id;
} GfWatch; } GfWatch;
void busAcquired(const Glib::RefPtr<Gio::DBus::Connection>&, Glib::ustring); void busAcquired(const Glib::RefPtr<Gio::DBus::Connection> &, Glib::ustring);
static gboolean handleRegisterHost(Watcher *, GDBusMethodInvocation *, static gboolean handleRegisterHost(Watcher *, GDBusMethodInvocation *, const gchar *);
const gchar *); static gboolean handleRegisterItem(Watcher *, GDBusMethodInvocation *, const gchar *);
static gboolean handleRegisterItem(Watcher *, GDBusMethodInvocation *, static GfWatch *gfWatchFind(GSList *list, const gchar *bus_name, const gchar *object_path);
const gchar *); static GfWatch *gfWatchNew(GfWatchType, const gchar *, const gchar *, const gchar *, Watcher *);
static GfWatch *gfWatchFind(GSList *list, const gchar *bus_name, static void nameVanished(GDBusConnection *connection, const char *name, gpointer data);
const gchar *object_path); static void gfWatchFree(gpointer data);
static GfWatch *gfWatchNew(GfWatchType, const gchar *, const gchar *,
const gchar *, Watcher *);
static void nameVanished(GDBusConnection *connection, const char *name,
gpointer data);
void updateRegisteredItems(SnWatcher *obj); void updateRegisteredItems(SnWatcher *obj);
uint32_t bus_name_id_; uint32_t bus_name_id_;
uint32_t watcher_id_; uint32_t watcher_id_;
GSList *hosts_ = nullptr; GSList * hosts_ = nullptr;
GSList *items_ = nullptr; GSList * items_ = nullptr;
SnWatcher *watcher_ = nullptr; SnWatcher *watcher_ = nullptr;
}; };
} // namespace waybar::modules::SNI } // namespace waybar::modules::SNI

View File

@ -1,40 +1,46 @@
#pragma once #pragma once
#include <iostream> #include <sigc++/sigc++.h>
#include <cstring>
#include <unistd.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/un.h> #include <sys/un.h>
#include <unistd.h>
#include <cstring>
#include <memory>
#include <mutex>
#include "ipc.hpp" #include "ipc.hpp"
namespace waybar::modules::sway { namespace waybar::modules::sway {
class Ipc { class Ipc {
public: public:
Ipc(); Ipc();
~Ipc(); ~Ipc();
struct ipc_response { struct ipc_response {
uint32_t size; uint32_t size;
uint32_t type; uint32_t type;
std::string payload; std::string payload;
}; };
struct ipc_response sendCmd(uint32_t type, const std::string &payload = "") const; sigc::signal<void, const struct ipc_response &> signal_event;
void subscribe(const std::string &payload) const; sigc::signal<void, const struct ipc_response &> signal_cmd;
struct ipc_response handleEvent() const;
protected: void sendCmd(uint32_t type, const std::string &payload = "");
void subscribe(const std::string &payload);
void handleEvent();
protected:
static inline const std::string ipc_magic_ = "i3-ipc"; static inline const std::string ipc_magic_ = "i3-ipc";
static inline const size_t ipc_header_size_ = ipc_magic_.size() + 8; static inline const size_t ipc_header_size_ = ipc_magic_.size() + 8;
const std::string getSocketPath() const; const std::string getSocketPath() const;
int open(const std::string &) const; int open(const std::string &) const;
struct ipc_response send(int fd, uint32_t type, const std::string &payload = "") const; struct ipc_response send(int fd, uint32_t type, const std::string &payload = "");
struct ipc_response recv(int fd) const; struct ipc_response recv(int fd);
int fd_; int fd_;
int fd_event_; int fd_event_;
std::mutex mutex_;
}; };
} // namespace waybar::modules::sway } // namespace waybar::modules::sway

View File

@ -1,28 +1,31 @@
#pragma once #pragma once
#include <fmt/format.h> #include <fmt/format.h>
#include "ALabel.hpp"
#include "bar.hpp" #include "bar.hpp"
#include "client.hpp" #include "client.hpp"
#include "util/sleeper_thread.hpp"
#include "util/json.hpp"
#include "ALabel.hpp"
#include "modules/sway/ipc/client.hpp" #include "modules/sway/ipc/client.hpp"
#include "util/json.hpp"
#include "util/sleeper_thread.hpp"
namespace waybar::modules::sway { namespace waybar::modules::sway {
class Mode : public ALabel { class Mode : public ALabel, public sigc::trackable {
public: public:
Mode(const std::string&, const waybar::Bar&, const Json::Value&); Mode(const std::string&, const Json::Value&);
~Mode() = default; ~Mode() = default;
auto update() -> void; auto update() -> void;
private:
void worker();
const Bar& bar_; private:
waybar::util::SleeperThread thread_; void onEvent(const struct Ipc::ipc_response&);
util::JsonParser parser_; void worker();
Ipc ipc_;
std::string mode_; std::string mode_;
util::JsonParser parser_;
std::mutex mutex_;
util::SleeperThread thread_;
Ipc ipc_;
}; };
} } // namespace waybar::modules::sway

View File

@ -2,31 +2,40 @@
#include <fmt/format.h> #include <fmt/format.h>
#include <tuple> #include <tuple>
#include "ALabel.hpp"
#include "bar.hpp" #include "bar.hpp"
#include "client.hpp" #include "client.hpp"
#include "util/sleeper_thread.hpp"
#include "util/json.hpp"
#include "ALabel.hpp"
#include "modules/sway/ipc/client.hpp" #include "modules/sway/ipc/client.hpp"
#include "util/json.hpp"
#include "util/sleeper_thread.hpp"
namespace waybar::modules::sway { namespace waybar::modules::sway {
class Window : public ALabel { class Window : public ALabel, 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;
auto update() -> void; auto update() -> void;
private:
void worker();
std::tuple<int, std::string> getFocusedNode(Json::Value nodes);
void getFocusedWindow();
const Bar& bar_; private:
waybar::util::SleeperThread thread_; void onEvent(const struct Ipc::ipc_response&);
util::JsonParser parser_; void onCmd(const struct Ipc::ipc_response&);
Ipc ipc_; void worker();
std::string window_; std::tuple<std::size_t, int, std::string, std::string> getFocusedNode(const Json::Value& nodes,
int windowId_; std::string& output);
void getTree();
const Bar& bar_;
std::string window_;
int windowId_;
std::string app_id_;
std::string old_app_id_;
std::size_t app_nb_;
util::JsonParser parser_;
std::mutex mutex_;
util::SleeperThread thread_;
Ipc ipc_;
}; };
} } // namespace waybar::modules::sway

View File

@ -1,42 +1,46 @@
#pragma once #pragma once
#include <fmt/format.h> #include <fmt/format.h>
#include <gtkmm/button.h>
#include <gtkmm/label.h>
#include "AModule.hpp"
#include "bar.hpp" #include "bar.hpp"
#include "client.hpp" #include "client.hpp"
#include "util/sleeper_thread.hpp"
#include "util/json.hpp"
#include "IModule.hpp"
#include "modules/sway/ipc/client.hpp" #include "modules/sway/ipc/client.hpp"
#include <gtkmm/button.h> #include "util/json.hpp"
#include "util/sleeper_thread.hpp"
namespace waybar::modules::sway { namespace waybar::modules::sway {
class Workspaces : public IModule { class Workspaces : public AModule, public sigc::trackable {
public: public:
Workspaces(const std::string&, const waybar::Bar&, const Json::Value&); Workspaces(const std::string&, const waybar::Bar&, const Json::Value&);
~Workspaces() = default; ~Workspaces() = default;
auto update() -> void; auto update() -> void;
operator Gtk::Widget &();
private:
void worker();
void addWorkspace(const Json::Value&);
std::string getIcon(const std::string&, const Json::Value&);
bool handleScroll(GdkEventScroll*);
std::string getPrevWorkspace();
std::string getNextWorkspace();
uint16_t getWorkspaceIndex(const std::string &name);
std::string trimWorkspaceName(std::string);
const Bar& bar_; private:
const Json::Value& config_; void onCmd(const struct Ipc::ipc_response&);
Json::Value workspaces_; void onEvent(const struct Ipc::ipc_response&);
waybar::util::SleeperThread thread_; void worker();
Gtk::Box box_; bool filterButtons();
util::JsonParser parser_; Gtk::Button& addButton(const Json::Value&);
Ipc ipc_; void onButtonReady(const Json::Value&, Gtk::Button&);
std::mutex mutex_; std::string getIcon(const std::string&, const Json::Value&);
bool scrolling_; const std::string getCycleWorkspace(std::vector<Json::Value>::iterator, bool prev) const;
std::unordered_map<std::string, Gtk::Button> buttons_; uint16_t getWorkspaceIndex(const std::string& name) const;
std::string trimWorkspaceName(std::string);
bool handleScroll(GdkEventScroll*);
const Bar& bar_;
std::vector<Json::Value> workspaces_;
std::vector<std::string> workspaces_order_;
Gtk::Box box_;
util::JsonParser parser_;
std::unordered_map<std::string, Gtk::Button> buttons_;
std::mutex mutex_;
util::SleeperThread thread_;
Ipc ipc_;
}; };
} } // namespace waybar::modules::sway

View File

@ -0,0 +1,24 @@
#pragma once
#include <fmt/format.h>
#include <fstream>
#include "ALabel.hpp"
#include "util/sleeper_thread.hpp"
namespace waybar::modules {
class Temperature : public ALabel {
public:
Temperature(const std::string&, const Json::Value&);
~Temperature() = default;
auto update() -> void;
private:
std::tuple<uint16_t, uint16_t> getTemperature();
bool isCritical(uint16_t);
std::string file_path_;
util::SleeperThread thread_;
};
} // namespace waybar::modules

View File

@ -1,25 +1,19 @@
#pragma once #pragma once
#include <sys/wait.h>
#include <giomm.h> #include <giomm.h>
#include <sys/wait.h>
#include <unistd.h> #include <unistd.h>
namespace waybar::util::command { namespace waybar::util::command {
struct res { struct res {
int exit_code; int exit_code;
std::string out; std::string out;
}; };
inline struct res exec(const std::string cmd) inline std::string read(FILE* fp) {
{
FILE* fp(popen(cmd.c_str(), "r"));
if (!fp) {
return { -1, "" };
}
std::array<char, 128> buffer = {0}; std::array<char, 128> buffer = {0};
std::string output; std::string output;
while (feof(fp) == 0) { while (feof(fp) == 0) {
if (fgets(buffer.data(), 128, fp) != nullptr) { if (fgets(buffer.data(), 128, fp) != nullptr) {
output += buffer.data(); output += buffer.data();
@ -27,27 +21,80 @@ inline struct res exec(const std::string cmd)
} }
// Remove last newline // Remove last newline
if (!output.empty() && output[output.length()-1] == '\n') { if (!output.empty() && output[output.length() - 1] == '\n') {
output.erase(output.length()-1); output.erase(output.length() - 1);
} }
int exit_code = WEXITSTATUS(pclose(fp)); return output;
return {exit_code, output};
} }
inline bool forkExec(std::string cmd) { inline int close(FILE* fp, pid_t pid) {
if (cmd == "") return true; int stat;
fclose(fp);
while (waitpid(pid, &stat, 0) == -1) {
if (errno != EINTR) {
stat = 0;
break;
}
}
return stat;
}
inline FILE* open(const std::string cmd, int& pid) {
if (cmd == "") return nullptr;
int fd[2];
pipe(fd);
pid_t child_pid = fork();
if (child_pid < 0) {
printf("Unable to exec cmd %s, error %s", cmd.c_str(), strerror(errno));
return nullptr;
}
if (!child_pid) {
::close(fd[0]);
dup2(fd[1], 1);
setpgid(child_pid, child_pid);
execl("/bin/sh", "sh", "-c", cmd.c_str(), (char*)0);
exit(0);
} else {
::close(fd[1]);
}
pid = child_pid;
return fdopen(fd[0], "r");
}
inline struct res exec(std::string cmd) {
int pid;
auto fp = command::open(cmd, pid);
if (!fp) return {-1, ""};
auto output = command::read(fp);
auto stat = command::close(fp, pid);
return {WEXITSTATUS(stat), output};
}
inline int32_t forkExec(std::string cmd) {
if (cmd == "") return -1;
int32_t pid = fork(); int32_t pid = fork();
if (pid < 0) { if (pid < 0) {
printf("Unable to exec cmd %s, error %s", cmd.c_str(), strerror(errno)); printf("Unable to exec cmd %s, error %s", cmd.c_str(), strerror(errno));
return false; return pid;
} }
// Child executes the command // Child executes the command
if (!pid) execl("/bin/sh", "sh", "-c", cmd.c_str(), (char*)0); if (!pid) {
setpgid(pid, pid);
execl("/bin/sh", "sh", "-c", cmd.c_str(), (char*)0);
exit(0);
} else {
signal(SIGCHLD,SIG_IGN);
}
return true; return pid;
} }
} // namespace waybar::util::command } // namespace waybar::util::command

View File

@ -5,27 +5,24 @@
namespace waybar::util { namespace waybar::util {
struct JsonParser { struct JsonParser {
JsonParser() {}
JsonParser() const Json::Value parse(const std::string& data) const {
: reader_(builder_.newCharReader()) Json::Value root(Json::objectValue);
{} if (data.empty()) {
return root;
const Json::Value parse(const std::string& data) const }
{ std::unique_ptr<Json::CharReader> const reader(builder_.newCharReader());
Json::Value root; std::string err;
std::string err; bool res = reader->parse(data.c_str(), data.c_str() + data.size(), &root, &err);
bool res = if (!res) throw std::runtime_error(err);
reader_->parse(data.c_str(), data.c_str() + data.size(), &root, &err);
if (!res)
throw std::runtime_error(err);
return root; return root;
} }
~JsonParser() = default; ~JsonParser() = default;
private: private:
Json::CharReaderBuilder builder_; Json::CharReaderBuilder builder_;
std::unique_ptr<Json::CharReader> const reader_;
}; };
} } // namespace waybar::util

View File

@ -1,76 +1,79 @@
#pragma once #pragma once
#include <chrono> #include <chrono>
#include <condition_variable>
#include <ctime> #include <ctime>
#include <functional> #include <functional>
#include <condition_variable>
#include <thread> #include <thread>
namespace waybar::util { namespace waybar::util {
class SleeperThread { class SleeperThread {
public: public:
SleeperThread() = default; SleeperThread() = default;
SleeperThread(std::function<void()> func) SleeperThread(std::function<void()> func)
: thread_{[this, func] { : thread_{[this, func] {
while (do_run_) func(); while (do_run_) {
}} signal_ = false;
{} func();
}
}} {}
SleeperThread& operator=(std::function<void()> func) SleeperThread& operator=(std::function<void()> func) {
{
thread_ = std::thread([this, func] { thread_ = std::thread([this, func] {
while (do_run_) func(); while (do_run_) {
signal_ = false;
func();
}
}); });
return *this; return *this;
} }
bool isRunning() const bool isRunning() const { return do_run_; }
{
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_);
return condvar_.wait_for(lk, dur, [this] { return !do_run_; }); return condvar_.wait_for(lk, dur, [this] { return signal_ || !do_run_; });
} }
auto sleep_until(std::chrono::time_point<std::chrono::system_clock, auto sleep_until(
std::chrono::system_clock::duration> time_point) std::chrono::time_point<std::chrono::system_clock, std::chrono::system_clock::duration>
{ time_point) {
std::unique_lock lk(mutex_); std::unique_lock lk(mutex_);
return condvar_.wait_until(lk, time_point, [this] { return !do_run_; }); return condvar_.wait_until(lk, time_point, [this] { return signal_ || !do_run_; });
} }
auto wake_up() auto wake_up() {
{ {
std::lock_guard<std::mutex> lck(mutex_);
signal_ = true;
}
condvar_.notify_all(); condvar_.notify_all();
} }
auto stop() auto stop() {
{
{ {
std::lock_guard<std::mutex> lck(mutex_); std::lock_guard<std::mutex> lck(mutex_);
signal_ = true;
do_run_ = false; do_run_ = false;
} }
condvar_.notify_all(); condvar_.notify_all();
} }
~SleeperThread() ~SleeperThread() {
{
stop(); stop();
if (thread_.joinable()) { if (thread_.joinable()) {
thread_.join(); thread_.join();
} }
} }
private: private:
std::thread thread_; std::thread thread_;
std::condition_variable condvar_; std::condition_variable condvar_;
std::mutex mutex_; std::mutex mutex_;
bool do_run_ = true; bool do_run_ = true;
bool signal_ = false;
}; };
} } // namespace waybar::util

View File

@ -0,0 +1,67 @@
waybar-backlight(5)
# NAME
waybar - backlight module
# DESCRIPTION
The *backlight* module displays the current backlight level.
# CONFIGURATION
*interval* ++
typeof: integer ++
default: 2 ++
The interval in which information gets polled.
*format* ++
typeof: string ++
default: {percent}% ++
The format, how information should be displayed. On {} data gets inserted.
*max-length* ++
typeof: integer ++
The maximum length in characters the module should display.
*rotate* ++
typeof: integer ++
Positive value to rotate the text label.
*states* ++
typeof: array ++
A number of backlight states which get activated on certain brightness levels.
*on-click* ++
typeof: string ++
Command to execute when the module is clicked.
*on-click-right* ++
typeof: string ++
Command to execute when the module is right clicked.
*on-scroll-up* ++
typeof: string ++
Command to execute when performing a scroll up on the module.
*on-scroll-down* ++
typeof: string
Command to execute when performing a scroll down on the module.
*smooth-scrolling-threshold* ++
typeof: double
Threshold to be used when scrolling.
# EXAMPLE:
```
"backlight": {
"device": "intel_backlight",
"format": "{percent}% {icon}",
"format-icons": ["", ""]
}
```
# STYLE
- *#backlight*

123
man/waybar-battery.5.scd Normal file
View File

@ -0,0 +1,123 @@
waybar-battery(5)
# NAME
waybar - battery module
# DESCRIPTION
The *battery* module displays the current capacity and state (eg. charging) of your battery.
# CONFIGURATION
*bat* ++
typeof: string ++
The battery to monitor, as in /sys/class/power_supply/ instead of auto detect.
*adapter* ++
typeof: string ++
The adapter to monitor, as in /sys/class/power_supply/ instead of auto detect.
*interval* ++
typeof: integer ++
default: 60 ++
The interval in which the information gets polled.
*states* ++
typeof: array ++
A number of battery states which get activated on certain capacity levels. See *waybar-states(5)*.
*format* ++
typeof: string ++
default: {capacity}% ++
The format, how the time should be displayed.
*format-icons*
typeof: array/object
Based on the current capacity, the corresponding icon gets selected. ++
The order is *low* to *high*. Or by the state if it is an object.
*max-length* ++
typeof: integer++
The maximum length in character the module should display.
*rotate* ++
typeof: integer++
Positive value to rotate the text label.
*on-click* ++
typeof: string ++
Command to execute when clicked on the module.
*on-click-right* ++
typeof: string ++
Command to execute when you right clicked on the module.
*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
*{capacity}*: Capacity in percentage
*{icon}*: Icon, as defined in *format-icons*.
*{time}*: Estimate of time until full or empty. Note that this is based on the power draw at the last refresh time, not an average.
# CUSTOM FORMATS
The *battery* module allows 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-<status>*: With the status, a custom format can be set depending on the status in /sys/class/power_supply/<bat>/status (in lowercase).
*format-<status>-<state>*: You can also set a custom format depending on both values.
# STATES
- Every entry (*state*) consists of a *<name>* (typeof: *string*) and a *<value>* (typeof: *integer*).
- The state can be addressed as a CSS class in the *style.css*. The name of the CSS class is the *<name>* of the state. Each class gets activated when the current capacity is equal or below the configured *<value>*.
- Also each state can have its own *format*. Those con be configured via *format-<name>*. Or if you want to differentiate a bit more even as *format-<status>-<state>*. For more information see *custom-formats*.
# EXAMPLES
```
"battery": {
"bat": "BAT2",
"interval": 60,
"states": {
"warning": 30,
"critical": 15
},
"format": "{capacity}% {icon}",
"format-icons": ["", "", "", "", ""],
"max-length": 25
}
```
# STYLE
- *#battery*
- *#battery.<status>*
- *<status>* is the value of /sys/class/power_supply/<bat>/status in lowercase.
- *#battery.<state>*
- *<state>* can be defined in the *config*. For more information see *states*.
- *#battery.<status>.<state>*
- Combination of both *<status>* and *<state>*.

65
man/waybar-clock.5.scd Normal file
View File

@ -0,0 +1,65 @@
waybar-clock(5)
# NAME
waybar - clock module
# DESCRIPTION
The *clock* module displays the current date and time.
# CONFIGURATION
*interval*: ++
typeof: integer ++
default: 60 ++
The interval in which the information gets polled.
*format*: ++
typeof: string ++
default: {:%H:%M} ++
The format, how the date and time should be displayed.
*max-length*: ++
typeof: integer ++
The maximum length in character the module should display.
*rotate*: ++
typeof: integer ++
Positive value to rotate the text label.
*on-click*: ++
typeof: string ++
Command to execute when clicked on the module.
*on-click-right*: ++
typeof: string ++
Command to execute when you right clicked on the module.
*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.
View all valid format options in *strftime(3)*.
# EXAMPLES
```
"clock": {
"interval": 60,
"format": "{:%H:%M}",
"max-length": 25
}
```
# STYLE
- *#clock*

78
man/waybar-cpu.5.scd Normal file
View File

@ -0,0 +1,78 @@
waybar-cpu(5)
# NAME
waybar - cpu module
# DESCRIPTION
The *cpu* module displays the current cpu utilization.
# CONFIGURATION
*interval*: ++
typeof: integer ++
default: 10 ++
The interval in which the information gets polled.
*format*: ++
typeof: string ++
default: {usage}% ++
The format, how information should be displayed. On {} data gets inserted.
*max-length*: ++
typeof: integer ++
The maximum length in character the module should display.
*rotate*: ++
typeof: integer ++
Positive value to rotate the text label.
*states*: ++
typeof: array ++
A number of cpu usage states which get activated on certain usage levels. See *waybar-states(5)*.
*on-click*: ++
typeof: string ++
Command to execute when clicked on the module.
*on-click-right*: ++
typeof: string ++
Command to execute when you right clicked on the module.
*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
*{load}*: Current cpu load.
*{usage}*: Current cpu usage.
# EXAMPLE
```
"cpu": {
"interval": 10,
"format": "{}% ",
"max-length": 10
}
```
# STYLE
- *#cpu*

190
man/waybar-custom.5.scd Normal file
View File

@ -0,0 +1,190 @@
waybar-custom(5)
# NAME
waybar - custom module
# DESCRIPTION
The *custom* module displays either the output of a script or static text.
To display static text, specify only the *format* field.
# CONFIGURATION
Addressed by *custom/<name>*
*exec*: ++
typeof: string ++
The path to the script, which should be executed.
*exec-if*: ++
typeof: string ++
The path to a script, which determines if the script in *exec* should be executed.
*exec* will be executed if the exit code of *exec-if* equals 0.
*return-type*: ++
typeof: string ++
See *return-type*
*interval*: ++
typeof: integer ++
The interval (in seconds) in which the information gets polled.
Use *once* if you want to execute the module only on startup.
You can update it manually with a signal. If no *interval* is defined,
it is assumed that the out script loops it self.
*signal*: ++
typeof: integer ++
The signal number used to update the module.
The number is valid between 1 and N, where *SIGRTMIN+N* = *SIGRTMAX*.
*format*: ++
typeof: string ++
default: {} ++
The format, how information should be displayed. On {} data gets inserted.
*format-icons*: ++
typeof: array ++
Based on the set percentage, the corresponding icon gets selected. The order is *low* to *high*.
*rotate*: ++
typeof: integer ++
Positive value to rotate the text label.
*max-length*: ++
typeof: integer ++
The maximum length in character the module should display.
*on-click*: ++
typeof: string ++
Command to execute when clicked on the module.
*on-click-right*: ++
typeof: string ++
Command to execute when you right clicked on the module.
*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.
*escape*: ++
typeof: bool ++
default: false ++
Option to enable escaping of script output.
# RETURN-TYPE
When *return-type* is set to *json*, Waybar expects the *exec*-script to output its data in JSON format.
This should look like this:
```
{"text": "$text", "tooltip": "$tooltip", "class": "$class", "percentage": $percentage }
```
The *class* parameter also accepts an array of strings.
If nothing or an invalid option is specified, Waybar expects i3blocks style output. Values are *newline* separated.
This should look like this:
```
$text\\n$tooltip\\n$class*
```
*class* is a CSS class, to apply different styles in *style.css*
# FORMAT REPLACEMENTS
*{}*: Output of the script.
*{percentage}* Percentage which can be set via a json return-type.
*{icon}*: An icon from 'format-icons' according to percentage.
# EXAMPLES
## Spotify:
```
"custom/spotify": {
"format": " {}",
"max-length": 40,
"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-if": "pgrep spotify"
}
```
## mpd:
```
"custom/mpd": {
"format": "♪ {}",
//"max-length": 15,
"interval": 10,
"exec": "mpc current",
"exec-if": "pgrep mpd",
"on-click": "mpc toggle",
"on-click-right": "sonata"
}
```
## cmus:
```
"custom/cmus": {
"format": "♪ {}",
//"max-length": 15,
"interval": 10,
"exec": "cmus-remote -C \"format_print '%a - %t'\"", // artist - title
"exec-if": "pgrep cmus",
"on-click": "cmus-remote -u", //toggle pause
"escape": true //handle markup entities
}
```
## Pacman
```
"custom/pacman": {
"format": "{} ",
"interval": "once",
"exec": "pacman_packages",
"on-click": "update-system",
"signal": 8
}
```
## Alternate Pacman
```
"custom/pacman": {
"format": "{} ",
"interval": 3600, // every hour
"exec": "checkupdates | wc -l", // # of updates
"exec-if": "exit 0", // always run; consider advanced run conditions
"on-click": "termite -e 'sudo pacman -Syu'; pkill -SIGRTMIN+8 waybar", // update system
"signal": 8
}
```
You can use the signal and update the number of available packages with *pkill -RTMIN+8 waybar*.
# STYLE
- *#custom-<name>*
- *#custom-<name>.<class>*
- *<class>* can be set by the script. For more information see *return-type*

View File

@ -0,0 +1,71 @@
waybar-idle-inhibitor(5)
# NAME
waybar - idle_inhibitor module
# DESCRIPTION
The *idle_inhibitor* module can inhibiting the idle behavior such as screen blanking, locking, and
screensaving, also known as "presentation mode".
# CONFIGURATION
*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.
*on-click*: ++
typeof: string ++
Command to execute when clicked on the module. A click also toggles the state
*on-click-right*: ++
typeof: string ++
Command to execute when you right clicked on the module.
*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
```
"idle_inhibitor": {
"format": "{icon}",
"format-icons": {
"activated": "",
"deactivated": ""
}
}
```

93
man/waybar-memory.5.scd Normal file
View File

@ -0,0 +1,93 @@
waybar-memory(5)
# NAME
waybar - memory module
# DESCRIPTION
The *memory* module displays the current date and time.
# CONFIGURATION
Addressed by *memory*
*interval*: ++
typeof: integer++
default: 30 ++
The interval in which the information gets polled.
*format*: ++
typeof: string ++
default: {percentage}% ++
The format, how information should be displayed.
*rotate*: ++
typeof: integer ++
Positive value to rotate the text label.
*states*: ++
typeof: array ++
A number of memory utilization states which get activated on certain percentage thresholds. See *waybar-states(5)*.
*max-length*: ++
typeof: integer ++
The maximum length in character the module should display.
*on-click*: ++
typeof: string ++
Command to execute when clicked on the module.
*on-click-right*: ++
typeof: string ++
Command to execute when you right clicked on the module.
*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
*{percentage}*: Percentage of memory in use.
*{total}*: Amount of total memory available in GiB.
*{used}*: Amount of used memory in GiB.
*{avail}*: Amount of available memory in GiB.
# EXAMPLES
```
"memory": {
"interval": 30,
"format": "{}% ",
"max-length": 10
}
```
## FORMATTED MEMORY VALUES
```
"memory": {
"interval": 30,
"format": "{used:0.1f}G/{total:0.1f}G "
}
```
# STYLE
- *#memory*

200
man/waybar-mpd.5.scd Normal file
View File

@ -0,0 +1,200 @@
waybar-mpd(5)
# NAME
waybar - mpd module
# DESCRIPTION
The *mpd* module displays information about a running "Music Player Daemon" instance.
# CONFIGURATION
Addressed by *mpd*
*server*: ++
typeof: string ++
The network address or Unix socket path of the MPD server. If empty, connect to the default host.
*port*: ++
typeof: integer ++
The port MPD listens to. If empty, use the default port.
*interval*: ++
typeof: integer++
default: 5 ++
The interval in which the connection to the MPD server is retried
*timeout*: ++
typeof: integer++
default: 30 ++
The timeout for the connection. Change this if your MPD server has a low `connection_timeout` setting
*unknown-tag*: ++
typeof: string ++
default: "N/A" ++
The text to display when a tag is not present in the current song, but used in `format`
*format*: ++
typeof: string ++
default: "{album} - {artist} - {title}" ++
Information displayed when a song is playing or paused
*format-stopped*: ++
typeof: string ++
default: "stopped" ++
Information displayed when the player is stopped.
*format-disconnected*: ++
typeof: string ++
default: "disconnected" ++
Information displayed when the MPD server can't be reached.
*tooltip*: ++
typeof: bool ++
default: true ++
Option to disable tooltip on hover.
*tooltip-format*: ++
typeof: string ++
default: "MPD (connected)" ++
Tooltip information displayed when connected to MPD.
*tooltip-format-disconnected*: ++
typeof: string ++
default: "MPD (disconnected)" ++
Tooltip information displayed when the MPD server can't be reached.
*rotate*: ++
typeof: integer ++
Positive value to rotate the text label.
*max-length*: ++
typeof: integer ++
The maximum length in character the module should display.
*on-click*: ++
typeof: string ++
Command to execute when clicked on the module.
*on-click-right*: ++
typeof: string ++
Command to execute when you right clicked on the module.
*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.
*state-icons*: ++
typeof: object ++
default: {} ++
Icon to show depending on the play/pause state of the player (*{ "playing": "...", "paused": "..." }*)
*consume-icons*: ++
typeof: object ++
default: {} ++
Icon to show depending on the "consume" option (*{ "on": "...", "off": "..." }*)
*random-icons*: ++
typeof: object ++
default: {} ++
Icon to show depending on the "random" option (*{ "on": "...", "off": "..." }*)
*repeat-icons*: ++
typeof: object ++
default: {} ++
Icon to show depending on the "repeat" option (*{ "on": "...", "off": "..." }*)
*single-icons*: ++
typeof: object ++
default: {} ++
Icon to show depending on the "single" option (*{ "on": "...", "off": "..." }*)
# FORMAT REPLACEMENTS
## WHEN PLAYING/PAUSED
*{artist}*: The artist of the current song
*{albumArtist}*: The artist of the current album
*{album}*: The album of the current song
*{title}*: The title of the current song
*{date}*: The date of the current song
*{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)
*{stateIcon}*: The icon corresponding the playing or paused status of the player (see *state-icons* option)
*{consumeIcon}*: The icon corresponding the "consume" option (see *consume-icons* option)
*{randomIcon}*: The icon corresponding the "random" option (see *random-icons* option)
*{repeatIcon}*: The icon corresponding the "repeat" option (see *repeat-icons* option)
*{singleIcon}*: The icon corresponding the "single" option (see *single-icons* option)
## WHEN STOPPED
*{consumeIcon}*: The icon corresponding the "consume" option (see *consume-icons* option)
*{randomIcon}*: The icon corresponding the "random" option (see *random-icons* option)
*{repeatIcon}*: The icon corresponding the "repeat" option (see *repeat-icons* option)
*{singleIcon}*: The icon corresponding the "single" option (see *single-icons* option)
## WHEN DISCONNECTED
Currently, no format replacements when disconnected.
# EXAMPLES
```
"mpd": {
"format": "{stateIcon} {consumeIcon}{randomIcon}{repeatIcon}{singleIcon}{artist} - {album} - {title} ({elapsedTime:%M:%S}/{totalTime:%M:%S}) ",
"format-disconnected": "Disconnected ",
"format-stopped": "{consumeIcon}{randomIcon}{repeatIcon}{singleIcon}Stopped ",
"interval": 2,
"consume-icons": {
"on": " " // Icon shows only when "consume" is on
},
"random-icons": {
"off": "<span color=\"#f53c3c\"></span> ", // Icon grayed out when "random" is off
"on": " "
},
"repeat-icons": {
"on": " "
},
"single-icons": {
"on": "1 "
},
"state-icons": {
"paused": "",
"playing": ""
},
"tooltip-format": "MPD (connected)",
"tooltip-format-disconnected": "MPD (disconnected)"
}
```
# STYLE
- *#mpd*
- *#mpd.disconnected*
- *#mpd.stopped*
- *#mpd.playing*
- *#mpd.paused*

143
man/waybar-network.5.scd Normal file
View File

@ -0,0 +1,143 @@
waybar-network(5)
# NAME
waybar - network module
# DESCRIPTION
The *network* module displays information about the current network connections.
# CONFIGURATION
Addressed by *network*
*interface*: ++
typeof: string ++
Use the defined interface instead of auto detection. Accepts wildcard.
*interval*: ++
typeof: integer ++
default: 60 ++
The interval in which the network information gets polled (e.g. signal strength).
*format*: ++
typeof: string ++
default: *{ifname}* ++
The format, how information should be displayed. This format is used when other formats aren't specified.
*format-ethernet*: ++
typeof: string ++
This format is used when a ethernet interface is displayed.
*format-wifi*: ++
typeof: string ++
This format is used when a wireless interface is displayed.
*format-linked*: ++
typeof: string ++
This format is used when a linked interface with no ip address is displayed.
*format-disconnected*: ++
typeof: string ++
This format is used when the displayed interface is disconnected.
*rotate*: ++
typeof: integer ++
Positive value to rotate the text label.
*max-length*: ++
typeof: integer ++
The maximum length in character the module should display.
*on-click*: ++
typeof: string ++
Command to execute when clicked on the module.
*on-click-right*: ++
typeof: string ++
Command to execute when you right clicked on the module.
*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.
*tooltip-format*: ++
typeof: string ++
The format, how information should be displayed in the tooltip. This format is used when other formats aren't specified.
*tooltip-format-ethernet*: ++
typeof: string ++
This format is used when a ethernet interface is displayed.
*tooltip-format-wifi*: ++
typeof: string ++
This format is used when a wireless interface is displayed.
*tooltip-format-disconnected*: ++
typeof: string ++
This format is used when the displayed interface is disconnected.
# FORMAT REPLACEMENTS
*{ifname}*: Name of the network interface.
*{ipaddr}*: The first IP of the interface.
*{netmask}*: The subnetmask corresponding to the IP.
*{cidr}*: The subnetmask corresponding to the IP in CIDR notation.
*{essid}*: Name (SSID) of the wireless network.
*{signalStrength}*: Signal strength of the wireless network.
*{signaldBm}*: Signal strength of the wireless network in dBm.
*{frequency}*: Frequency of the wireless network in MHz.
*{bandwidthUpBits}*: Instant up speed in bits/seconds.
*{bandwidthDownBits}*: Instant down speed in bits/seconds.
*{bandwidthUpOctets}*: Instant up speed in octets/seconds.
*{bandwidthDownOctets}*: Instant down speed in octets/seconds.
# EXAMPLES
```
"network": {
"interface": "wlp2s0",
"format": "{ifname}",
"format-wifi": "{essid} ({signalStrength}%) ",
"format-ethernet": "{ifname} ",
"format-disconnected": "", //An empty format will hide the module.
"tooltip-format": "{ifname}",
"tooltip-format-wifi": "{essid} ({signalStrength}%) ",
"tooltip-format-ethernet": "{ifname} ",
"tooltip-format-disconnected": "Disconnected",
"max-length": 50
}
```
# STYLE
- *#network*
- *#network.disconnected*
- *#network.linked*
- *#network.ethernet*
- *#network.wifi*

132
man/waybar-pulseaudio.5.scd Normal file
View File

@ -0,0 +1,132 @@
waybar-pulseaudio(5)
# NAME
waybar - pulseaudio module
# DESCRIPTION
The *pulseaudio* module displays the current volume reported by PulseAudio.
Additionally you can control the volume by scrolling *up* or *down* while the cursor is over the module.
# CONFIGURATION
*format*: ++
typeof: string ++
default: {volume}% ++
The format, how information should be displayed. This format is used when other formats aren't specified.
*format-bluetooth*: ++
typeof: string ++
This format is used when using bluetooth speakers.
*format-muted*: ++
typeof: string ++
This format is used when the sound is muted.
*format-source*: ++
typeof: string ++
default: {volume}% ++
This format used for the source.
*format-source-muted*: ++
typeof: string ++
This format is used when the source is muted.
*format-icons*: ++
typeof: array ++
Based on the current port-name and volume, the corresponding icon gets selected. The order is *low* to *high*. See [`Icons`](#module-pulseaudio-config-icons)
*rotate*: ++
typeof: integer ++
Positive value to rotate the text label.
*states*: ++
typeof: array ++
A number of volume states which get activated on certain volume levels. See *waybar-states(5)*
*max-length*: ++
typeof: integer ++
The maximum length in character the module should display.
*scroll-step*: ++
typeof: float ++
default: 1.0 ++
The speed in which to change the volume when scrolling.
*on-click*: ++
typeof: string ++
Command to execute when clicked on the module.
*on-click-right*: ++
typeof: string ++
Command to execute when you right clicked on the module.
*on-scroll-up*: ++
typeof: string ++
Command to execute when scrolling up on the module. This replaces the default behaviour of volume control.
*on-scroll-down*: ++
typeof: string ++
Command to execute when scrolling down on the module. This replaces the default behaviour of volume control.
*smooth-scrolling-threshold*: ++
typeof: double ++
Threshold to be used when scrolling.
*tooltip*: ++
typeof: bool ++
default: true ++
Option to disable tooltip on hover.
# FORMAT REPLACEMENTS
*{volume}*: Volume in percentage.
*{icon}*: Icon, as defined in `format-icons`.
*{format_source}*: Source format, `format-source`, `format-source-muted`.
# ICONS:
The following strings for `format-icons` are supported.
If they are found in the current PulseAudio port name, the corresponding icons will be selected.
- *default* (Shown, when no other port is found)
- *headphones*
- *speaker*
- *hdmi*
- *headset*
- *handsfree*
- *portable*
- *car*
- *hifi*
- *phone*
# EXAMPLES
```
"pulseaudio": {
"format": "{volume}% {icon}",
"format-bluetooth": "{volume}% {icon}",
"format-muted": "",
"format-icons": {
"headphones": "",
"handsfree": "",
"headset": "",
"phone": "",
"portable": "",
"car": "",
"default": ["", ""]
},
"scroll-step": 1,
"on-click": "pavucontrol"
}
```
# STYLE
- *#pulseaudio*
- *#pulseaudio.bluetooth*
- *#pulseaudio.muted*

43
man/waybar-states.5.scd Normal file
View File

@ -0,0 +1,43 @@
waybar-states(5)
# OVERVIEW
Some modules support 'states' which allows percentage values to be used as styling triggers to
apply a class when the value matches the declared state value.
# STATES
- Every entry (*state*) consits of a *<name>* (typeof: *string*) and a *<value>* (typeof: *integer*).
- The state can be addressed as a CSS class in the *style.css*. The name of the CSS class is the *<name>* of the state.
Each class gets activated when the current capacity is equal or below the configured *<value>*.
- Also each state can have its own *format*.
Those con be configured via *format-<name>*.
Or if you want to differentiate a bit more even as *format-<status>-<state>*.
# EXAMPLE
```
"battery": {
"bat": "BAT2",
"interval": 60,
"states": {
"warning": 30,
"critical": 15
},
"format": "{capacity}% {icon}",
"format-icons": ["", "", "", "", ""],
"max-length": 25
}
```
# STYLING STATES
- *#battery.<state>*
- *<state>* can be defined in the *config*.
# EXAMPLE:
- *#battery.warning: { background: orange; }*
- *#battery.critical: { background: red; }*

View File

@ -0,0 +1,64 @@
waybar-sway-mode(5)
# NAME
waybar - sway mode module
# DESCRIPTION
The *mode* module displays the current binding mode of Sway
# CONFIGURATION
Addressed by *sway/mode*
*format*: ++
typeof: string ++
default: {} ++
The format, how information should be displayed. On {} data gets inserted.
*rotate*: ++
typeof: integer ++
Positive value to rotate the text label.
*max-length*: ++
typeof: integer ++
The maximum length in character the module should display.
*on-click*: ++
typeof: string ++
Command to execute when clicked on the module.
*on-click-right*: ++
typeof: string ++
Command to execute when you right clicked on the module.
*on-scroll-up*: ++
typeof: string ++
Command to execute when scrolling up on the module.
*on-scroll-down*: ++
typeof: string ++
Command to execute when scrolling down on the module.
*smooth-scrolling-threshold*: ++
typeof: double ++
Threshold to be used when scrolling.
*tooltip*: ++
typeof: bool ++
default: true ++
Option to disable tooltip on hover.
# EXAMPLES
```
"sway/window": {
"format": " {}",
"max-length": 50
}
```
# STYLE
- *#mode*

View File

@ -0,0 +1,67 @@
waybar-sway-window(5)
# NAME
waybar - sway window module
# DESCRIPTION
The *window* module displays the title of the currently focused window in Sway
# CONFIGURATION
Addressed by *sway/window*
*format*: ++
typeof: string ++
default: {} ++
The format, how information should be displayed. On {} data gets inserted.
*rotate*: ++
typeof: integer ++
Positive value to rotate the text label.
*max-length*: ++
typeof: integer ++
The maximum length in character the module should display.
*on-click*: ++
typeof: string ++
Command to execute when clicked on the module.
*on-click-right*: ++
typeof: string ++
Command to execute when you right clicked on the module.
*on-scroll-up*: ++
typeof: string ++
Command to execute when scrolling up on the module.
*on-scroll-down*: ++
typeof: string ++
Command to execute when scrolling down on the module.
*smooth-scrolling-threshold*: ++
typeof: double ++
Threshold to be used when scrolling.
*tooltip*: ++
typeof: bool ++
default: true ++
Option to disable tooltip on hover.
# EXAMPLES
```
"sway/window": {
"format": "{}",
"max-length": 50
}
```
# STYLE
- *#window*
- *window#waybar.empty* When no windows is in the workspace
- *window#waybar.solo* When one window is in the workspace
- *window#waybar.<app_id>* Where *app_id* is the app_id or *instance* name like (*chromium*) of the only window in the workspace

View File

@ -0,0 +1,123 @@
waybar-sway-workspaces(5)
# NAME
waybar - sway workspaces module
# DESCRIPTION
The *workspaces* module displays the currently used workspaces in Sway.
# CONFIGURATION
Addressed by *sway/workspaces*
*all-outputs*: ++
typeof: bool ++
default: false ++
If set to false, workspaces will only be shown on the output they are on. If set to true all workspaces will be shown on every output.
*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*.
*disable-scroll*: ++
typeof: bool ++
default: false ++
If set to false, you can scroll to cycle through workspaces. If set to true this behaviour is disabled.
*smooth-scrolling-threshold*: ++
typeof: double ++
Threshold to be used when scrolling.
*disable-scroll-wraparound*: ++
typeof: bool ++
default: false ++
If set to false, scrolling on the workspace indicator will wrap around to the first workspace when reading the end, and vice versa. If set to true this behavior is disabled.
*enable-bar-scroll*: ++
typeof: bool ++
default: false ++
If set to false, you can't scroll to cycle throughout workspaces from the entire bar. If set to true this behaviour is enabled.
*disable-markup*: ++
typeof: bool ++
default: false ++
If set to true, button label will escape pango markup.
*current-only*: ++
typeof: bool ++
default: false ++
If set to true. Only focused workspaces will be shown.
*persistent_workspaces*: ++
typeof: json (see below) ++
default: empty ++
Lists workspaces that should always be shown, even when non existent
# FORMAT REPLACEMENTS
*{name}*: Name of the workspace, as defined by sway.
*{icon}*: Icon, as defined in *format-icons*.
*{index}*: Index of the workspace.
# ICONS
Additional to workspace name matching, the following *format-icons* can be set.
- *default*: Will be shown, when no string matches is found.
- *urgent*: Will be shown, when workspace is flagged as urgent
- *focused*: Will be shown, when workspace is focused
# PERSISTANT WORKSPACES
Each entry of *persistant_workspace* names a workspace that should always be shown.
Associated with that value is a list of outputs indicating *where* the workspace should be shown,
an empty list denoting all outputs.
```
"sway/workspaces": {
"persistant_workspaces": {
"3": [], // Always show a workspace with name '3', on all outputs if it does not exists
"4": ["eDP-1"], // Always show a workspace with name '4', on output 'eDP-1' if it does not exists
"5": ["eDP-1", "DP-2"] // Always show a workspace with name '5', on outputs 'eDP-1' and 'DP-2' if it does not exists
}
}
```
n.b.: the list of outputs can be obtained from command line using *swaymsg -t get_outputs*
# EXAMPLES
```
"sway/workspaces": {
"disable-scroll": true,
"all-outputs": true,
"format": "{name}: {icon}",
"format-icons": {
"1": "",
"2": "",
"3": "",
"4": "",
"5": "",
"urgent": "",
"focused": "",
"default": ""
}
}
```
# Style
- *#workspaces button*
- *#workspaces button.visible*
- *#workspaces button.focused*
- *#workspaces button.urgent*
- *#workspaces button.persistant*

View File

@ -0,0 +1,99 @@
waybar-temperature(5)
# NAME
waybar - temperature module
# DESCRIPTION
The *temperature* module displays the current temperature from a thermal zone.
# CONFIGURATION
Addressed by *temperature*
*thermal-zone*: ++
typeof: integer ++
The thermal zone, as in */sys/class/thermal/*.
*hwmon-path*: ++
typeof: string ++
The temperature path to use, e.g. */sys/class/hwmon/hwmon2/temp1_input* instead of one in */sys/class/thermal/*.
*critical-threshold*: ++
typeof: integer ++
The threshold before it is considered critical (Celcius).
*interval*: ++
typeof: integer ++
default: 10 ++
The interval in which the information gets polled.
*format-critical*: ++
typeof: string ++
The format to use when temperature is considered critical
*format*: ++
typeof: string ++
default: {temperatureC}°C ++
The format (Celcius/Farenheit) in which the temperature should be displayed.
*format-icons*: ++
typeof: array ++
Based on the current temperature (Celcius) and *critical-threshold* if available, the corresponding icon gets selected. The order is *low* to *high*.
*rotate*: ++
typeof: integer ++
Positive value to rotate the text label.
*max-length*: ++
typeof: integer ++
The maximum length in characters the module should display.
*on-click*: ++
typeof: string ++
Command to execute when you clicked on the module.
*on-click-right*: ++
typeof: string ++
Command to execute when you right clicked on the module.
*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
*{temperatureC}*: Temperature in Celcius.
*{temperatureF}*: Temperature in Fahrenheit.
# EXAMPLES
```
"temperature": {
// "thermal-zone": 2,
// "hwmon-path": "/sys/class/hwmon/hwmon2/temp1_input",
// "critical-threshold": 80,
// "format-critical": "{temperatureC}°C ",
"format": "{temperatureC}°C "
}
```
# STYLE
- *#temperature*
- *#temperature.critical*

35
man/waybar-tray.5.scd Normal file
View File

@ -0,0 +1,35 @@
waybar-tray(5)
# NAME
waybar - tray module
# DESCRIPTION
_WARNING_ *tray* is still in beta. There may me bugs. Breaking changes may occur.
# CONFIGURATION
Addressed by *tray*
*icon-size*: ++
typeof: integer ++
Defines the size of the tray icons.
*spacing*: ++
typeof: integer ++
Defines the spacing between the tray icons.
# EXAMPLES
```
"tray": {
"icon-size": 21,
"spacing": 10
}
```
# STYLE
- *#tray*

193
man/waybar.5.scd Normal file
View File

@ -0,0 +1,193 @@
waybar(5)
# NAME
waybar - configuration file
# DESCRIPTION
The configuration uses the JSON file format and is named *config*.
Valid locations for this file are:
- *$XDG_CONFIG_HOME/waybar/config*
- *~/.config/waybar/config*
- *~/waybar/config*
- */etc/xdg/waybar/config*
A good starting point is the default configuration found at https://github.com/Alexays/Waybar/blob/master/resources/config.
Also a minimal example configuration can be found on the at the bottom of this man page.
# BAR CONFIGURATION
*layer* ++
typeof: string ++
default: bottom ++
Decide if the bar is displayed in front of the windows or behind them.
*output* ++
typeof: string|array ++
Specifies on which screen this bar will be displayed.
*position* ++
typeof: string ++
default: top ++
Bar position, can be *top*, *bottom*, *left*, *right*.
*height* ++
typeof: integer ++
Height to be used by the bar if possible. Leave blank for a dynamic value.
*width* ++
typeof: integer ++
Width to be used by the bar if possible. Leave blank for a dynamic value.
*modules-left* ++
typeof: array ++
Modules that will be displayed on the left.
*modules-center* ++
typeof: array ++
Modules that will be displayed in the center.
*modules-right* ++
typeof: array
Modules that will be displayed on the right.
*margin* ++
typeof: string ++
Margins value using the CSS format without units.
*margin-<top\|left\|bottom\|right>* ++
typeof: integer ++
Margins value without units.
*name* ++
typeof: string ++
Optional name added as a CSS class, for styling multiple waybars.
# MODULE FORMAT
You can use PangoMarkupFormat (See https://developer.gnome.org/pango/stable/PangoMarkupFormat.html#PangoMarkupFormat).
e.g.
```
"format": "<span style=\"italic\">{}</span>"
```
# MULTIPLE INSTANCES OF A MODULE
If you want to have a second instance of a module, you can suffix it by a '#' and a custom name.
For example if you want a second battery module, you can add *"battery#bat2"* to your modules.
To configure the newly added module, you then also add a module configuration with the same name.
This could then look something like this *(this is an incomplete example)*:
```
"modules-right": ["battery", "battery#bat2"],
"battery": {
"bat": "BAT1"
},
"battery#bat2": {
"bat": "BAT2"
}
```
# MINIMAL CONFIGURATION
A minimal *config* file could look like this:
```
{
"layer": "top",
"modules-left": ["sway/workspaces", "sway/mode"],
"modules-center": ["sway/window"],
"modules-right": ["battery", "clock"],
"sway/window": {
"max-length": 50
},
"battery": {
"format": "{capacity}% {icon}",
"format-icons": ["", "", "", "", ""]
},
"clock": {
"format-alt": "{:%a, %d. %b %H:%M}"
}
}
```
# MULTI OUTPUT CONFIGURATION
## Limit a configuration to some outputs
```
{
"layer": "top",
"output": "eDP-1",
"modules-left": ["sway/workspaces", "sway/mode"],
...
}
```
```
{
"layer": "top",
"output": ["eDP-1", "VGA"],
"modules-left": ["sway/workspaces", "sway/mode"],
...
}
```
## Configuration of multiple outputs
Don't specify an output to create multiple bars on the same screen.
```
[{
"layer": "top",
"output": "eDP-1",
"modules-left": ["sway/workspaces", "sway/mode"],
...
}, {
"layer": "top",
"output": "VGA",
"modules-right": ["clock"],
...
}]
```
## Rotating modules
When positioning Waybar on the left or right side of the screen, sometimes it's useful to be able to rotate the contents of a module so the text runs vertically. This can be done using the "rotate" property of the module. Example:
```
{
"clock": {
"rotate": 90
}
}
```
Valid options for the "rotate" property are: 0, 90, 180 and 270.
# SUPPORTED MODULES
- *waybar-backlight(5)*
- *waybar-battery(5)*
- *waybar-clock(5)*
- *waybar-cpu(5)*
- *waybar-custom(5)*
- *waybar-idle-inhibitor(5)*
- *waybar-memory(5)*
- *waybar-mdp(5)*
- *waybar-network(5)*
- *waybar-pulseaudio(5)*
- *waybar-sway-mode(5)*
- *waybar-sway-window(5)*
- *waybar-sway-workspaces(5)*
- *waybar-temperature(5)*
- *waybar-tray(5)*

View File

@ -1,6 +1,6 @@
project( project(
'waybar', 'cpp', 'c', 'waybar', 'cpp', 'c',
version: '0.4.0', version: '0.8.0',
license: 'MIT', license: 'MIT',
default_options : [ default_options : [
'cpp_std=c++17', 'cpp_std=c++17',
@ -12,7 +12,7 @@ project(
cpp_args = [] cpp_args = []
cpp_link_args = [] cpp_link_args = []
if false # libc++ if get_option('libcxx')
cpp_args += ['-stdlib=libc++'] cpp_args += ['-stdlib=libc++']
cpp_link_args += ['-stdlib=libc++', '-lc++abi'] cpp_link_args += ['-stdlib=libc++', '-lc++abi']
@ -34,7 +34,12 @@ else
endif endif
if not compiler.has_header('filesystem') if not compiler.has_header('filesystem')
add_project_arguments('-DFILESYSTEM_EXPERIMENTAL', language: 'cpp') if compiler.has_header('experimental/filesystem')
add_project_arguments('-DFILESYSTEM_EXPERIMENTAL', language: 'cpp')
else
add_project_arguments('-DNO_FILESYSTEM', language: 'cpp')
warning('No filesystem header found, some modules may not work')
endif
endif endif
add_global_arguments(cpp_args, language : 'cpp') add_global_arguments(cpp_args, language : 'cpp')
@ -42,11 +47,11 @@ add_global_link_arguments(cpp_link_args, language : 'cpp')
thread_dep = dependency('threads') thread_dep = dependency('threads')
libinput = dependency('libinput') libinput = dependency('libinput')
fmt = dependency('fmt', version : ['>=5.2.1'], fallback : ['fmt', 'fmt_dep']) fmt = dependency('fmt', version : ['>=5.3.0'], fallback : ['fmt', 'fmt_dep'])
spdlog = dependency('spdlog', version : ['>=1.3.1'], fallback : ['spdlog', 'spdlog_dep'])
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')
wlroots = dependency('wlroots', fallback: ['wlroots', 'wlroots'])
gtkmm = dependency('gtkmm-3.0') gtkmm = dependency('gtkmm-3.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'))
@ -56,22 +61,25 @@ 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'))
libmpdclient = dependency('libmpdclient', required: get_option('mpd'))
src_files = files( src_files = files(
'src/factory.cpp', 'src/factory.cpp',
'src/AModule.cpp',
'src/ALabel.cpp', 'src/ALabel.cpp',
'src/modules/memory.cpp', 'src/modules/memory.cpp',
'src/modules/battery.cpp', 'src/modules/battery.cpp',
'src/modules/clock.cpp', 'src/modules/clock.cpp',
'src/modules/custom.cpp', 'src/modules/custom.cpp',
'src/modules/cpu.cpp', 'src/modules/cpu.cpp',
'src/modules/idle_inhibitor.cpp', 'src/modules/idle_inhibitor.cpp',
'src/modules/temperature.cpp',
'src/main.cpp', 'src/main.cpp',
'src/bar.cpp', 'src/bar.cpp',
'src/client.cpp' 'src/client.cpp'
) )
if find_program('sway', required : false).found() if true # find_program('sway', required : false).found()
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',
@ -106,6 +114,11 @@ if libudev.found()
src_files += 'src/modules/backlight.cpp' src_files += 'src/modules/backlight.cpp'
endif endif
if libmpdclient.found()
add_project_arguments('-DHAVE_LIBMPDCLIENT', language: 'cpp')
src_files += 'src/modules/mpd.cpp'
endif
subdir('protocol') subdir('protocol')
executable( executable(
@ -113,10 +126,10 @@ executable(
src_files, src_files,
dependencies: [ dependencies: [
thread_dep, thread_dep,
wlroots,
client_protos, client_protos,
wayland_client, wayland_client,
fmt, fmt,
spdlog,
sigcpp, sigcpp,
jsoncpp, jsoncpp,
libinput, libinput,
@ -127,7 +140,8 @@ executable(
libnl, libnl,
libnlgen, libnlgen,
libpulse, libpulse,
libudev libudev,
libmpdclient
], ],
include_directories: [include_directories('include')], include_directories: [include_directories('include')],
install: true, install: true,
@ -139,6 +153,50 @@ install_data(
install_dir: join_paths(get_option('out'), 'etc/xdg/waybar') install_dir: join_paths(get_option('out'), 'etc/xdg/waybar')
) )
scdoc = dependency('scdoc', version: '>=1.9.2', native: true, required: false)
if scdoc.found()
scdoc_prog = find_program(scdoc.get_pkgconfig_variable('scdoc'), native: true)
sh = find_program('sh', native: true)
mandir = get_option('mandir')
man_files = [
'waybar.5.scd',
'waybar-backlight.5.scd',
'waybar-battery.5.scd',
'waybar-clock.5.scd',
'waybar-cpu.5.scd',
'waybar-custom.5.scd',
'waybar-idle-inhibitor.5.scd',
'waybar-memory.5.scd',
'waybar-mpd.5.scd',
'waybar-network.5.scd',
'waybar-pulseaudio.5.scd',
'waybar-sway-mode.5.scd',
'waybar-sway-window.5.scd',
'waybar-sway-workspaces.5.scd',
'waybar-temperature.5.scd',
'waybar-tray.5.scd',
'waybar-states.5.scd',
]
foreach filename : man_files
topic = filename.split('.')[-3].split('/')[-1]
section = filename.split('.')[-2]
output = '@0@.@1@'.format(topic, section)
custom_target(
output,
input: 'man/@0@'.format(filename),
output: output,
command: [
sh, '-c', '@0@ < @INPUT@ > @1@'.format(scdoc_prog.path(), output)
],
install: true,
install_dir: '@0@/man@1@'.format(mandir, section)
)
endforeach
endif
clangtidy = find_program('clang-tidy', required: false) clangtidy = find_program('clang-tidy', required: false)
if clangtidy.found() if clangtidy.found()

View File

@ -1,5 +1,7 @@
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('pulseaudio', type: 'feature', value: 'auto', description: 'Enable support for pulseaudio') option('pulseaudio', type: 'feature', value: 'auto', description: 'Enable support for pulseaudio')
option('dbusmenu-gtk', type: 'feature', value: 'auto', description: 'Enable support for tray') option('dbusmenu-gtk', type: 'feature', value: 'auto', description: 'Enable support for tray')
option('mpd', type: 'feature', value: 'auto', description: 'Enable support for the Music Player Daemon')
option('out', type: 'string', value : '/', description: 'output prefix directory') option('out', type: 'string', value : '/', description: 'output prefix directory')

View File

@ -1,12 +1,12 @@
{ {
"layer": "top", // Waybar at top layer "layer": "top", // Waybar at top layer
// "position": "bottom", // Waybar at the bottom of your screen // "position": "bottom", // Waybar position (top|bottom|left|right)
// "height": 30, // Waybar height "height": 30, // Waybar height (to be removed for auto height)
// "width": 1280, // Waybar width // "width": 1280, // Waybar width
// Choose the order of the modules // Choose the order of the modules
"modules-left": ["sway/workspaces", "sway/mode", "custom/spotify"], "modules-left": ["sway/workspaces", "sway/mode", "custom/media"],
"modules-center": ["sway/window"], "modules-center": ["sway/window"],
"modules-right": ["idle_inhibitor", "pulseaudio", "network", "cpu", "memory", "backlight", "battery", "battery#bat2", "clock", "tray"], "modules-right": ["mpd", "idle_inhibitor", "pulseaudio", "network", "cpu", "memory", "temperature", "backlight", "battery", "battery#bat2", "clock", "tray"],
// Modules configuration // Modules configuration
// "sway/workspaces": { // "sway/workspaces": {
// "disable-scroll": true, // "disable-scroll": true,
@ -26,6 +26,32 @@
"sway/mode": { "sway/mode": {
"format": "<span style=\"italic\">{}</span>" "format": "<span style=\"italic\">{}</span>"
}, },
"mpd": {
"format": "{stateIcon} {consumeIcon}{randomIcon}{repeatIcon}{singleIcon}{artist} - {album} - {title} ({elapsedTime:%M:%S}/{totalTime:%M:%S}) ",
"format-disconnected": "Disconnected ",
"format-stopped": "{consumeIcon}{randomIcon}{repeatIcon}{singleIcon}Stopped ",
"unknown-tag": "N/A",
"interval": 2,
"consume-icons": {
"on": " "
},
"random-icons": {
"off": "<span color=\"#f53c3c\"></span> ",
"on": " "
},
"repeat-icons": {
"on": " "
},
"single-icons": {
"on": "1 "
},
"state-icons": {
"paused": "",
"playing": ""
},
"tooltip-format": "MPD (connected)",
"tooltip-format-disconnected": "MPD (disconnected)"
},
"idle_inhibitor": { "idle_inhibitor": {
"format": "{icon}", "format": "{icon}",
"format-icons": { "format-icons": {
@ -42,11 +68,20 @@
"format-alt": "{:%Y-%m-%d}" "format-alt": "{:%Y-%m-%d}"
}, },
"cpu": { "cpu": {
"format": "{usage}% " "format": "{usage}% ",
"tooltip": false
}, },
"memory": { "memory": {
"format": "{}% " "format": "{}% "
}, },
"temperature": {
// "thermal-zone": 2,
// "hwmon-path": "/sys/class/hwmon/hwmon2/temp1_input",
"critical-threshold": 80,
// "format-critical": "{temperatureC}°C {icon}",
"format": "{temperatureC}°C {icon}",
"format-icons": ["", "", ""]
},
"backlight": { "backlight": {
// "device": "acpi_video1", // "device": "acpi_video1",
"format": "{percent}% {icon}", "format": "{percent}% {icon}",
@ -59,6 +94,9 @@
"critical": 15 "critical": 15
}, },
"format": "{capacity}% {icon}", "format": "{capacity}% {icon}",
"format-charging": "{capacity}% ",
"format-plugged": "{capacity}% ",
"format-alt": "{time} {icon}",
// "format-good": "", // An empty format will hide the module // "format-good": "", // An empty format will hide the module
// "format-full": "", // "format-full": "",
"format-icons": ["", "", "", "", ""] "format-icons": ["", "", "", "", ""]
@ -67,16 +105,21 @@
"bat": "BAT2" "bat": "BAT2"
}, },
"network": { "network": {
// "interface": "wlp2s0", // (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": "{ifname}: {ipaddr}/{cidr} ",
"format-disconnected": "Disconnected ⚠" "format-linked": "{ifname} (No IP) ",
"format-disconnected": "Disconnected ⚠",
"format-alt": "{ifname}: {ipaddr}/{cidr}"
}, },
"pulseaudio": { "pulseaudio": {
//"scroll-step": 1, // "scroll-step": 1, // %, can be a float
"format": "{volume}% {icon}", "format": "{volume}% {icon} {format_source}",
"format-bluetooth": "{volume}% {icon}", "format-bluetooth": "{volume}% {icon} {format_source}",
"format-muted": "", "format-bluetooth-muted": " {icon} {format_source}",
"format-muted": " {format_source}",
"format-source": "{volume}% ",
"format-source-muted": "",
"format-icons": { "format-icons": {
"headphones": "", "headphones": "",
"handsfree": "", "handsfree": "",
@ -84,13 +127,20 @@
"phone": "", "phone": "",
"portable": "", "portable": "",
"car": "", "car": "",
"default": ["", ""] "default": ["", "", ""]
}, },
"on-click": "pavucontrol" "on-click": "pavucontrol"
}, },
"custom/spotify": { "custom/media": {
"format": " {}", "format": "{icon} {}",
"return-type": "json",
"max-length": 40, "max-length": 40,
"format-icons": {
"spotify": "",
"default": "🎜"
},
"escape": true,
"exec": "$HOME/.config/waybar/mediaplayer.py 2> /dev/null" // Script in resources folder "exec": "$HOME/.config/waybar/mediaplayer.py 2> /dev/null" // Script in resources folder
// "exec": "$HOME/.config/waybar/mediaplayer.py --player spotify 2> /dev/null" // Filter player based on name
} }
} }

View File

@ -1,19 +1,34 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import argparse
import logging
import sys import sys
import signal import signal
import gi import gi
import json
gi.require_version('Playerctl', '2.0') gi.require_version('Playerctl', '2.0')
from gi.repository import Playerctl, GLib from gi.repository import Playerctl, GLib
manager = Playerctl.PlayerManager() logger = logging.getLogger(__name__)
loop = GLib.MainLoop()
def write_output(text, player):
logger.info('Writing output')
output = {'text': text,
'class': 'custom-' + player.props.player_name,
'alt': player.props.player_name}
sys.stdout.write(json.dumps(output) + '\n')
sys.stdout.flush()
def on_play(player, status, manager): def on_play(player, status, manager):
logger.info('Received new playback status')
on_metadata(player, player.props.metadata, manager) on_metadata(player, player.props.metadata, manager)
def on_metadata(player, metadata, manager): def on_metadata(player, metadata, manager):
logger.info('Received new metadata')
track_info = '' track_info = ''
if player.props.player_name == 'spotify' and \ if player.props.player_name == 'spotify' and \
@ -23,28 +38,27 @@ def on_metadata(player, metadata, manager):
elif player.get_artist() != '' and player.get_title() != '': elif player.get_artist() != '' and player.get_title() != '':
track_info = '{artist} - {title}'.format(artist=player.get_artist(), track_info = '{artist} - {title}'.format(artist=player.get_artist(),
title=player.get_title()) title=player.get_title())
if player.props.status != 'Playing' and track_info:
track_info = '' + track_info
write_output(track_info, player)
def on_player_appeared(manager, player, selected_player=None):
if player is not None and (selected_player is None or player.name == selected_player):
init_player(manager, player)
else: else:
sys.stdout.write('\n') logger.debug("New player appeared, but it's not the selected player, skipping")
sys.stdout.flush()
return
if player.props.status == 'Playing':
sys.stdout.write(track_info + '\n')
else:
sys.stdout.write('' + track_info + '\n')
sys.stdout.flush()
def on_name_appeared(manager, name):
init_player(name)
def on_player_vanished(manager, player): def on_player_vanished(manager, player):
sys.stdout.write("\n") logger.info('Player has vanished')
sys.stdout.write('\n')
sys.stdout.flush() sys.stdout.flush()
def init_player(name): def init_player(manager, name):
logger.debug('Initialize player: {player}'.format(player=name.name))
player = Playerctl.Player.new_from_name(name) player = Playerctl.Player.new_from_name(name)
player.connect('playback-status', on_play, manager) player.connect('playback-status', on_play, manager)
player.connect('metadata', on_metadata, manager) player.connect('metadata', on_metadata, manager)
@ -53,19 +67,60 @@ def init_player(name):
def signal_handler(sig, frame): def signal_handler(sig, frame):
sys.stdout.write("\n") logger.debug('Received signal to stop, exiting')
sys.stdout.write('\n')
sys.stdout.flush() sys.stdout.flush()
loop.quit() # loop.quit()
sys.exit(0) sys.exit(0)
manager.connect('name-appeared', on_name_appeared) def parse_arguments():
manager.connect('player-vanished', on_player_vanished) parser = argparse.ArgumentParser()
signal.signal(signal.SIGINT, signal_handler) # Increase verbosity with every occurance of -v
signal.signal(signal.SIGTERM, signal_handler) parser.add_argument('-v', '--verbose', action='count', default=0)
for player in manager.props.player_names: # Define for which player we're listening
init_player(player) parser.add_argument('--player')
return parser.parse_args()
def main():
arguments = parse_arguments()
# Initialize logging
logging.basicConfig(stream=sys.stderr, level=logging.DEBUG,
format='%(name)s %(levelname)s %(message)s')
# Logging is set by default to WARN and higher.
# With every occurrence of -v it's lowered by one
logger.setLevel(max((3 - arguments.verbose) * 10, 0))
# Log the sent command line arguments
logger.debug('Arguments received {}'.format(vars(arguments)))
manager = Playerctl.PlayerManager()
loop = GLib.MainLoop()
manager.connect('name-appeared', lambda *args: on_player_appeared(*args, arguments.player))
manager.connect('player-vanished', on_player_vanished)
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
for player in manager.props.player_names:
if arguments.player is not None and arguments.player != player.name:
logger.debug('{player} is not the filtered player, skipping it'
.format(player=player.name)
)
continue
init_player(manager, player)
loop.run()
if __name__ == '__main__':
main()
loop.run()

View File

@ -7,32 +7,78 @@
} }
window#waybar { window#waybar {
background: rgba(43, 48, 59, 0.5); background-color: rgba(43, 48, 59, 0.5);
border-bottom: 3px solid rgba(100, 114, 125, 0.5); border-bottom: 3px solid rgba(100, 114, 125, 0.5);
color: white; color: #ffffff;
transition-property: background-color;
transition-duration: .5s;
}
window#waybar.hidden {
opacity: 0.2;
}
/*
window#waybar.empty {
background-color: transparent;
}
window#waybar.solo {
background-color: #FFFFFF;
}
*/
window#waybar.termite {
background-color: #3F3F3F;
}
window#waybar.chromium {
background-color: #000000;
border: none;
} }
/* https://github.com/Alexays/Waybar/wiki/FAQ#the-workspace-buttons-have-a-strange-hover-effect */
#workspaces button { #workspaces button {
padding: 0 5px; padding: 0 5px;
background: transparent; background-color: transparent;
color: white; color: #ffffff;
border-bottom: 3px solid transparent; border-bottom: 3px solid transparent;
} }
/* https://github.com/Alexays/Waybar/wiki/FAQ#the-workspace-buttons-have-a-strange-hover-effect */
#workspaces button:hover {
background: rgba(0, 0, 0, 0.2);
box-shadow: inherit;
border-bottom: 3px solid #ffffff;
}
#workspaces button.focused { #workspaces button.focused {
background: #64727D; background-color: #64727D;
border-bottom: 3px solid white; border-bottom: 3px solid #ffffff;
}
#workspaces button.urgent {
background-color: #eb4d4b;
} }
#mode { #mode {
background: #64727D; background-color: #64727D;
border-bottom: 3px solid white; border-bottom: 3px solid #ffffff;
} }
#clock, #battery, #cpu, #memory, #backlight, #network, #pulseaudio, #custom-spotify, #tray, #mode, #idle_inhibitor { #clock,
#battery,
#cpu,
#memory,
#temperature,
#backlight,
#network,
#pulseaudio,
#custom-media,
#tray,
#mode,
#idle_inhibitor {
padding: 0 10px; padding: 0 10px;
margin: 0 5px; margin: 0 4px;
color: #ffffff;
} }
#clock { #clock {
@ -41,24 +87,24 @@ window#waybar {
#battery { #battery {
background-color: #ffffff; background-color: #ffffff;
color: black; color: #000000;
} }
#battery.charging { #battery.charging {
color: white; color: #ffffff;
background-color: #26A65B; background-color: #26A65B;
} }
@keyframes blink { @keyframes blink {
to { to {
background-color: #ffffff; background-color: #ffffff;
color: black; color: #000000;
} }
} }
#battery.critical:not(.charging) { #battery.critical:not(.charging) {
background: #f53c3c; background-color: #f53c3c;
color: white; color: #ffffff;
animation-name: blink; animation-name: blink;
animation-duration: 0.5s; animation-duration: 0.5s;
animation-timing-function: linear; animation-timing-function: linear;
@ -66,40 +112,61 @@ window#waybar {
animation-direction: alternate; animation-direction: alternate;
} }
label:focus {
background-color: #000000;
}
#cpu { #cpu {
background: #2ecc71; background-color: #2ecc71;
color: #000000; color: #000000;
} }
#memory { #memory {
background: #9b59b6; background-color: #9b59b6;
} }
#backlight { #backlight {
background: #90b1b1; background-color: #90b1b1;
} }
#network { #network {
background: #2980b9; background-color: #2980b9;
} }
#network.disconnected { #network.disconnected {
background: #f53c3c; background-color: #f53c3c;
} }
#pulseaudio { #pulseaudio {
background: #f1c40f; background-color: #f1c40f;
color: black; color: #000000;
} }
#pulseaudio.muted { #pulseaudio.muted {
background: #90b1b1; background-color: #90b1b1;
color: #2a5c45; color: #2a5c45;
} }
#custom-spotify { #custom-media {
background: #66cc99; background-color: #66cc99;
color: #2a5c45; color: #2a5c45;
min-width: 100px;
}
#custom-media.custom-spotify {
background-color: #66cc99;
}
#custom-media.custom-vlc {
background-color: #ffa000;
}
#temperature {
background-color: #f0932b;
}
#temperature.critical {
background-color: #eb4d4b;
} }
#tray { #tray {
@ -109,3 +176,25 @@ window#waybar {
#idle_inhibitor { #idle_inhibitor {
background-color: #2d3436; background-color: #2d3436;
} }
#idle_inhibitor.activated {
background-color: #ecf0f1;
color: #2d3436;
}
#mpd {
background-color: #66cc99;
color: #2a5c45;
}
#mpd.disconnected {
background-color: #f53c3c;
}
#mpd.stopped {
background-color: #90b1b1;
}
#mpd.paused {
background-color: #51a37a;
}

View File

@ -1,93 +1,40 @@
#include "ALabel.hpp" #include "ALabel.hpp"
#include <fmt/format.h>
#include <util/command.hpp> #include <util/command.hpp>
#include <iostream> namespace waybar {
waybar::ALabel::ALabel(const Json::Value& config, const std::string format, uint16_t interval) ALabel::ALabel(const Json::Value& config, const std::string& name, const std::string& id,
: config_(config), const std::string& format, uint16_t interval, bool ellipsize)
format_(config_["format"].isString() ? config_["format"].asString() : format), : AModule(config, name, id, config["format-alt"].isString()),
interval_(std::chrono::seconds(config_["interval"].isUInt() format_(config_["format"].isString() ? config_["format"].asString() : format),
? config_["interval"].asUInt() : interval)), default_format_(format_) interval_(config_["interval"] == "once"
{ ? std::chrono::seconds(100000000)
: std::chrono::seconds(
config_["interval"].isUInt() ? config_["interval"].asUInt() : interval)),
default_format_(format_) {
label_.set_name(name);
if (!id.empty()) {
label_.get_style_context()->add_class(id);
}
event_box_.add(label_); event_box_.add(label_);
if (config_["max-length"].isUInt()) { if (config_["max-length"].isUInt()) {
label_.set_max_width_chars(config_["max-length"].asUInt()); label_.set_max_width_chars(config_["max-length"].asUInt());
label_.set_ellipsize(Pango::EllipsizeMode::ELLIPSIZE_END); label_.set_ellipsize(Pango::EllipsizeMode::ELLIPSIZE_END);
} } else if (ellipsize && label_.get_max_width_chars() == -1) {
if (config_["format-alt"].isString()) { label_.set_ellipsize(Pango::EllipsizeMode::ELLIPSIZE_END);
event_box_.add_events(Gdk::BUTTON_PRESS_MASK);
event_box_.signal_button_press_event().connect(
sigc::mem_fun(*this, &ALabel::handleToggle));
} }
// configure events' user commands if (config_["rotate"].isUInt()) {
if (config_["on-click"].isString() || config_["on-click-right"].isString()) { label_.set_angle(config["rotate"].asUInt());
event_box_.add_events(Gdk::BUTTON_PRESS_MASK);
event_box_.signal_button_press_event().connect(
sigc::mem_fun(*this, &ALabel::handleToggle));
}
if (config_["on-scroll-up"].isString() || config_["on-scroll-down"].isString()) {
event_box_.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);
event_box_.signal_scroll_event().connect(
sigc::mem_fun(*this, &ALabel::handleScroll));
} }
} }
auto waybar::ALabel::update() -> void { auto ALabel::update() -> void {
// Nothing here // Nothing here
} }
bool waybar::ALabel::handleToggle(GdkEventButton* const& e) { std::string ALabel::getIcon(uint16_t percentage, const std::string& alt, uint16_t max) {
if (config_["on-click"].isString() && e->button == 1) {
waybar::util::command::forkExec(config_["on-click"].asString());
} else if (config_["on-click-right"].isString() && e->button == 3) {
waybar::util::command::forkExec(config_["on-click-right"].asString());
} else {
alt_ = !alt_;
if (alt_) {
format_ = config_["format-alt"].asString();
} else {
format_ = default_format_;
}
}
dp.emit();
return true;
}
bool waybar::ALabel::handleScroll(GdkEventScroll* e) {
// Avoid concurrent scroll event
std::lock_guard<std::mutex> lock(mutex_);
bool direction_up = false;
if (e->direction == GDK_SCROLL_UP) {
direction_up = true;
}
if (e->direction == GDK_SCROLL_DOWN) {
direction_up = false;
}
if (e->direction == GDK_SCROLL_SMOOTH) {
gdouble delta_x, delta_y;
gdk_event_get_scroll_deltas(reinterpret_cast<const GdkEvent*>(e),
&delta_x, &delta_y);
if (delta_y < 0) {
direction_up = true;
} else if (delta_y > 0) {
direction_up = false;
}
}
if (direction_up && config_["on-scroll-up"].isString()) {
waybar::util::command::forkExec(config_["on-scroll-up"].asString());
} else if (config_["on-scroll-down"].isString()) {
waybar::util::command::forkExec(config_["on-scroll-down"].asString());
}
dp.emit();
return true;
}
std::string waybar::ALabel::getIcon(uint16_t percentage, const std::string& alt)
{
auto format_icons = config_["format-icons"]; auto format_icons = config_["format-icons"];
if (format_icons.isObject()) { if (format_icons.isObject()) {
if (!alt.empty() && (format_icons[alt].isString() || format_icons[alt].isArray())) { if (!alt.empty() && (format_icons[alt].isString() || format_icons[alt].isArray())) {
@ -98,7 +45,7 @@ std::string waybar::ALabel::getIcon(uint16_t percentage, const std::string& alt)
} }
if (format_icons.isArray()) { if (format_icons.isArray()) {
auto size = format_icons.size(); auto size = format_icons.size();
auto idx = std::clamp(percentage / (100 / size), 0U, size - 1); auto idx = std::clamp(percentage / ((max == 0 ? 100 : max) / size), 0U, size - 1);
format_icons = format_icons[idx]; format_icons = format_icons[idx];
} }
if (format_icons.isString()) { if (format_icons.isString()) {
@ -107,9 +54,45 @@ std::string waybar::ALabel::getIcon(uint16_t percentage, const std::string& alt)
return ""; return "";
} }
bool waybar::ALabel::tooltipEnabled() bool waybar::ALabel::handleToggle(GdkEventButton* const& e) {
{ if (config_["format-alt-click"].isUInt() && e->button == config_["format-alt-click"].asUInt()) {
return config_["tooltip"].isBool() ? config_["tooltip"].asBool() : true; alt_ = !alt_;
if (alt_ && config_["format-alt"].isString()) {
format_ = config_["format-alt"].asString();
} else {
format_ = default_format_;
}
}
return AModule::handleToggle(e);
} }
waybar::ALabel::operator Gtk::Widget&() { return event_box_; } std::string ALabel::getState(uint8_t value, bool lesser) {
if (!config_["states"].isObject()) {
return "";
}
// Get current state
std::vector<std::pair<std::string, uint8_t>> states;
if (config_["states"].isObject()) {
for (auto it = config_["states"].begin(); it != config_["states"].end(); ++it) {
if (it->isUInt() && it.key().isString()) {
states.emplace_back(it.key().asString(), it->asUInt());
}
}
}
// Sort states
std::sort(states.begin(), states.end(), [&lesser](auto& a, auto& b) {
return lesser ? a.second < b.second : a.second > b.second;
});
std::string valid_state;
for (auto const& state : states) {
if ((lesser ? value <= state.second : value >= state.second) && valid_state.empty()) {
label_.get_style_context()->add_class(state.first);
valid_state = state.first;
} else {
label_.get_style_context()->remove_class(state.first);
}
}
return valid_state;
}
} // namespace waybar

119
src/AModule.cpp Normal file
View File

@ -0,0 +1,119 @@
#include "AModule.hpp"
#include <fmt/format.h>
#include <util/command.hpp>
namespace waybar {
AModule::AModule(const Json::Value& config, const std::string& name, const std::string& id,
bool enable_click, bool enable_scroll)
: config_(std::move(config)) {
// configure events' user commands
if (config_["on-click"].isString() || config_["on-click-middle"].isString() ||
config_["on-click-backward"].isString() || config_["on-click-forward"].isString() ||
config_["on-click-right"].isString() || enable_click) {
event_box_.add_events(Gdk::BUTTON_PRESS_MASK);
event_box_.signal_button_press_event().connect(sigc::mem_fun(*this, &AModule::handleToggle));
}
if (config_["on-scroll-up"].isString() || config_["on-scroll-down"].isString() || enable_scroll) {
event_box_.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);
event_box_.signal_scroll_event().connect(sigc::mem_fun(*this, &AModule::handleScroll));
}
}
AModule::~AModule() {
for (const auto& pid : pid_) {
if (pid != -1) {
kill(-pid, 9);
}
}
}
auto AModule::update() -> void {
// Nothing here
}
bool AModule::handleToggle(GdkEventButton* const& e) {
std::string format;
if (config_["on-click"].isString() && e->button == 1) {
format = config_["on-click"].asString();
} else if (config_["on-click-middle"].isString() && e->button == 2) {
format = config_["on-click-middle"].asString();
} else if (config_["on-click-right"].isString() && e->button == 3) {
format = config_["on-click-right"].asString();
} else if (config_["on-click-forward"].isString() && e->button == 8) {
format = config_["on-click-backward"].asString();
} else if (config_["on-click-backward"].isString() && e->button == 9) {
format = config_["on-click-forward"].asString();
}
if (!format.empty()) {
pid_.push_back(util::command::forkExec(fmt::format(format, fmt::arg("arg", click_param_))));
}
dp.emit();
return true;
}
AModule::SCROLL_DIR AModule::getScrollDir(GdkEventScroll* e) {
switch (e -> direction) {
case GDK_SCROLL_UP: return SCROLL_DIR::UP;
case GDK_SCROLL_DOWN: return SCROLL_DIR::DOWN;
case GDK_SCROLL_LEFT: return SCROLL_DIR::LEFT;
case GDK_SCROLL_RIGHT: return SCROLL_DIR::RIGHT;
case GDK_SCROLL_SMOOTH: {
SCROLL_DIR dir{SCROLL_DIR::NONE};
distance_scrolled_y_ += e->delta_y;
distance_scrolled_x_ += e->delta_x;
gdouble threshold = 0;
if (config_["smooth-scrolling-threshold"].isNumeric()) {
threshold = config_["smooth-scrolling-threshold"].asDouble();
}
if (distance_scrolled_y_ < -threshold) {
dir = SCROLL_DIR::UP;
} else if (distance_scrolled_y_ > threshold) {
dir = SCROLL_DIR::DOWN;
} else if (distance_scrolled_x_ > threshold) {
dir = SCROLL_DIR::RIGHT;
} else if (distance_scrolled_x_ < -threshold) {
dir = SCROLL_DIR::LEFT;
}
switch (dir) {
case SCROLL_DIR::UP:
case SCROLL_DIR::DOWN:
distance_scrolled_y_ = 0;
break;
case SCROLL_DIR::LEFT:
case SCROLL_DIR::RIGHT:
distance_scrolled_x_ = 0;
break;
case SCROLL_DIR::NONE:
break;
}
return dir;
}
// Silence -Wreturn-type:
default: return SCROLL_DIR::NONE;
}
}
bool AModule::handleScroll(GdkEventScroll* e) {
auto dir = getScrollDir(e);
if (dir == SCROLL_DIR::UP && config_["on-scroll-up"].isString()) {
pid_.push_back(util::command::forkExec(config_["on-scroll-up"].asString()));
} else if (dir == SCROLL_DIR::DOWN && config_["on-scroll-down"].isString()) {
pid_.push_back(util::command::forkExec(config_["on-scroll-down"].asString()));
}
dp.emit();
return true;
}
bool AModule::tooltipEnabled() {
return config_["tooltip"].isBool() ? config_["tooltip"].asBool() : true;
}
AModule::operator Gtk::Widget&() { return event_box_; }
} // namespace waybar

View File

@ -1,220 +1,282 @@
#include "bar.hpp" #include "bar.hpp"
#include "client.hpp" #include "client.hpp"
#include "factory.hpp" #include "factory.hpp"
#include "util/json.hpp" #include <spdlog/spdlog.h>
waybar::Bar::Bar(const Client& client, waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config)
std::unique_ptr<struct wl_output *> &&p_output, uint32_t p_wl_name) : output(w_output),
: client(client), window{Gtk::WindowType::WINDOW_TOPLEVEL}, config(w_config),
surface(nullptr), layer_surface(nullptr), window{Gtk::WindowType::WINDOW_TOPLEVEL},
output(std::move(p_output)), wl_name(p_wl_name), surface(nullptr),
left_(Gtk::ORIENTATION_HORIZONTAL, 0), center_(Gtk::ORIENTATION_HORIZONTAL, 0), layer_surface(nullptr),
right_(Gtk::ORIENTATION_HORIZONTAL, 0), box_(Gtk::ORIENTATION_HORIZONTAL, 0) anchor_(ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP),
{ left_(Gtk::ORIENTATION_HORIZONTAL, 0),
static const struct zxdg_output_v1_listener xdgOutputListener = { center_(Gtk::ORIENTATION_HORIZONTAL, 0),
.logical_position = handleLogicalPosition, right_(Gtk::ORIENTATION_HORIZONTAL, 0),
.logical_size = handleLogicalSize, box_(Gtk::ORIENTATION_HORIZONTAL, 0) {
.done = handleDone,
.name = handleName,
.description = handleDescription,
};
xdg_output_ =
zxdg_output_manager_v1_get_xdg_output(client.xdg_output_manager, *output);
zxdg_output_v1_add_listener(xdg_output_, &xdgOutputListener, this);
window.set_title("waybar"); window.set_title("waybar");
window.set_name("waybar"); window.set_name("waybar");
window.set_decorated(false); window.set_decorated(false);
window.set_resizable(false); window.get_style_context()->add_class(output->name);
setupConfig(); window.get_style_context()->add_class(config["name"].asString());
setupCss(); window.get_style_context()->add_class(config["position"].asString());
auto wrap = reinterpret_cast<GtkWidget*>(window.gobj()); if (config["position"] == "right" || config["position"] == "left") {
gtk_widget_realize(wrap); height_ = 0;
GdkWindow *gdk_window = gtk_widget_get_window(wrap); width_ = 1;
gdk_wayland_window_set_use_custom_surface(gdk_window); }
surface = gdk_wayland_window_get_wl_surface(gdk_window); height_ = config["height"].isUInt() ? config["height"].asUInt() : height_;
width_ = config["width"].isUInt() ? config["width"].asUInt() : width_;
window.signal_realize().connect_notify(sigc::mem_fun(*this, &Bar::onRealize));
window.signal_map_event().connect_notify(sigc::mem_fun(*this, &Bar::onMap));
window.signal_configure_event().connect_notify(sigc::mem_fun(*this, &Bar::onConfigure));
window.set_size_request(width_, height_);
if (config["position"] == "bottom") {
anchor_ = ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM;
} else if (config["position"] == "left") {
anchor_ = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT;
} else if (config["position"] == "right") {
anchor_ = ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
}
if (anchor_ == ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM ||
anchor_ == ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP) {
anchor_ |= ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
} else if (anchor_ == ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT ||
anchor_ == ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT) {
anchor_ |= ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM;
left_ = Gtk::Box(Gtk::ORIENTATION_VERTICAL, 0);
center_ = Gtk::Box(Gtk::ORIENTATION_VERTICAL, 0);
right_ = Gtk::Box(Gtk::ORIENTATION_VERTICAL, 0);
box_ = Gtk::Box(Gtk::ORIENTATION_VERTICAL, 0);
vertical = true;
}
setupWidgets();
if (window.get_realized()) {
onRealize();
}
window.show_all();
} }
void waybar::Bar::initBar() void waybar::Bar::onConfigure(GdkEventConfigure* ev) {
{ auto tmp_height = height_;
std::size_t layer_top = config_["layer"] == "top" auto tmp_width = width_;
? ZWLR_LAYER_SHELL_V1_LAYER_TOP : ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM; if (ev->height > static_cast<int>(height_)) {
layer_surface = zwlr_layer_shell_v1_get_layer_surface( // Default minimal value
client.layer_shell, surface, *output, layer_top, "waybar"); if (height_ != 1) {
spdlog::warn(MIN_HEIGHT_MSG, height_, ev->height);
}
if (config["height"].isUInt()) {
spdlog::info(SIZE_DEFINED, "Height");
} else {
tmp_height = ev->height;
}
}
if (ev->width > static_cast<int>(width_)) {
// Default minimal value
if (width_ != 1) {
spdlog::warn(MIN_WIDTH_MSG, width_, ev->width);
}
if (config["width"].isUInt()) {
spdlog::info(SIZE_DEFINED, "Width");
} else {
tmp_width = ev->width;
}
}
if (tmp_width != width_ || tmp_height != height_) {
zwlr_layer_surface_v1_set_size(layer_surface, tmp_width, tmp_height);
}
}
void waybar::Bar::onRealize() {
auto gdk_window = window.get_window()->gobj();
gdk_wayland_window_set_use_custom_surface(gdk_window);
}
void waybar::Bar::onMap(GdkEventAny* ev) {
auto gdk_window = window.get_window()->gobj();
surface = gdk_wayland_window_get_wl_surface(gdk_window);
auto client = waybar::Client::inst();
auto layer =
config["layer"] == "top" ? ZWLR_LAYER_SHELL_V1_LAYER_TOP : ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM;
layer_surface = zwlr_layer_shell_v1_get_layer_surface(
client->layer_shell, surface, output->output, layer, "waybar");
zwlr_layer_surface_v1_set_keyboard_interactivity(layer_surface, false);
zwlr_layer_surface_v1_set_anchor(layer_surface, anchor_);
zwlr_layer_surface_v1_set_size(layer_surface, width_, height_);
setMarginsAndZone(height_, width_);
static const struct zwlr_layer_surface_v1_listener layer_surface_listener = { static const struct zwlr_layer_surface_v1_listener layer_surface_listener = {
.configure = layerSurfaceHandleConfigure, .configure = layerSurfaceHandleConfigure,
.closed = layerSurfaceHandleClosed, .closed = layerSurfaceHandleClosed,
}; };
zwlr_layer_surface_v1_add_listener(layer_surface, &layer_surface_listener, this); zwlr_layer_surface_v1_add_listener(layer_surface, &layer_surface_listener, this);
std::size_t anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT
| ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
if (config_["position"] == "bottom") {
anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM;
} else {
anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP;
}
auto height = config_["height"].isUInt() ? config_["height"].asUInt() : height_;
auto width = config_["width"].isUInt() ? config_["width"].asUInt() : width_;
zwlr_layer_surface_v1_set_anchor(layer_surface, anchor);
zwlr_layer_surface_v1_set_exclusive_zone(layer_surface, height);
zwlr_layer_surface_v1_set_size(layer_surface, width, height);
wl_surface_commit(surface); wl_surface_commit(surface);
wl_display_roundtrip(client->wl_display);
setupWidgets();
} }
void waybar::Bar::handleLogicalPosition(void* /*data*/, void waybar::Bar::setMarginsAndZone(uint32_t height, uint32_t width) {
struct zxdg_output_v1* /*zxdg_output_v1*/, int32_t /*x*/, int32_t /*y*/) if (config["margin-top"].isInt() || config["margin-right"].isInt() ||
{ config["margin-bottom"].isInt() || config["margin-left"].isInt()) {
// Nothing here margins_ = {
config["margin-top"].isInt() ? config["margin-top"].asInt() : 0,
config["margin-right"].isInt() ? config["margin-right"].asInt() : 0,
config["margin-bottom"].isInt() ? config["margin-bottom"].asInt() : 0,
config["margin-left"].isInt() ? config["margin-left"].asInt() : 0,
};
} else if (config["margin"].isString()) {
std::istringstream iss(config["margin"].asString());
std::vector<std::string> margins{std::istream_iterator<std::string>(iss), {}};
try {
if (margins.size() == 1) {
auto gaps = std::stoi(margins[0], nullptr, 10);
margins_ = {.top = gaps, .right = gaps, .bottom = gaps, .left = gaps};
}
if (margins.size() == 2) {
auto vertical_margins = std::stoi(margins[0], nullptr, 10);
auto horizontal_margins = std::stoi(margins[1], nullptr, 10);
margins_ = {.top = vertical_margins,
.right = horizontal_margins,
.bottom = vertical_margins,
.left = horizontal_margins};
}
if (margins.size() == 3) {
auto horizontal_margins = std::stoi(margins[1], nullptr, 10);
margins_ = {.top = std::stoi(margins[0], nullptr, 10),
.right = horizontal_margins,
.bottom = std::stoi(margins[2], nullptr, 10),
.left = horizontal_margins};
}
if (margins.size() == 4) {
margins_ = {.top = std::stoi(margins[0], nullptr, 10),
.right = std::stoi(margins[1], nullptr, 10),
.bottom = std::stoi(margins[2], nullptr, 10),
.left = std::stoi(margins[3], nullptr, 10)};
}
} catch (...) {
spdlog::warn("Invalid margins: {}", config["margin"].asString());
}
} else if (config["margin"].isInt()) {
auto gaps = config["margin"].asInt();
margins_ = {.top = gaps, .right = gaps, .bottom = gaps, .left = gaps};
}
zwlr_layer_surface_v1_set_margin(
layer_surface, margins_.top, margins_.right, margins_.bottom, margins_.left);
auto zone = vertical ? width + margins_.right : height + margins_.bottom;
zwlr_layer_surface_v1_set_exclusive_zone(layer_surface, zone);
} }
void waybar::Bar::handleLogicalSize(void* /*data*/, // Converting string to button code rn as to avoid doing it later
struct zxdg_output_v1* /*zxdg_output_v1*/, int32_t /*width*/, void waybar::Bar::setupAltFormatKeyForModule(const std::string& module_name) {
int32_t /*height*/) if (config.isMember(module_name)) {
{ Json::Value& module = config[module_name];
// Nothing here if (module.isMember("format-alt")) {
} if (module.isMember("format-alt-click")) {
Json::Value& click = module["format-alt-click"];
void waybar::Bar::handleDone(void* /*data*/, if (click.isString()) {
struct zxdg_output_v1* /*zxdg_output_v1*/) if (click == "click-right") {
{ module["format-alt-click"] = 3U;
// Nothing here } else if (click == "click-middle") {
} module["format-alt-click"] = 2U;
} else if (click == "click-backward") {
bool waybar::Bar::isValidOutput(const Json::Value &config) module["format-alt-click"] = 8U;
{ } else if (click == "click-forward") {
bool found = true; module["format-alt-click"] = 9U;
if (config["output"].isArray()) { } else {
bool in_array = false; module["format-alt-click"] = 1U; // default click-left
for (auto const &output : config["output"]) { }
if (output.isString() && output.asString() == output_name) { } else {
in_array = true; module["format-alt-click"] = 1U;
break; }
} else {
module["format-alt-click"] = 1U;
} }
} }
found = in_array;
} }
if (config["output"].isString() && config["output"].asString() != output_name) {
found = false;
}
return found;
} }
void waybar::Bar::handleName(void* data, struct zxdg_output_v1* /*xdg_output*/, void waybar::Bar::setupAltFormatKeyForModuleList(const char* module_list_name) {
const char* name) if (config.isMember(module_list_name)) {
{ Json::Value& modules = config[module_list_name];
auto o = static_cast<waybar::Bar *>(data); for (const Json::Value& module_name : modules) {
o->output_name = name; if (module_name.isString()) {
bool found = true; setupAltFormatKeyForModule(module_name.asString());
if (o->config_.isArray()) {
bool in_array = false;
for (auto const &config : o->config_) {
if (config.isObject() && o->isValidOutput(config)) {
in_array = true;
o->config_ = config;
break;
} }
} }
found = in_array;
} else {
found = o->isValidOutput(o->config_);
}
if (!found) {
wl_output_destroy(*o->output);
zxdg_output_v1_destroy(o->xdg_output_);
} else {
o->initBar();
} }
} }
void waybar::Bar::handleDescription(void* /*data*/, void waybar::Bar::handleSignal(int signal) {
struct zxdg_output_v1* /*zxdg_output_v1*/, const char* /*description*/) for (auto& module : modules_left_) {
{ auto* custom = dynamic_cast<waybar::modules::Custom*>(module.get());
// Nothing here 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());
if (custom != nullptr) {
custom->refresh(signal);
}
}
} }
void waybar::Bar::layerSurfaceHandleConfigure(void* data, void waybar::Bar::layerSurfaceHandleConfigure(void* data, struct zwlr_layer_surface_v1* surface,
struct zwlr_layer_surface_v1* surface, uint32_t serial, uint32_t width, uint32_t serial, uint32_t width, uint32_t height) {
uint32_t height) auto o = static_cast<waybar::Bar*>(data);
{
auto o = static_cast<waybar::Bar *>(data);
zwlr_layer_surface_v1_ack_configure(surface, serial);
if (width != o->width_ || height != o->height_) { if (width != o->width_ || height != o->height_) {
o->width_ = width; o->width_ = width;
o->height_ = height; o->height_ = height;
o->window.set_size_request(o->width_, o->height_); o->window.set_size_request(o->width_, o->height_);
o->window.resize(o->width_, o->height_); o->window.resize(o->width_, o->height_);
auto zone = o->vertical ? width + o->margins_.right : height + o->margins_.bottom;
int dummy_width, min_height; zwlr_layer_surface_v1_set_exclusive_zone(o->layer_surface, zone);
o->window.get_size(dummy_width, min_height); spdlog::info(BAR_SIZE_MSG,
if (o->height_ < static_cast<uint32_t>(min_height)) { o->width_ == 1 ? "auto" : std::to_string(o->width_),
std::cout << fmt::format("Requested height: {} exceeds the minimum \ o->height_ == 1 ? "auto" : std::to_string(o->height_),
height: {} required by the modules", o->height_, min_height) << std::endl; o->output->name);
o->height_ = min_height;
}
std::cout << fmt::format(
"Bar configured (width: {}, height: {}) for output: {}",
o->width_, o->height_, o->output_name) << std::endl;
wl_surface_commit(o->surface); wl_surface_commit(o->surface);
} }
zwlr_layer_surface_v1_ack_configure(surface, serial);
} }
void waybar::Bar::layerSurfaceHandleClosed(void* data, void waybar::Bar::layerSurfaceHandleClosed(void* data, struct zwlr_layer_surface_v1* /*surface*/) {
struct zwlr_layer_surface_v1* /*surface*/) auto o = static_cast<waybar::Bar*>(data);
{ if (o->layer_surface) {
auto o = static_cast<waybar::Bar *>(data); zwlr_layer_surface_v1_destroy(o->layer_surface);
std::cout << "Bar removed from output: " + o->output_name << std::endl; o->layer_surface = nullptr;
zwlr_layer_surface_v1_destroy(o->layer_surface); }
wl_output_destroy(*o->output);
zxdg_output_v1_destroy(o->xdg_output_);
o->modules_left_.clear(); o->modules_left_.clear();
o->modules_center_.clear(); o->modules_center_.clear();
o->modules_right_.clear(); o->modules_right_.clear();
} }
auto waybar::Bar::toggle() -> void auto waybar::Bar::toggle() -> void {
{
visible = !visible; visible = !visible;
auto zone = visible ? height_ : 0; auto zone = visible ? height_ : 0;
if (!visible) {
window.get_style_context()->add_class("hidden");
} else {
window.get_style_context()->remove_class("hidden");
}
zwlr_layer_surface_v1_set_exclusive_zone(layer_surface, zone); zwlr_layer_surface_v1_set_exclusive_zone(layer_surface, zone);
wl_surface_commit(surface); wl_surface_commit(surface);
} }
auto waybar::Bar::setupConfig() -> void void waybar::Bar::getModules(const Factory& factory, const std::string& pos) {
{ if (config[pos].isArray()) {
std::ifstream file(client.config_file); for (const auto& name : config[pos]) {
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::Bar::setupCss() -> void
{
css_provider_ = Gtk::CssProvider::create();
style_context_ = Gtk::StyleContext::create();
// Load our css file, wherever that may be hiding
if (css_provider_->load_from_path(client.css_file)) {
Glib::RefPtr<Gdk::Screen> screen = window.get_screen();
style_context_->add_provider_for_screen(screen, css_provider_,
GTK_STYLE_PROVIDER_PRIORITY_USER);
}
}
void waybar::Bar::getModules(const Factory& factory, const std::string& pos)
{
if (config_[pos].isArray()) {
for (const auto &name : config_[pos]) {
try { try {
auto module = factory.makeModule(name.asString()); auto module = factory.makeModule(name.asString());
if (pos == "modules-left") { if (pos == "modules-left") {
@ -230,36 +292,39 @@ void waybar::Bar::getModules(const Factory& factory, const std::string& pos)
try { try {
module->update(); module->update();
} catch (const std::exception& e) { } catch (const std::exception& e) {
std::cerr << name.asString() + ": " + e.what() << std::endl; spdlog::error("{}: {}", name.asString(), e.what());
} }
}); });
} catch (const std::exception& e) { } catch (const std::exception& e) {
std::cerr << e.what() << std::endl; spdlog::warn("module {}: {}", name.asString(), e.what());
} }
} }
} }
} }
auto waybar::Bar::setupWidgets() -> void auto waybar::Bar::setupWidgets() -> void {
{
window.add(box_); window.add(box_);
box_.pack_start(left_, true, true); box_.pack_start(left_, false, false);
box_.set_center_widget(center_); box_.set_center_widget(center_);
box_.pack_end(right_, true, true); box_.pack_end(right_, false, false);
Factory factory(*this, config_); // Convert to button code for every module that is used.
setupAltFormatKeyForModuleList("modules-left");
setupAltFormatKeyForModuleList("modules-right");
setupAltFormatKeyForModuleList("modules-center");
Factory factory(*this, config);
getModules(factory, "modules-left"); getModules(factory, "modules-left");
getModules(factory, "modules-center"); getModules(factory, "modules-center");
getModules(factory, "modules-right"); getModules(factory, "modules-right");
for (auto const& module : modules_left_) { for (auto const& module : modules_left_) {
left_.pack_start(*module, false, true, 0); left_.pack_start(*module, false, false);
} }
for (auto const& module : modules_center_) { for (auto const& module : modules_center_) {
center_.pack_start(*module, true, true, 0); center_.pack_start(*module, false, false);
} }
std::reverse(modules_right_.begin(), modules_right_.end()); std::reverse(modules_right_.begin(), modules_right_.end());
for (auto const& module : modules_right_) { for (auto const& module : modules_right_) {
right_.pack_end(*module, false, false, 0); right_.pack_end(*module, false, false);
} }
window.show_all();
} }

View File

@ -1,25 +1,19 @@
#include "client.hpp" #include "client.hpp"
#include "util/clara.hpp" #include <spdlog/spdlog.h>
#include <fstream>
#include <iostream> #include <iostream>
#include "util/clara.hpp"
#include "util/json.hpp"
waybar::Client::Client(int argc, char* argv[]) waybar::Client *waybar::Client::inst() {
: gtk_main(argc, argv), static auto c = new Client();
gdk_display(Gdk::Display::get_default()) return c;
{
if (!gdk_display) {
throw std::runtime_error("Can't find display");
}
if (!GDK_IS_WAYLAND_DISPLAY(gdk_display->gobj())) {
throw std::runtime_error("Bar need to run under Wayland");
}
wl_display = gdk_wayland_display_get_wl_display(gdk_display->gobj());
} }
const std::string waybar::Client::getValidPath(std::vector<std::string> paths) const std::string waybar::Client::getValidPath(const std::vector<std::string> &paths) const {
{
wordexp_t p; wordexp_t p;
for (const std::string &path: paths) { for (const std::string &path : paths) {
if (wordexp(path.c_str(), &p, 0) == 0) { if (wordexp(path.c_str(), &p, 0) == 0) {
if (access(*p.we_wordv, F_OK) == 0) { if (access(*p.we_wordv, F_OK) == 0) {
std::string result = *p.we_wordv; std::string result = *p.we_wordv;
@ -33,99 +27,239 @@ const std::string waybar::Client::getValidPath(std::vector<std::string> paths)
return std::string(); return std::string();
} }
void waybar::Client::handleGlobal(void *data, struct wl_registry *registry, void waybar::Client::handleGlobal(void *data, struct wl_registry *registry, uint32_t name,
uint32_t name, const char *interface, uint32_t version) const char *interface, uint32_t version) {
{ auto client = static_cast<Client *>(data);
auto o = static_cast<waybar::Client *>(data);
if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) { if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) {
o->layer_shell = static_cast<struct zwlr_layer_shell_v1 *>( client->layer_shell = static_cast<struct zwlr_layer_shell_v1 *>(
wl_registry_bind(registry, name, &zwlr_layer_shell_v1_interface, version)); wl_registry_bind(registry, name, &zwlr_layer_shell_v1_interface, version));
} else if (strcmp(interface, wl_output_interface.name) == 0) { } else if (strcmp(interface, wl_output_interface.name) == 0) {
auto output = std::make_unique<struct wl_output *>(); auto wl_output = static_cast<struct wl_output *>(
*output = static_cast<struct wl_output *>(wl_registry_bind(registry, name, wl_registry_bind(registry, name, &wl_output_interface, version));
&wl_output_interface, version)); client->outputs_.emplace_back(new struct waybar_output({wl_output, "", name, nullptr}));
if (o->xdg_output_manager != nullptr) { client->handleOutput(client->outputs_.back());
o->bars.emplace_back(std::make_unique<Bar>(*o, std::move(output), name)); } else if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0 &&
} version >= ZXDG_OUTPUT_V1_NAME_SINCE_VERSION) {
} else if (strcmp(interface, wl_seat_interface.name) == 0) { client->xdg_output_manager = static_cast<struct zxdg_output_manager_v1 *>(wl_registry_bind(
o->seat = static_cast<struct wl_seat *>(wl_registry_bind(registry, name, registry, name, &zxdg_output_manager_v1_interface, ZXDG_OUTPUT_V1_NAME_SINCE_VERSION));
&wl_seat_interface, version));
} else if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0
&& version >= ZXDG_OUTPUT_V1_NAME_SINCE_VERSION) {
o->xdg_output_manager = static_cast<struct zxdg_output_manager_v1 *>(
wl_registry_bind(registry, name,
&zxdg_output_manager_v1_interface, ZXDG_OUTPUT_V1_NAME_SINCE_VERSION));
} else if (strcmp(interface, zwp_idle_inhibit_manager_v1_interface.name) == 0) { } else if (strcmp(interface, zwp_idle_inhibit_manager_v1_interface.name) == 0) {
o->idle_inhibit_manager = static_cast<struct zwp_idle_inhibit_manager_v1 *>( client->idle_inhibit_manager = static_cast<struct zwp_idle_inhibit_manager_v1 *>(
wl_registry_bind(registry, name, wl_registry_bind(registry, name, &zwp_idle_inhibit_manager_v1_interface, 1));
&zwp_idle_inhibit_manager_v1_interface, 1));
} }
} }
void waybar::Client::handleGlobalRemove(void* data, void waybar::Client::handleGlobalRemove(void * data, struct wl_registry * /*registry*/,
struct wl_registry* /*registry*/, uint32_t name) uint32_t name) {
{ auto client = static_cast<Client *>(data);
auto o = static_cast<waybar::Client *>(data); for (auto it = client->bars.begin(); it != client->bars.end();) {
for (auto it = o->bars.begin(); it != o->bars.end(); ++it) { if ((*it)->output->wl_name == name) {
if ((**it).wl_name == name) { auto output_name = (*it)->output->name;
o->bars.erase(it); (*it)->window.close();
break; it = client->bars.erase(it);
spdlog::info("Bar removed from output: {}", output_name);
} else {
++it;
} }
} }
auto it = std::find_if(client->outputs_.begin(),
client->outputs_.end(),
[&name](const auto &output) { return output->wl_name == name; });
if (it != client->outputs_.end()) {
if ((*it)->xdg_output != nullptr) {
zxdg_output_v1_destroy((*it)->xdg_output);
(*it)->xdg_output = nullptr;
}
if ((*it)->output != nullptr) {
wl_output_destroy((*it)->output);
(*it)->output = nullptr;
}
client->outputs_.erase(it);
}
} }
void waybar::Client::setupConfigs(const std::string& config, const std::string& style) void waybar::Client::handleOutput(std::unique_ptr<struct waybar_output> &output) {
{ static const struct zxdg_output_v1_listener xdgOutputListener = {
config_file = config.empty() ? getValidPath({ .logical_position = handleLogicalPosition,
"$XDG_CONFIG_HOME/waybar/config", .logical_size = handleLogicalSize,
"$HOME/.config/waybar/config", .done = handleDone,
"$HOME/waybar/config", .name = handleName,
"/etc/xdg/waybar/config", .description = handleDescription,
"./resources/config", };
}) : config; output->xdg_output = zxdg_output_manager_v1_get_xdg_output(xdg_output_manager, output->output);
css_file = style.empty() ? getValidPath({ zxdg_output_v1_add_listener(output->xdg_output, &xdgOutputListener, &output->wl_name);
"$XDG_CONFIG_HOME/waybar/style.css", }
"$HOME/.config/waybar/style.css",
"$HOME/waybar/style.css", void waybar::Client::handleLogicalPosition(void * /*data*/,
"/etc/xdg/waybar/style.css", struct zxdg_output_v1 * /*zxdg_output_v1*/,
"./resources/style.css", int32_t /*x*/, int32_t /*y*/) {
}) : style; // Nothing here
}
void waybar::Client::handleLogicalSize(void * /*data*/, struct zxdg_output_v1 * /*zxdg_output_v1*/,
int32_t /*width*/, int32_t /*height*/) {
// Nothing here
}
void waybar::Client::handleDone(void * /*data*/, struct zxdg_output_v1 * /*zxdg_output_v1*/) {
// Nothing here
}
bool waybar::Client::isValidOutput(const Json::Value & config,
std::unique_ptr<struct waybar_output> &output) {
bool found = true;
if (config["output"].isArray()) {
bool in_array = false;
for (auto const &output_conf : config["output"]) {
if (output_conf.isString() && output_conf.asString() == output->name) {
in_array = true;
break;
}
}
found = in_array;
}
if (config["output"].isString() && config["output"].asString() != output->name) {
found = false;
}
return found;
}
std::unique_ptr<struct waybar::waybar_output> &waybar::Client::getOutput(uint32_t wl_name) {
auto it = std::find_if(outputs_.begin(), outputs_.end(), [&wl_name](const auto &output) {
return output->wl_name == wl_name;
});
if (it == outputs_.end()) {
throw std::runtime_error("Unable to find valid output");
}
return *it;
}
std::vector<Json::Value> waybar::Client::getOutputConfigs(
std::unique_ptr<struct waybar_output> &output) {
std::vector<Json::Value> configs;
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::handleName(void * data, struct zxdg_output_v1 * /*xdg_output*/,
const char *name) {
auto wl_name = *static_cast<uint32_t *>(data);
auto client = waybar::Client::inst();
try {
auto &output = client->getOutput(wl_name);
output->name = name;
auto configs = client->getOutputConfigs(output);
if (configs.empty()) {
if (output->output != nullptr) {
wl_output_destroy(output->output);
output->output = nullptr;
}
if (output->xdg_output != nullptr) {
zxdg_output_v1_destroy(output->xdg_output);
output->xdg_output = nullptr;
}
} else {
wl_display_roundtrip(client->wl_display);
for (const auto &config : configs) {
client->bars.emplace_back(std::make_unique<Bar>(output.get(), config));
Glib::RefPtr<Gdk::Screen> screen = client->bars.back()->window.get_screen();
client->style_context_->add_provider_for_screen(
screen, client->css_provider_, GTK_STYLE_PROVIDER_PRIORITY_USER);
}
}
} catch (const std::exception &e) {
std::cerr << e.what() << std::endl;
}
}
void waybar::Client::handleDescription(void * /*data*/, struct zxdg_output_v1 * /*zxdg_output_v1*/,
const char * /*description*/) {
// Nothing here
}
std::tuple<const std::string, const std::string> waybar::Client::getConfigs(
const std::string &config, const std::string &style) const {
auto config_file = config.empty() ? getValidPath({
"$XDG_CONFIG_HOME/waybar/config",
"$HOME/.config/waybar/config",
"$HOME/waybar/config",
"/etc/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",
"./resources/style.css",
})
: style;
if (css_file.empty() || config_file.empty()) { if (css_file.empty() || config_file.empty()) {
throw std::runtime_error("Missing required resources files"); throw std::runtime_error("Missing required resources files");
} }
std::cout << "Resources files: " + config_file + ", " + css_file << std::endl; spdlog::info("Resources files: {}, {}", config_file, css_file);
return {config_file, css_file};
} }
void waybar::Client::bindInterfaces() 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 {
css_provider_ = Gtk::CssProvider::create();
style_context_ = Gtk::StyleContext::create();
// Load our css file, wherever that may be hiding
if (!css_provider_->load_from_path(css_file)) {
throw std::runtime_error("Can't open style file");
}
}
void waybar::Client::bindInterfaces() {
registry = wl_display_get_registry(wl_display); registry = wl_display_get_registry(wl_display);
static const struct wl_registry_listener registry_listener = { static const struct wl_registry_listener registry_listener = {
.global = handleGlobal, .global = handleGlobal,
.global_remove = handleGlobalRemove, .global_remove = handleGlobalRemove,
}; };
wl_registry_add_listener(registry, &registry_listener, this); wl_registry_add_listener(registry, &registry_listener, this);
wl_display_roundtrip(wl_display); wl_display_roundtrip(wl_display);
if (!layer_shell || !seat || !xdg_output_manager) { if (layer_shell == nullptr || xdg_output_manager == nullptr) {
throw std::runtime_error("Failed to acquire required resources."); throw std::runtime_error("Failed to acquire required resources.");
} }
wl_display_roundtrip(wl_display);
} }
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;
std::string style; std::string style;
std::string bar_id; std::string bar_id;
auto cli = clara::detail::Help(show_help) std::string log_level;
| clara::detail::Opt(show_version)["-v"]["--version"]("Show version") auto cli = clara::detail::Help(show_help) |
| clara::detail::Opt(config, "config")["-c"]["--config"]("Config path") clara::detail::Opt(show_version)["-v"]["--version"]("Show version") |
| clara::detail::Opt(style, "style")["-s"]["--style"]("Style path") clara::detail::Opt(config, "config")["-c"]["--config"]("Config path") |
| clara::detail::Opt(bar_id, "id")["-b"]["--bar"]("Bar id"); clara::detail::Opt(style, "style")["-s"]["--style"]("Style path") |
clara::detail::Opt(
log_level,
"trace|debug|info|warning|error|critical|off")["-l"]["--log-level"]("Log level") |
clara::detail::Opt(bar_id, "id")["-b"]["--bar"]("Bar id");
auto res = cli.parse(clara::detail::Args(argc, argv)); auto res = cli.parse(clara::detail::Args(argc, argv));
if (!res) { if (!res) {
std::cerr << "Error in command line: " << res.errorMessage() << std::endl; spdlog::error("Error in command line: {}", res.errorMessage());
return 1; return 1;
} }
if (show_help) { if (show_help) {
@ -136,15 +270,29 @@ int waybar::Client::main(int argc, char* argv[])
std::cout << "Waybar v" << VERSION << std::endl; std::cout << "Waybar v" << VERSION << std::endl;
return 0; return 0;
} }
setupConfigs(config, style); if (!log_level.empty()) {
spdlog::set_level(spdlog::level::from_str(log_level));
}
gtk_app = Gtk::Application::create(argc, argv, "fr.arouillard.waybar");
gdk_display = Gdk::Display::get_default();
if (!gdk_display) {
throw std::runtime_error("Can't find display");
}
if (!GDK_IS_WAYLAND_DISPLAY(gdk_display->gobj())) {
throw std::runtime_error("Bar need to run under Wayland");
}
wl_display = gdk_wayland_display_get_wl_display(gdk_display->gobj());
auto [config_file, css_file] = getConfigs(config, style);
setupConfig(config_file);
setupCss(css_file);
bindInterfaces(); bindInterfaces();
gtk_main.run(); gtk_app->hold();
gtk_app->run();
bars.clear(); bars.clear();
zxdg_output_manager_v1_destroy(xdg_output_manager); zxdg_output_manager_v1_destroy(xdg_output_manager);
zwlr_layer_shell_v1_destroy(layer_shell); zwlr_layer_shell_v1_destroy(layer_shell);
zwp_idle_inhibit_manager_v1_destroy(idle_inhibit_manager); zwp_idle_inhibit_manager_v1_destroy(idle_inhibit_manager);
wl_registry_destroy(registry); wl_registry_destroy(registry);
wl_seat_destroy(seat);
wl_display_disconnect(wl_display); wl_display_disconnect(wl_display);
return 0; return 0;
} }

View File

@ -1,21 +1,20 @@
#include "factory.hpp" #include "factory.hpp"
waybar::Factory::Factory(const Bar& bar, const Json::Value& config) waybar::Factory::Factory(const Bar& bar, const Json::Value& config) : bar_(bar), config_(config) {}
: bar_(bar), config_(config)
{}
waybar::IModule* waybar::Factory::makeModule(const std::string &name) const waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
{
try { try {
auto hash_pos = name.find("#"); auto hash_pos = name.find('#');
auto ref = name.substr(0, hash_pos); auto ref = name.substr(0, hash_pos);
auto id = hash_pos != std::string::npos ? name.substr(hash_pos + 1) : ""; auto id = hash_pos != std::string::npos ? name.substr(hash_pos + 1) : "";
#ifndef NO_FILESYSTEM
if (ref == "battery") { if (ref == "battery") {
return new waybar::modules::Battery(id, config_[name]); return new waybar::modules::Battery(id, config_[name]);
} }
#ifdef HAVE_SWAY #endif
#ifdef HAVE_SWAY
if (ref == "sway/mode") { if (ref == "sway/mode") {
return new waybar::modules::sway::Mode(id, bar_, config_[name]); return new waybar::modules::sway::Mode(id, config_[name]);
} }
if (ref == "sway/workspaces") { if (ref == "sway/workspaces") {
return new waybar::modules::sway::Workspaces(id, bar_, config_[name]); return new waybar::modules::sway::Workspaces(id, bar_, config_[name]);
@ -23,7 +22,7 @@ waybar::IModule* waybar::Factory::makeModule(const std::string &name) const
if (ref == "sway/window") { if (ref == "sway/window") {
return new waybar::modules::sway::Window(id, bar_, config_[name]); return new waybar::modules::sway::Window(id, bar_, config_[name]);
} }
#endif #endif
if (ref == "idle_inhibitor") { if (ref == "idle_inhibitor") {
return new waybar::modules::IdleInhibitor(id, bar_, config_[name]); return new waybar::modules::IdleInhibitor(id, bar_, config_[name]);
} }
@ -36,28 +35,36 @@ waybar::IModule* waybar::Factory::makeModule(const std::string &name) const
if (ref == "clock") { if (ref == "clock") {
return new waybar::modules::Clock(id, config_[name]); return new waybar::modules::Clock(id, config_[name]);
} }
#ifdef HAVE_DBUSMENU #if defined(HAVE_DBUSMENU) && !defined(NO_FILESYSTEM)
if (ref == "tray") { if (ref == "tray") {
return new waybar::modules::SNI::Tray(id, config_[name]); return new waybar::modules::SNI::Tray(id, bar_, config_[name]);
} }
#endif #endif
#ifdef HAVE_LIBNL #ifdef HAVE_LIBNL
if (ref == "network") { if (ref == "network") {
return new waybar::modules::Network(id, config_[name]); return new waybar::modules::Network(id, config_[name]);
} }
#endif #endif
#ifdef HAVE_LIBUDEV #ifdef HAVE_LIBUDEV
if (ref == "backlight") { if (ref == "backlight") {
return new waybar::modules::Backlight(id, config_[name]); return new waybar::modules::Backlight(id, config_[name]);
} }
#endif #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]);
} }
#endif #endif
#ifdef HAVE_LIBMPDCLIENT
if (ref == "mpd") {
return new waybar::modules::MPD(id, config_[name]);
}
#endif
if (ref == "temperature") {
return new waybar::modules::Temperature(id, config_[name]);
}
if (ref.compare(0, 7, "custom/") == 0 && ref.size() > 7) { if (ref.compare(0, 7, "custom/") == 0 && ref.size() > 7) {
return new waybar::modules::Custom(ref.substr(7), config_[name]); return new waybar::modules::Custom(ref.substr(7), id, config_[name]);
} }
} catch (const std::exception& e) { } catch (const std::exception& e) {
auto err = fmt::format("Disabling module \"{}\", {}", name, e.what()); auto err = fmt::format("Disabling module \"{}\", {}", name, e.what());

View File

@ -1,30 +1,32 @@
#include "client.hpp"
#include <csignal> #include <csignal>
#include <iostream> #include <spdlog/spdlog.h>
#include "client.hpp"
namespace waybar { int main(int argc, char* argv[]) {
static Client* client;
} // namespace waybar
int main(int argc, char* argv[])
{
try { try {
waybar::Client c(argc, argv); auto client = waybar::Client::inst();
waybar::client = &c; std::signal(SIGUSR1, [](int /*signal*/) {
std::signal(SIGUSR1, [] (int /*signal*/) { for (auto& bar : waybar::Client::inst()->bars) {
for (auto& bar : waybar::client->bars) {
bar->toggle(); bar->toggle();
} }
}); });
return c.main(argc, argv); for (int sig = SIGRTMIN + 1; sig <= SIGRTMAX; ++sig) {
std::signal(sig, [](int sig) {
for (auto& bar : waybar::Client::inst()->bars) {
bar->handleSignal(sig);
}
});
}
auto ret = client->main(argc, argv);
delete client;
return ret;
} catch (const std::exception& e) { } catch (const std::exception& e) {
std::cerr << e.what() << std::endl; spdlog::error("{}", e.what());
return 1; return 1;
} catch (const Glib::Exception& e) { } catch (const Glib::Exception& e) {
std::cerr << e.what().c_str() << std::endl; spdlog::error("{}", static_cast<std::string>(e.what()));
return 1; return 1;
} }
} }

View File

@ -13,7 +13,7 @@
namespace { namespace {
class FileDescriptor { class FileDescriptor {
public: public:
explicit FileDescriptor(int fd) : fd_(fd) {} explicit FileDescriptor(int fd) : fd_(fd) {}
FileDescriptor(const FileDescriptor &other) = delete; FileDescriptor(const FileDescriptor &other) = delete;
FileDescriptor(FileDescriptor &&other) noexcept = delete; FileDescriptor(FileDescriptor &&other) noexcept = delete;
@ -28,7 +28,7 @@ public:
} }
int get() const { return fd_; } int get() const { return fd_; }
private: private:
int fd_; int fd_;
}; };
@ -60,9 +60,7 @@ void check_neq(int rc, int bad_rc, const char *message = "neq, rc was: ") {
} }
} }
void check0(int rc, const char *message = "rc wasn't 0") { void check0(int rc, const char *message = "rc wasn't 0") { check_eq(rc, 0, message); }
check_eq(rc, 0, message);
}
void check_gte(int rc, int gte, const char *message = "rc was: ") { void check_gte(int rc, int gte, const char *message = "rc was: ") {
if (rc < gte) { if (rc < gte) {
@ -75,41 +73,30 @@ void check_nn(const void *ptr, const char *message = "ptr was null") {
throw std::runtime_error(message); throw std::runtime_error(message);
} }
} }
} // namespace } // namespace
waybar::modules::Backlight::BacklightDev::BacklightDev(std::string name, waybar::modules::Backlight::BacklightDev::BacklightDev(std::string name, int actual, int max)
int actual, int max)
: name_(std::move(name)), actual_(actual), max_(max) {} : name_(std::move(name)), actual_(actual), max_(max) {}
std::string_view waybar::modules::Backlight::BacklightDev::name() const { std::string_view waybar::modules::Backlight::BacklightDev::name() const { return name_; }
return name_;
}
int waybar::modules::Backlight::BacklightDev::get_actual() const { int waybar::modules::Backlight::BacklightDev::get_actual() const { return actual_; }
return actual_;
}
void waybar::modules::Backlight::BacklightDev::set_actual(int actual) { void waybar::modules::Backlight::BacklightDev::set_actual(int actual) { actual_ = actual; }
actual_ = actual;
}
int waybar::modules::Backlight::BacklightDev::get_max() const { return max_; } int waybar::modules::Backlight::BacklightDev::get_max() const { return max_; }
void waybar::modules::Backlight::BacklightDev::set_max(int max) { max_ = max; } void waybar::modules::Backlight::BacklightDev::set_max(int max) { max_ = max; }
waybar::modules::Backlight::Backlight(const std::string &name, waybar::modules::Backlight::Backlight(const std::string &id, const Json::Value &config)
const Json::Value &config) : ALabel(config, "backlight", id, "{percent}%", 2),
: ALabel(config, "{percent}%", 2), name_(name), preferred_device_(config["device"].isString() ? config["device"].asString() : "") {
preferred_device_(
config["device"].isString() ? config["device"].asString() : "") {
label_.set_name("backlight");
// Get initial state // Get initial state
{ {
std::unique_ptr<udev, UdevDeleter> udev_check{udev_new()}; std::unique_ptr<udev, UdevDeleter> udev_check{udev_new()};
check_nn(udev_check.get(), "Udev check new failed"); check_nn(udev_check.get(), "Udev check new failed");
enumerate_devices(devices_.begin(), devices_.end(), enumerate_devices(
std::back_inserter(devices_), udev_check.get()); devices_.begin(), devices_.end(), std::back_inserter(devices_), udev_check.get());
if (devices_.empty()) { if (devices_.empty()) {
throw std::runtime_error("No backlight found"); throw std::runtime_error("No backlight found");
} }
@ -123,28 +110,26 @@ waybar::modules::Backlight::Backlight(const std::string &name,
std::unique_ptr<udev_monitor, UdevMonitorDeleter> mon{ std::unique_ptr<udev_monitor, UdevMonitorDeleter> mon{
udev_monitor_new_from_netlink(udev.get(), "udev")}; udev_monitor_new_from_netlink(udev.get(), "udev")};
check_nn(mon.get(), "udev monitor new failed"); check_nn(mon.get(), "udev monitor new failed");
check_gte(udev_monitor_filter_add_match_subsystem_devtype( check_gte(udev_monitor_filter_add_match_subsystem_devtype(mon.get(), "backlight", nullptr),
mon.get(), "backlight", nullptr), 0,
0, "udev failed to add monitor filter: "); "udev failed to add monitor filter: ");
udev_monitor_enable_receiving(mon.get()); udev_monitor_enable_receiving(mon.get());
auto udev_fd = udev_monitor_get_fd(mon.get()); auto udev_fd = udev_monitor_get_fd(mon.get());
auto epoll_fd = FileDescriptor{epoll_create1(0)}; auto epoll_fd = FileDescriptor{epoll_create1(EPOLL_CLOEXEC)};
check_neq(epoll_fd.get(), -1, "epoll init failed: "); check_neq(epoll_fd.get(), -1, "epoll init failed: ");
epoll_event ctl_event; epoll_event ctl_event{};
ctl_event.events = EPOLLIN; ctl_event.events = EPOLLIN;
ctl_event.data.fd = udev_fd; ctl_event.data.fd = udev_fd;
check0( check0(epoll_ctl(epoll_fd.get(), EPOLL_CTL_ADD, ctl_event.data.fd, &ctl_event),
epoll_ctl(epoll_fd.get(), EPOLL_CTL_ADD, ctl_event.data.fd, &ctl_event), "epoll_ctl failed: {}");
"epoll_ctl failed: {}");
epoll_event events[EPOLL_MAX_EVENTS]; epoll_event events[EPOLL_MAX_EVENTS];
while (udev_thread_.isRunning()) { while (udev_thread_.isRunning()) {
const int event_count = const int event_count = epoll_wait(
epoll_wait(epoll_fd.get(), events, EPOLL_MAX_EVENTS, epoll_fd.get(), events, EPOLL_MAX_EVENTS, std::chrono::milliseconds{interval_}.count());
std::chrono::milliseconds{interval_}.count());
if (!udev_thread_.isRunning()) { if (!udev_thread_.isRunning()) {
break; break;
} }
@ -156,17 +141,14 @@ waybar::modules::Backlight::Backlight(const std::string &name,
for (int i = 0; i < event_count; ++i) { for (int i = 0; i < event_count; ++i) {
const auto &event = events[i]; const auto &event = events[i];
check_eq(event.data.fd, udev_fd, "unexpected udev fd"); check_eq(event.data.fd, udev_fd, "unexpected udev fd");
std::unique_ptr<udev_device, UdevDeviceDeleter> dev{ std::unique_ptr<udev_device, UdevDeviceDeleter> dev{udev_monitor_receive_device(mon.get())};
udev_monitor_receive_device(mon.get())};
check_nn(dev.get(), "epoll dev was null"); check_nn(dev.get(), "epoll dev was null");
upsert_device(devices.begin(), devices.end(), upsert_device(devices.begin(), devices.end(), std::back_inserter(devices), dev.get());
std::back_inserter(devices), dev.get());
} }
// Refresh state if timed out // Refresh state if timed out
if (event_count == 0) { if (event_count == 0) {
enumerate_devices(devices.begin(), devices.end(), enumerate_devices(devices.begin(), devices.end(), std::back_inserter(devices), udev.get());
std::back_inserter(devices), udev.get());
} }
{ {
std::scoped_lock<std::mutex> lock(udev_thread_mutex_); std::scoped_lock<std::mutex> lock(udev_thread_mutex_);
@ -186,19 +168,17 @@ auto waybar::modules::Backlight::update() -> void {
devices = devices_; devices = devices_;
} }
const auto best = const auto best = best_device(devices.cbegin(), devices.cend(), preferred_device_);
best_device(devices.cbegin(), devices.cend(), preferred_device_);
if (best != nullptr) { if (best != nullptr) {
if (previous_best_.has_value() && previous_best_.value() == *best && if (previous_best_.has_value() && previous_best_.value() == *best &&
!previous_format_.empty() && previous_format_ == format_) { !previous_format_.empty() && previous_format_ == format_) {
return; return;
} }
const auto percent = const auto percent = best->get_max() == 0 ? 100 : best->get_actual() * 100 / best->get_max();
best->get_max() == 0 ? 100 : best->get_actual() * 100 / best->get_max(); label_.set_markup(fmt::format(
label_.set_markup(fmt::format(format_, format_, fmt::arg("percent", std::to_string(percent)), fmt::arg("icon", getIcon(percent))));
fmt::arg("percent", std::to_string(percent)), getState(percent);
fmt::arg("icon", getIcon(percent))));
} else { } else {
if (!previous_best_.has_value()) { if (!previous_best_.has_value()) {
return; return;
@ -210,33 +190,30 @@ auto waybar::modules::Backlight::update() -> void {
} }
template <class ForwardIt> template <class ForwardIt>
const waybar::modules::Backlight::BacklightDev * const waybar::modules::Backlight::BacklightDev *waybar::modules::Backlight::best_device(
waybar::modules::Backlight::best_device(ForwardIt first, ForwardIt last, ForwardIt first, ForwardIt last, std::string_view preferred_device) {
std::string_view preferred_device) { const auto found = std::find_if(
const auto found = first, last, [preferred_device](const auto &dev) { return dev.name() == preferred_device; });
std::find_if(first, last, [preferred_device](const auto &dev) {
return dev.name() == preferred_device;
});
if (found != last) { if (found != last) {
return &(*found); return &(*found);
} }
const auto max = const auto max = std::max_element(
std::max_element(first, last, [](const auto &l, const auto &r) { first, last, [](const auto &l, const auto &r) { return l.get_max() < r.get_max(); });
return l.get_max() < r.get_max();
});
return max == last ? nullptr : &(*max); return max == last ? nullptr : &(*max);
} }
template <class ForwardIt, class Inserter> template <class ForwardIt, class Inserter>
void waybar::modules::Backlight::upsert_device(ForwardIt first, ForwardIt last, void waybar::modules::Backlight::upsert_device(ForwardIt first, ForwardIt last, Inserter inserter,
Inserter inserter,
udev_device *dev) { udev_device *dev) {
const char *name = udev_device_get_sysname(dev); const char *name = udev_device_get_sysname(dev);
check_nn(name); check_nn(name);
const char *actual = udev_device_get_sysattr_value(dev, "actual_brightness"); const char *actual_brightness_attr =
strcmp(name, "amdgpu_bl0") == 0 ? "brightness" : "actual_brightness";
const char *actual = udev_device_get_sysattr_value(dev, actual_brightness_attr);
check_nn(actual); check_nn(actual);
const int actual_int = std::stoi(actual); const int actual_int = std::stoi(actual);
@ -244,9 +221,8 @@ void waybar::modules::Backlight::upsert_device(ForwardIt first, ForwardIt last,
check_nn(max); check_nn(max);
const int max_int = std::stoi(max); const int max_int = std::stoi(max);
auto found = std::find_if(first, last, [name](const auto &device) { auto found =
return device.name() == name; std::find_if(first, last, [name](const auto &device) { return device.name() == name; });
});
if (found != last) { if (found != last) {
found->set_actual(actual_int); found->set_actual(actual_int);
found->set_max(max_int); found->set_max(max_int);
@ -257,21 +233,16 @@ void waybar::modules::Backlight::upsert_device(ForwardIt first, ForwardIt last,
} }
template <class ForwardIt, class Inserter> template <class ForwardIt, class Inserter>
void waybar::modules::Backlight::enumerate_devices(ForwardIt first, void waybar::modules::Backlight::enumerate_devices(ForwardIt first, ForwardIt last,
ForwardIt last, Inserter inserter, udev *udev) {
Inserter inserter, std::unique_ptr<udev_enumerate, UdevEnumerateDeleter> enumerate{udev_enumerate_new(udev)};
udev *udev) {
std::unique_ptr<udev_enumerate, UdevEnumerateDeleter> enumerate{
udev_enumerate_new(udev)};
udev_enumerate_add_match_subsystem(enumerate.get(), "backlight"); udev_enumerate_add_match_subsystem(enumerate.get(), "backlight");
udev_enumerate_scan_devices(enumerate.get()); udev_enumerate_scan_devices(enumerate.get());
udev_list_entry *enum_devices = udev_list_entry *enum_devices = udev_enumerate_get_list_entry(enumerate.get());
udev_enumerate_get_list_entry(enumerate.get());
udev_list_entry *dev_list_entry; udev_list_entry *dev_list_entry;
udev_list_entry_foreach(dev_list_entry, enum_devices) { udev_list_entry_foreach(dev_list_entry, enum_devices) {
const char *path = udev_list_entry_get_name(dev_list_entry); const char * path = udev_list_entry_get_name(dev_list_entry);
std::unique_ptr<udev_device, UdevDeviceDeleter> dev{ std::unique_ptr<udev_device, UdevDeviceDeleter> dev{udev_device_new_from_syspath(udev, path)};
udev_device_new_from_syspath(udev, path)};
check_nn(dev.get(), "dev new failed"); check_nn(dev.get(), "dev new failed");
upsert_device(first, last, inserter, dev.get()); upsert_device(first, last, inserter, dev.get());
} }

View File

@ -1,12 +1,8 @@
#include "modules/battery.hpp" #include "modules/battery.hpp"
#include <spdlog/spdlog.h>
waybar::modules::Battery::Battery(const std::string& id, const Json::Value& config) waybar::modules::Battery::Battery(const std::string& id, const Json::Value& config)
: ALabel(config, "{capacity}%", 60) : ALabel(config, "battery", id, "{capacity}%", 60) {
{
label_.set_name("battery");
if (!id.empty()) {
label_.get_style_context()->add_class(id);
}
getBatteries(); getBatteries();
fd_ = inotify_init1(IN_CLOEXEC); fd_ = inotify_init1(IN_CLOEXEC);
if (fd_ == -1) { if (fd_ == -1) {
@ -21,23 +17,21 @@ waybar::modules::Battery::Battery(const std::string& id, const Json::Value& conf
worker(); worker();
} }
waybar::modules::Battery::~Battery() waybar::modules::Battery::~Battery() {
{
for (auto wd : wds_) { for (auto wd : wds_) {
inotify_rm_watch(fd_, wd); inotify_rm_watch(fd_, wd);
} }
close(fd_); close(fd_);
} }
void waybar::modules::Battery::worker() void waybar::modules::Battery::worker() {
{
thread_timer_ = [this] { thread_timer_ = [this] {
dp.emit(); dp.emit();
thread_timer_.sleep_for(interval_); thread_timer_.sleep_for(interval_);
}; };
thread_ = [this] { thread_ = [this] {
struct inotify_event event = {0}; struct inotify_event event = {0};
int nbytes = read(fd_, &event, sizeof(event)); int nbytes = read(fd_, &event, sizeof(event));
if (nbytes != sizeof(event) || event.mask & IN_IGNORED) { if (nbytes != sizeof(event) || event.mask & IN_IGNORED) {
thread_.stop(); thread_.stop();
return; return;
@ -48,27 +42,26 @@ void waybar::modules::Battery::worker()
}; };
} }
void waybar::modules::Battery::getBatteries() void waybar::modules::Battery::getBatteries() {
{
try { try {
for (auto const& node : fs::directory_iterator(data_dir_)) { for (auto& node : fs::directory_iterator(data_dir_)) {
if (!fs::is_directory(node)) { if (!fs::is_directory(node)) {
continue; continue;
} }
auto dir_name = node.path().filename(); auto dir_name = node.path().filename();
auto bat_defined = config_["bat"].isString(); auto bat_defined = config_["bat"].isString();
if (((bat_defined && dir_name == config_["bat"].asString()) if (((bat_defined && dir_name == config_["bat"].asString()) || !bat_defined) &&
|| !bat_defined) && fs::exists(node / "capacity") fs::exists(node.path() / "capacity") && fs::exists(node.path() / "uevent") &&
&& fs::exists(node / "uevent") && fs::exists(node / "status")) { fs::exists(node.path() / "status")) {
batteries_.push_back(node); batteries_.push_back(node.path());
} }
auto adap_defined = config_["adapter"].isString(); auto adap_defined = config_["adapter"].isString();
if (((adap_defined && dir_name == config_["adapter"].asString()) if (((adap_defined && dir_name == config_["adapter"].asString()) || !adap_defined) &&
|| !adap_defined) && fs::exists(node / "online")) { fs::exists(node.path() / "online")) {
adapter_ = node; adapter_ = node.path();
} }
} }
} catch (fs::filesystem_error &e) { } catch (fs::filesystem_error& e) {
throw std::runtime_error(e.what()); throw std::runtime_error(e.what());
} }
if (batteries_.empty()) { if (batteries_.empty()) {
@ -79,82 +72,99 @@ void waybar::modules::Battery::getBatteries()
} }
} }
const std::tuple<uint8_t, std::string> waybar::modules::Battery::getInfos() const const std::tuple<uint8_t, float, std::string> waybar::modules::Battery::getInfos() const {
{
try { try {
uint16_t total = 0; uint16_t total = 0;
uint32_t total_power = 0; // μW
uint32_t total_energy = 0; // μWh
uint32_t total_energy_full = 0;
std::string status = "Unknown"; std::string status = "Unknown";
for (auto const& bat : batteries_) { for (auto const& bat : batteries_) {
uint16_t capacity; uint16_t capacity;
uint32_t power_now;
uint32_t energy_full;
uint32_t energy_now;
std::string _status; std::string _status;
std::ifstream(bat / "capacity") >> capacity; std::ifstream(bat / "capacity") >> capacity;
std::ifstream(bat / "status") >> _status; std::ifstream(bat / "status") >> _status;
auto rate_path = fs::exists(bat / "current_now") ? "current_now" : "power_now";
std::ifstream(bat / rate_path) >> power_now;
auto now_path = fs::exists(bat / "charge_now") ? "charge_now" : "energy_now";
std::ifstream(bat / now_path) >> energy_now;
auto full_path = fs::exists(bat / "charge_full") ? "charge_full" : "energy_full";
std::ifstream(bat / full_path) >> energy_full;
if (_status != "Unknown") { if (_status != "Unknown") {
status = _status; status = _status;
} }
total += capacity; total += capacity;
total_power += power_now;
total_energy += energy_now;
total_energy_full += energy_full;
}
if (!adapter_.empty() && status == "Discharging") {
bool online;
std::ifstream(adapter_ / "online") >> online;
if (online) {
status = "Plugged";
}
}
float time_remaining = 0;
if (status == "Discharging" && total_power != 0) {
time_remaining = (float)total_energy / total_power;
} else if (status == "Charging" && total_power != 0) {
time_remaining = -(float)(total_energy_full - total_energy) / total_power;
} }
uint16_t capacity = total / batteries_.size(); uint16_t capacity = total / batteries_.size();
return {capacity, status}; return {capacity, time_remaining, status};
} catch (const std::exception& e) { } catch (const std::exception& e) {
std::cerr << e.what() << std::endl; spdlog::error("Battery: {}", e.what());
return {0, "Unknown"}; return {0, 0, "Unknown"};
} }
} }
const std::string waybar::modules::Battery::getAdapterStatus(uint8_t capacity) const const std::string waybar::modules::Battery::getAdapterStatus(uint8_t capacity) const {
{
if (!adapter_.empty()) { if (!adapter_.empty()) {
bool online; bool online;
std::ifstream(adapter_ / "online") >> online; std::ifstream(adapter_ / "online") >> online;
if (capacity == 100) { if (capacity == 100) {
return "Full"; return "Full";
} }
return online ? "Charging" : "Discharging"; if (online) {
return "Charging";
}
return "Discharging";
} }
return "Unknown"; return "Unknown";
} }
const std::string waybar::modules::Battery::getState(uint8_t capacity) const const std::string waybar::modules::Battery::formatTimeRemaining(float hoursRemaining) {
{ hoursRemaining = std::fabs(hoursRemaining);
// Get current state uint16_t full_hours = static_cast<uint16_t>(hoursRemaining);
std::vector<std::pair<std::string, uint8_t>> states; uint16_t minutes = static_cast<uint16_t>(60 * (hoursRemaining - full_hours));
if (config_["states"].isObject()) { return std::to_string(full_hours) + " h " + std::to_string(minutes) + " min";
for (auto it = config_["states"].begin(); it != config_["states"].end(); ++it) {
if (it->isUInt() && it.key().isString()) {
states.push_back({it.key().asString(), it->asUInt()});
}
}
}
// Sort states
std::sort(states.begin(), states.end(), [](auto &a, auto &b) {
return a.second < b.second;
});
std::string valid_state;
for (auto const& state : states) {
if (capacity <= state.second && valid_state.empty()) {
label_.get_style_context()->add_class(state.first);
valid_state = state.first;
} else {
label_.get_style_context()->remove_class(state.first);
}
}
return valid_state;
} }
auto waybar::modules::Battery::update() -> void auto waybar::modules::Battery::update() -> void {
{ auto [capacity, time_remaining, status] = getInfos();
auto [capacity, status] = getInfos();
if (status == "Unknown") { if (status == "Unknown") {
status = getAdapterStatus(capacity); status = getAdapterStatus(capacity);
} }
if (tooltipEnabled()) { if (tooltipEnabled()) {
label_.set_tooltip_text(status); std::string tooltip_text;
if (time_remaining != 0) {
std::string time_to = std::string("Time to ") + ((time_remaining > 0) ? "empty" : "full");
tooltip_text = time_to + ": " + formatTimeRemaining(time_remaining);
} else {
tooltip_text = status;
}
label_.set_tooltip_text(tooltip_text);
} }
std::transform(status.begin(), status.end(), status.begin(), ::tolower); std::transform(status.begin(), status.end(), status.begin(), ::tolower);
auto format = format_; auto format = format_;
auto state = getState(capacity); auto state = getState(capacity, true);
label_.get_style_context()->remove_class(old_status_); if (!old_status_.empty()) {
label_.get_style_context()->remove_class(old_status_);
}
label_.get_style_context()->add_class(status); label_.get_style_context()->add_class(status);
old_status_ = status; old_status_ = status;
if (!state.empty() && config_["format-" + status + "-" + state].isString()) { if (!state.empty() && config_["format-" + status + "-" + state].isString()) {
@ -168,7 +178,9 @@ auto waybar::modules::Battery::update() -> void
event_box_.hide(); event_box_.hide();
} else { } else {
event_box_.show(); event_box_.show();
label_.set_markup(fmt::format(format, fmt::arg("capacity", capacity), label_.set_markup(fmt::format(format,
fmt::arg("icon", getIcon(capacity)))); fmt::arg("capacity", capacity),
fmt::arg("icon", getIcon(capacity, state)),
fmt::arg("time", formatTimeRemaining(time_remaining))));
} }
} }

View File

@ -1,26 +1,19 @@
#include "modules/clock.hpp" #include "modules/clock.hpp"
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, "{:%H:%M}", 60) : ALabel(config, "clock", id, "{:%H:%M}", 60) {
{
label_.set_name("clock");
if (!id.empty()) {
label_.get_style_context()->add_class(id);
}
thread_ = [this] { thread_ = [this] {
auto now = std::chrono::system_clock::now();
dp.emit(); dp.emit();
auto now = std::chrono::system_clock::now();
auto timeout = std::chrono::floor<std::chrono::seconds>(now + interval_); auto timeout = std::chrono::floor<std::chrono::seconds>(now + interval_);
auto time_s = std::chrono::time_point_cast<std::chrono::seconds>(timeout); auto diff = std::chrono::seconds(timeout.time_since_epoch().count() % interval_.count());
auto sub_m = thread_.sleep_until(timeout - diff);
std::chrono::duration_cast<std::chrono::seconds>(time_s.time_since_epoch()).count() % interval_.count();
thread_.sleep_until(timeout - std::chrono::seconds(sub_m - 1));
}; };
} }
auto waybar::modules::Clock::update() -> void auto waybar::modules::Clock::update() -> void {
{ auto now = std::chrono::system_clock::now();
auto localtime = fmt::localtime(std::time(nullptr)); auto localtime = fmt::localtime(std::chrono::system_clock::to_time_t(now));
auto text = fmt::format(format_, localtime); auto text = fmt::format(format_, localtime);
label_.set_markup(text); label_.set_markup(text);

View File

@ -1,56 +1,49 @@
#include "modules/cpu.hpp" #include "modules/cpu.hpp"
#include <numeric>
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, "{usage}%", 10) : ALabel(config, "cpu", id, "{usage}%", 10) {
{
label_.set_name("cpu");
if (!id.empty()) {
label_.get_style_context()->add_class(id);
}
thread_ = [this] { thread_ = [this] {
dp.emit(); dp.emit();
thread_.sleep_for(interval_); thread_.sleep_for(interval_);
}; };
} }
auto waybar::modules::Cpu::update() -> void auto waybar::modules::Cpu::update() -> void {
{
// TODO: as creating dynamic fmt::arg arrays is buggy we have to calc both // TODO: as creating dynamic fmt::arg arrays is buggy we have to calc both
auto cpu_load = getCpuLoad(); auto cpu_load = getCpuLoad();
auto [cpu_usage, tooltip] = getCpuUsage(); auto [cpu_usage, tooltip] = getCpuUsage();
if (tooltipEnabled()) { if (tooltipEnabled()) {
label_.set_tooltip_text(tooltip); label_.set_tooltip_text(tooltip);
} }
label_.set_markup(fmt::format(format_, label_.set_markup(fmt::format(format_, fmt::arg("load", cpu_load), fmt::arg("usage", cpu_usage)));
fmt::arg("load", cpu_load), fmt::arg("usage", cpu_usage))); getState(cpu_usage);
} }
uint16_t waybar::modules::Cpu::getCpuLoad() uint16_t waybar::modules::Cpu::getCpuLoad() {
{
struct sysinfo info = {0}; struct sysinfo info = {0};
if (sysinfo(&info) == 0) { if (sysinfo(&info) == 0) {
float f_load = 1.f / (1u << SI_LOAD_SHIFT); float f_load = 1.F / (1U << SI_LOAD_SHIFT);
uint16_t load = info.loads[0] * f_load * 100 / get_nprocs(); uint16_t load = info.loads[0] * f_load * 100 / get_nprocs();
return load; return load;
} }
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<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; uint16_t usage = 0;
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];
const float delta_idle = curr_idle - prev_idle; const float delta_idle = curr_idle - prev_idle;
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; usage = tmp;
tooltip = fmt::format("Total: {}%", tmp); tooltip = fmt::format("Total: {}%", tmp);
@ -62,21 +55,21 @@ std::tuple<uint16_t, std::string> waybar::modules::Cpu::getCpuUsage()
return {usage, tooltip}; return {usage, tooltip};
} }
std::vector<std::tuple<size_t, size_t>> waybar::modules::Cpu::parseCpuinfo() std::vector<std::tuple<size_t, size_t>> waybar::modules::Cpu::parseCpuinfo() {
{
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_);
} }
std::vector< std::tuple<size_t, size_t> > cpuinfo; std::vector<std::tuple<size_t, size_t>> cpuinfo;
std::string line; std::string line;
while (getline(info, line)) { while (getline(info, line)) {
if (line.substr(0,3).compare("cpu") != 0) { if (line.substr(0, 3).compare("cpu") != 0) {
break; break;
} }
std::stringstream sline(line.substr(5)); std::stringstream sline(line.substr(5));
std::vector<size_t> times; std::vector<size_t> times;
for (size_t time; sline >> time; times.push_back(time)); for (size_t time = 0; sline >> time; times.push_back(time))
;
size_t idle_time = 0; size_t idle_time = 0;
size_t total_time = 0; size_t total_time = 0;
@ -84,7 +77,7 @@ std::vector<std::tuple<size_t, size_t>> waybar::modules::Cpu::parseCpuinfo()
idle_time = times[3]; idle_time = times[3];
total_time = std::accumulate(times.begin(), times.end(), 0); total_time = std::accumulate(times.begin(), times.end(), 0);
} }
cpuinfo.push_back({idle_time, total_time}); cpuinfo.emplace_back(idle_time, total_time);
} }
return cpuinfo; return cpuinfo;
} }

View File

@ -1,10 +1,9 @@
#include "modules/custom.hpp" #include "modules/custom.hpp"
#include <spdlog/spdlog.h>
waybar::modules::Custom::Custom(const std::string& name, waybar::modules::Custom::Custom(const std::string& name, const std::string& id,
const Json::Value& config) const Json::Value& config)
: ALabel(config, "{}"), name_(name), fp_(nullptr) : ALabel(config, "custom-" + name, id, "{}"), name_(name), fp_(nullptr), pid_(-1) {
{
label_.set_name("custom-" + name_);
if (config_["exec"].isString()) { if (config_["exec"].isString()) {
if (interval_.count() > 0) { if (interval_.count() > 0) {
delayWorker(); delayWorker();
@ -15,71 +14,85 @@ waybar::modules::Custom::Custom(const std::string& name,
dp.emit(); dp.emit();
} }
waybar::modules::Custom::~Custom() waybar::modules::Custom::~Custom() {
{ if (pid_ != -1) {
if (fp_) { kill(-pid_, 9);
pclose(fp_); pid_ = -1;
fp_ = nullptr;
} }
} }
void waybar::modules::Custom::delayWorker() void waybar::modules::Custom::delayWorker() {
{
thread_ = [this] { thread_ = [this] {
bool can_update = true; bool can_update = true;
if (config_["exec-if"].isString()) { if (config_["exec-if"].isString()) {
auto res = waybar::util::command::exec(config_["exec-if"].asString()); auto res = util::command::exec(config_["exec-if"].asString());
if (res.exit_code != 0) { if (res.exit_code != 0) {
can_update = false; can_update = false;
event_box_.hide(); event_box_.hide();
} }
} }
if (can_update) { if (can_update) {
output_ = waybar::util::command::exec(config_["exec"].asString()); output_ = util::command::exec(config_["exec"].asString());
dp.emit(); dp.emit();
} }
thread_.sleep_for(interval_); thread_.sleep_for(interval_);
}; };
} }
void waybar::modules::Custom::continuousWorker() void waybar::modules::Custom::continuousWorker() {
{
auto cmd = config_["exec"].asString(); auto cmd = config_["exec"].asString();
fp_ = popen(cmd.c_str(), "r"); pid_ = -1;
fp_ = util::command::open(cmd, pid_);
if (!fp_) { if (!fp_) {
throw std::runtime_error("Unable to open " + cmd); throw std::runtime_error("Unable to open " + cmd);
} }
thread_ = [this] { thread_ = [&] {
char* buff = nullptr; char* buff = nullptr;
size_t len = 0; size_t len = 0;
if (getline(&buff, &len, fp_) == -1) { if (getline(&buff, &len, fp_) == -1) {
int exit_code = 1; int exit_code = 1;
if (fp_) { if (fp_) {
exit_code = WEXITSTATUS(pclose(fp_)); exit_code = WEXITSTATUS(util::command::close(fp_, pid_));
fp_ = nullptr; fp_ = nullptr;
} }
thread_.stop(); thread_.stop();
if (exit_code != 0) { if (exit_code != 0) {
output_ = { exit_code, "" }; output_ = {exit_code, ""};
dp.emit(); dp.emit();
std::cerr << name_ + " just stopped unexpectedly, is it endless?" << std::endl; spdlog::error("{} stopped unexpectedly, is it endless?", name_);
} }
return; return;
} }
std::string output = buff; std::string output = buff;
// Remove last newline // Remove last newline
if (!output.empty() && output[output.length()-1] == '\n') { if (!output.empty() && output[output.length() - 1] == '\n') {
output.erase(output.length()-1); output.erase(output.length() - 1);
} }
output_ = { 0, output }; output_ = {0, output};
dp.emit(); dp.emit();
}; };
} }
auto waybar::modules::Custom::update() -> void void waybar::modules::Custom::refresh(int sig) {
{ if (sig == SIGRTMIN + config_["signal"].asInt()) {
thread_.wake_up();
}
}
bool waybar::modules::Custom::handleScroll(GdkEventScroll* e) {
auto ret = ALabel::handleScroll(e);
thread_.wake_up();
return ret;
}
bool waybar::modules::Custom::handleToggle(GdkEventButton* const& e) {
auto ret = ALabel::handleToggle(e);
thread_.wake_up();
return ret;
}
auto waybar::modules::Custom::update() -> void {
// Hide label if output is empty // Hide label if output is empty
if (config_["exec"].isString() && (output_.out.empty() || output_.exit_code != 0)) { if (config_["exec"].isString() && (output_.out.empty() || output_.exit_code != 0)) {
event_box_.hide(); event_box_.hide();
@ -89,39 +102,38 @@ auto waybar::modules::Custom::update() -> void
} else { } else {
parseOutputRaw(); parseOutputRaw();
} }
auto str = fmt::format(format_,
auto str = fmt::format(format_, text_, text_,
fmt::arg("alt", alt_), fmt::arg("alt", alt_),
fmt::arg("icon", getIcon(percentage_)), fmt::arg("icon", getIcon(percentage_, alt_)),
fmt::arg("percentage", percentage_)); fmt::arg("percentage", percentage_));
label_.set_markup(str); if (str.empty()) {
if (tooltipEnabled()) { event_box_.hide();
if (text_ == tooltip_) {
label_.set_tooltip_text(str);
} else {
label_.set_tooltip_text(tooltip_);
}
}
if (class_ != "") {
if (prevclass_ != "") {
label_.get_style_context()->remove_class(prevclass_);
}
label_.get_style_context()->add_class(class_);
prevclass_ = class_;
} else { } else {
label_.get_style_context()->remove_class(prevclass_); label_.set_markup(str);
prevclass_ = ""; if (tooltipEnabled()) {
if (text_ == tooltip_) {
label_.set_tooltip_text(str);
} else {
label_.set_tooltip_text(tooltip_);
}
}
auto classes = label_.get_style_context()->list_classes();
for (auto const& c : classes) {
label_.get_style_context()->remove_class(c);
}
for (auto const& c : class_) {
label_.get_style_context()->add_class(c);
}
event_box_.show();
} }
event_box_.show();
} }
} }
void waybar::modules::Custom::parseOutputRaw() void waybar::modules::Custom::parseOutputRaw() {
{
std::istringstream output(output_.out); std::istringstream output(output_.out);
std::string line; std::string line;
int i = 0; int i = 0;
while (getline(output, line)) { while (getline(output, line)) {
if (i == 0) { if (i == 0) {
if (config_["escape"].isBool() && config_["escape"].asBool()) { if (config_["escape"].isBool() && config_["escape"].asBool()) {
@ -130,11 +142,11 @@ void waybar::modules::Custom::parseOutputRaw()
text_ = line; text_ = line;
} }
tooltip_ = line; tooltip_ = line;
class_ = ""; class_.clear();
} else if (i == 1) { } else if (i == 1) {
tooltip_ = line; tooltip_ = line;
} else if (i == 2) { } else if (i == 2) {
class_ = line; class_.push_back(line);
} else { } else {
break; break;
} }
@ -142,10 +154,10 @@ void waybar::modules::Custom::parseOutputRaw()
} }
} }
void waybar::modules::Custom::parseOutputJson() void waybar::modules::Custom::parseOutputJson() {
{
std::istringstream output(output_.out); std::istringstream output(output_.out);
std::string line; std::string line;
class_.clear();
while (getline(output, line)) { while (getline(output, line)) {
auto parsed = parser_.parse(line); auto parsed = parser_.parse(line);
if (config_["escape"].isBool() && config_["escape"].asBool()) { if (config_["escape"].isBool() && config_["escape"].asBool()) {
@ -159,7 +171,13 @@ void waybar::modules::Custom::parseOutputJson()
alt_ = parsed["alt"].asString(); alt_ = parsed["alt"].asString();
} }
tooltip_ = parsed["tooltip"].asString(); tooltip_ = parsed["tooltip"].asString();
class_ = parsed["class"].asString(); if (parsed["class"].isString()) {
class_.push_back(parsed["class"].asString());
} else if (parsed["class"].isArray()) {
for (auto const& c : parsed["class"]) {
class_.push_back(c.asString());
}
}
if (!parsed["percentage"].asString().empty() && parsed["percentage"].isUInt()) { if (!parsed["percentage"].asString().empty() && parsed["percentage"].isUInt()) {
percentage_ = parsed["percentage"].asUInt(); percentage_ = parsed["percentage"].asUInt();
} else { } else {

View File

@ -1,50 +1,53 @@
#include "modules/idle_inhibitor.hpp" #include "modules/idle_inhibitor.hpp"
#include "util/command.hpp"
waybar::modules::IdleInhibitor::IdleInhibitor(const std::string& id, const Bar& bar,
waybar::modules::IdleInhibitor::IdleInhibitor(const std::string& id, const Bar& bar, const Json::Value& config) const Json::Value& config)
: ALabel(config, "{status}"), bar_(bar), status_("deactivated"), idle_inhibitor_(nullptr) : ALabel(config, "idle_inhibitor", id, "{status}"),
{ bar_(bar),
label_.set_name("idle_inhibitor"); status_("deactivated"),
if (!id.empty()) { idle_inhibitor_(nullptr),
label_.get_style_context()->add_class(id); pid_(-1) {
}
event_box_.add_events(Gdk::BUTTON_PRESS_MASK); event_box_.add_events(Gdk::BUTTON_PRESS_MASK);
event_box_.signal_button_press_event().connect( event_box_.signal_button_press_event().connect(
sigc::mem_fun(*this, &IdleInhibitor::onClick)); sigc::mem_fun(*this, &IdleInhibitor::handleToggle));
dp.emit(); dp.emit();
} }
waybar::modules::IdleInhibitor::~IdleInhibitor() waybar::modules::IdleInhibitor::~IdleInhibitor() {
{ if (idle_inhibitor_ != nullptr) {
if(idle_inhibitor_) {
zwp_idle_inhibitor_v1_destroy(idle_inhibitor_); zwp_idle_inhibitor_v1_destroy(idle_inhibitor_);
idle_inhibitor_ = nullptr; idle_inhibitor_ = nullptr;
} }
if (pid_ != -1) {
kill(-pid_, 9);
pid_ = -1;
}
} }
auto waybar::modules::IdleInhibitor::update() -> void auto waybar::modules::IdleInhibitor::update() -> void {
{
label_.set_markup( label_.set_markup(
fmt::format(format_, fmt::arg("status", status_), fmt::format(format_, fmt::arg("status", status_), fmt::arg("icon", getIcon(0, status_))));
fmt::arg("icon", getIcon(0, status_)))); label_.get_style_context()->add_class(status_);
if(tooltipEnabled()) { if (tooltipEnabled()) {
label_.set_tooltip_text(status_); label_.set_tooltip_text(status_);
} }
} }
bool waybar::modules::IdleInhibitor::onClick(GdkEventButton* const& e) bool waybar::modules::IdleInhibitor::handleToggle(GdkEventButton* const& e) {
{ if (e->button == 1) {
if(e->button == 1) { label_.get_style_context()->remove_class(status_);
if (idle_inhibitor_) { if (idle_inhibitor_ != nullptr) {
zwp_idle_inhibitor_v1_destroy(idle_inhibitor_); zwp_idle_inhibitor_v1_destroy(idle_inhibitor_);
idle_inhibitor_ = nullptr; idle_inhibitor_ = nullptr;
status_ = "deactivated"; status_ = "deactivated";
} else { } else {
idle_inhibitor_ = zwp_idle_inhibit_manager_v1_create_inhibitor( idle_inhibitor_ = zwp_idle_inhibit_manager_v1_create_inhibitor(
bar_.client.idle_inhibit_manager, bar_.surface); waybar::Client::inst()->idle_inhibit_manager, bar_.surface);
status_ = "activated"; status_ = "activated";
} }
click_param = status_;
} }
dp.emit(); ALabel::handleToggle(e);
return true; return true;
} }

View File

@ -1,25 +1,28 @@
#include "modules/memory.hpp" #include "modules/memory.hpp"
waybar::modules::Memory::Memory(const std::string& id, const Json::Value& config) waybar::modules::Memory::Memory(const std::string& id, const Json::Value& config)
: ALabel(config, "{}%", 30) : ALabel(config, "memory", id, "{}%", 30) {
{
label_.set_name("memory");
if (!id.empty()) {
label_.get_style_context()->add_class(id);
}
thread_ = [this] { thread_ = [this] {
dp.emit(); dp.emit();
thread_.sleep_for(interval_); thread_.sleep_for(interval_);
}; };
} }
auto waybar::modules::Memory::update() -> void auto waybar::modules::Memory::update() -> void {
{
parseMeminfo(); parseMeminfo();
if (memtotal_ > 0 && memfree_ >= 0) { if (memtotal_ > 0 && memfree_ >= 0) {
int used_ram_percentage = 100 * (memtotal_ - memfree_) / memtotal_; auto total_ram_gigabytes = memtotal_ / std::pow(1024, 2);
label_.set_markup(fmt::format(format_, used_ram_percentage)); int used_ram_percentage = 100 * (memtotal_ - memfree_) / memtotal_;
auto used_ram_gigabytes = (memtotal_ - memfree_) / std::pow(1024, 2); auto used_ram_gigabytes = (memtotal_ - memfree_) / std::pow(1024, 2);
auto available_ram_gigabytes = memfree_ / std::pow(1024, 2);
getState(used_ram_percentage);
label_.set_markup(fmt::format(format_,
used_ram_percentage,
fmt::arg("total", total_ram_gigabytes),
fmt::arg("percentage", used_ram_percentage),
fmt::arg("used", used_ram_gigabytes),
fmt::arg("avail", available_ram_gigabytes)));
if (tooltipEnabled()) { if (tooltipEnabled()) {
label_.set_tooltip_text(fmt::format("{:.{}f}Gb used", used_ram_gigabytes, 1)); label_.set_tooltip_text(fmt::format("{:.{}f}Gb used", used_ram_gigabytes, 1));
} }
@ -29,21 +32,20 @@ auto waybar::modules::Memory::update() -> void
} }
} }
void waybar::modules::Memory::parseMeminfo() void waybar::modules::Memory::parseMeminfo() {
{ int64_t memfree = -1, membuffer = -1, memcache = -1, memavail = -1;
long memfree = -1, membuffer = -1, memcache = -1, memavail = -1;
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_);
} }
std::string line; std::string line;
while (getline(info, line)) { while (getline(info, line)) {
auto posDelim = line.find(":"); auto posDelim = line.find(':');
if (posDelim == std::string::npos) { if (posDelim == std::string::npos) {
continue; continue;
} }
std::string name = line.substr(0, posDelim); std::string name = line.substr(0, posDelim);
long value = std::stol(line.substr(posDelim + 1)); int64_t value = std::stol(line.substr(posDelim + 1));
if (name.compare("MemTotal") == 0) { if (name.compare("MemTotal") == 0) {
memtotal_ = value; memtotal_ = value;
@ -56,8 +58,7 @@ void waybar::modules::Memory::parseMeminfo()
} else if (name.compare("Cached") == 0) { } else if (name.compare("Cached") == 0) {
memcache = value; memcache = value;
} }
if (memtotal_ > 0 && if (memtotal_ > 0 && (memavail >= 0 || (memfree > -1 && membuffer > -1 && memcache > -1))) {
(memavail >= 0 || (memfree > -1 && membuffer > -1 && memcache > -1))) {
break; break;
} }
} }

339
src/modules/mpd.cpp Normal file
View File

@ -0,0 +1,339 @@
#include "modules/mpd.hpp"
#include <fmt/chrono.h>
#include <spdlog/spdlog.h>
waybar::modules::MPD::MPD(const std::string& id, const Json::Value& config)
: ALabel(config, "mpd", id, "{album} - {artist} - {title}", 5),
module_name_(id.empty() ? "mpd" : "mpd#" + id),
server_(nullptr),
port_(config_["port"].isUInt() ? config["port"].asUInt() : 0),
timeout_(config_["timeout"].isUInt() ? config_["timeout"].asUInt() * 1'000 : 30'000),
connection_(nullptr, &mpd_connection_free),
alternate_connection_(nullptr, &mpd_connection_free),
status_(nullptr, &mpd_status_free),
song_(nullptr, &mpd_song_free) {
if (!config_["port"].isNull() && !config_["port"].isUInt()) {
spdlog::warn("{}: `port` configuration should be an unsigned int", module_name_);
}
if (!config_["timeout"].isNull() && !config_["timeout"].isUInt()) {
spdlog::warn("{}: `timeout` configuration should be an unsigned int", module_name_);
}
if (!config["server"].isNull()) {
if (!config_["server"].isString()) {
spdlog::warn("{}:`server` configuration should be a string", module_name_);
}
server_ = config["server"].asCString();
}
event_listener().detach();
event_box_.add_events(Gdk::BUTTON_PRESS_MASK);
event_box_.signal_button_press_event().connect(sigc::mem_fun(*this, &MPD::handlePlayPause));
}
auto waybar::modules::MPD::update() -> void {
std::lock_guard guard(connection_lock_);
tryConnect();
if (connection_ != nullptr) {
try {
bool wasPlaying = playing();
fetchState();
if (!wasPlaying && playing()) {
periodic_updater().detach();
}
} catch (const std::exception& e) {
spdlog::error("{}: {}", module_name_, e.what());
state_ = MPD_STATE_UNKNOWN;
}
}
setLabel();
}
std::thread waybar::modules::MPD::event_listener() {
return std::thread([this] {
while (true) {
try {
if (connection_ == nullptr) {
// Retry periodically if no connection
dp.emit();
std::this_thread::sleep_for(interval_);
} else {
waitForEvent();
dp.emit();
}
} catch (const std::exception& e) {
spdlog::warn("{}: {}", module_name_, e.what());
}
}
});
}
std::thread waybar::modules::MPD::periodic_updater() {
return std::thread([this] {
while (connection_ != nullptr && playing()) {
dp.emit();
std::this_thread::sleep_for(std::chrono::seconds(1));
}
});
}
std::string waybar::modules::MPD::getTag(mpd_tag_type type, unsigned idx) {
std::string result =
config_["unknown-tag"].isString() ? config_["unknown-tag"].asString() : "N/A";
const char* tag = mpd_song_get_tag(song_.get(), type, idx);
// mpd_song_get_tag can return NULL, so make sure it's valid before setting
if (tag) result = tag;
return result;
}
void waybar::modules::MPD::setLabel() {
if (connection_ == nullptr) {
label_.get_style_context()->add_class("disconnected");
label_.get_style_context()->remove_class("stopped");
label_.get_style_context()->remove_class("playing");
label_.get_style_context()->remove_class("paused");
auto format = config_["format-disconnected"].isString()
? config_["format-disconnected"].asString()
: "disconnected";
label_.set_markup(format);
if (tooltipEnabled()) {
std::string tooltip_format;
tooltip_format = config_["tooltip-format-disconnected"].isString()
? config_["tooltip-format-disconnected"].asString()
: "MPD (disconnected)";
// Nothing to format
label_.set_tooltip_text(tooltip_format);
}
return;
} else {
label_.get_style_context()->remove_class("disconnected");
}
auto format = format_;
std::string artist, album_artist, album, title, date;
std::chrono::seconds elapsedTime, totalTime;
std::string stateIcon = "";
if (stopped()) {
format =
config_["format-stopped"].isString() ? config_["format-stopped"].asString() : "stopped";
label_.get_style_context()->add_class("stopped");
label_.get_style_context()->remove_class("playing");
label_.get_style_context()->remove_class("paused");
} else {
label_.get_style_context()->remove_class("stopped");
if (playing()) {
label_.get_style_context()->add_class("playing");
label_.get_style_context()->remove_class("paused");
} else {
label_.get_style_context()->add_class("paused");
label_.get_style_context()->remove_class("playing");
}
stateIcon = getStateIcon();
artist = getTag(MPD_TAG_ARTIST);
album_artist = getTag(MPD_TAG_ALBUM_ARTIST);
album = getTag(MPD_TAG_ALBUM);
title = getTag(MPD_TAG_TITLE);
date = getTag(MPD_TAG_DATE);
elapsedTime = std::chrono::seconds(mpd_status_get_elapsed_time(status_.get()));
totalTime = std::chrono::seconds(mpd_status_get_total_time(status_.get()));
}
bool consumeActivated = mpd_status_get_consume(status_.get());
std::string consumeIcon = getOptionIcon("consume", consumeActivated);
bool randomActivated = mpd_status_get_random(status_.get());
std::string randomIcon = getOptionIcon("random", randomActivated);
bool repeatActivated = mpd_status_get_repeat(status_.get());
std::string repeatIcon = getOptionIcon("repeat", repeatActivated);
bool singleActivated = mpd_status_get_single(status_.get());
std::string singleIcon = getOptionIcon("single", singleActivated);
// TODO: format can fail
label_.set_markup(
fmt::format(format,
fmt::arg("artist", Glib::Markup::escape_text(artist).raw()),
fmt::arg("albumArtist", Glib::Markup::escape_text(album_artist).raw()),
fmt::arg("album", Glib::Markup::escape_text(album).raw()),
fmt::arg("title", Glib::Markup::escape_text(title).raw()),
fmt::arg("date", Glib::Markup::escape_text(date).raw()),
fmt::arg("elapsedTime", elapsedTime),
fmt::arg("totalTime", totalTime),
fmt::arg("stateIcon", stateIcon),
fmt::arg("consumeIcon", consumeIcon),
fmt::arg("randomIcon", randomIcon),
fmt::arg("repeatIcon", repeatIcon),
fmt::arg("singleIcon", singleIcon)));
if (tooltipEnabled()) {
std::string tooltip_format;
tooltip_format = config_["tooltip-format"].isString() ? config_["tooltip-format"].asString()
: "MPD (connected)";
auto tooltip_text = fmt::format(tooltip_format,
fmt::arg("artist", artist),
fmt::arg("albumArtist", album_artist),
fmt::arg("album", album),
fmt::arg("title", title),
fmt::arg("date", date),
fmt::arg("stateIcon", stateIcon),
fmt::arg("consumeIcon", consumeIcon),
fmt::arg("randomIcon", randomIcon),
fmt::arg("repeatIcon", repeatIcon),
fmt::arg("singleIcon", singleIcon));
label_.set_tooltip_text(tooltip_text);
}
}
std::string waybar::modules::MPD::getStateIcon() {
if (!config_["state-icons"].isObject()) {
return "";
}
if (connection_ == nullptr) {
spdlog::warn("{}: Trying to fetch state icon while disconnected", module_name_);
return "";
}
if (stopped()) {
spdlog::warn("{}: Trying to fetch state icon while stopped", module_name_);
return "";
}
if (playing()) {
return config_["state-icons"]["playing"].asString();
} else {
return config_["state-icons"]["paused"].asString();
}
}
std::string waybar::modules::MPD::getOptionIcon(std::string optionName, bool activated) {
if (!config_[optionName + "-icons"].isObject()) {
return "";
}
if (connection_ == nullptr) {
spdlog::warn("{}: Trying to fetch option icon while disconnected", module_name_);
return "";
}
if (activated) {
return config_[optionName + "-icons"]["on"].asString();
} else {
return config_[optionName + "-icons"]["off"].asString();
}
}
void waybar::modules::MPD::tryConnect() {
if (connection_ != nullptr) {
return;
}
connection_ =
unique_connection(mpd_connection_new(server_, port_, timeout_), &mpd_connection_free);
alternate_connection_ =
unique_connection(mpd_connection_new(server_, port_, timeout_), &mpd_connection_free);
if (connection_ == nullptr || alternate_connection_ == nullptr) {
spdlog::error("{}: Failed to connect to MPD", module_name_);
connection_.reset();
alternate_connection_.reset();
return;
}
try {
checkErrors(connection_.get());
spdlog::info("{}: Connected to MPD", module_name_);
} catch (std::runtime_error& e) {
spdlog::error("{}: Failed to connect to MPD: {}", module_name_, e.what());
connection_.reset();
alternate_connection_.reset();
}
}
void waybar::modules::MPD::checkErrors(mpd_connection* conn) {
switch (mpd_connection_get_error(conn)) {
case MPD_ERROR_SUCCESS:
mpd_connection_clear_error(conn);
return;
case MPD_ERROR_TIMEOUT:
case MPD_ERROR_CLOSED:
mpd_connection_clear_error(conn);
connection_.reset();
alternate_connection_.reset();
state_ = MPD_STATE_UNKNOWN;
throw std::runtime_error("Connection to MPD closed");
default:
auto error_message = mpd_connection_get_error_message(conn);
mpd_connection_clear_error(conn);
throw std::runtime_error(std::string(error_message));
}
}
void waybar::modules::MPD::fetchState() {
auto conn = connection_.get();
status_ = unique_status(mpd_run_status(conn), &mpd_status_free);
checkErrors(conn);
state_ = mpd_status_get_state(status_.get());
checkErrors(conn);
song_ = unique_song(mpd_run_current_song(conn), &mpd_song_free);
checkErrors(conn);
}
void waybar::modules::MPD::waitForEvent() {
auto conn = alternate_connection_.get();
// Wait for a player (play/pause), option (random, shuffle, etc.), or playlist
// change
if (!mpd_send_idle_mask(
conn, static_cast<mpd_idle>(MPD_IDLE_PLAYER | MPD_IDLE_OPTIONS | MPD_IDLE_PLAYLIST))) {
checkErrors(conn);
return;
}
// alternate_idle_ = true;
// See issue #277:
// https://github.com/Alexays/Waybar/issues/277
mpd_recv_idle(conn, /* disable_timeout = */ false);
checkErrors(conn);
mpd_response_finish(conn);
checkErrors(conn);
}
bool waybar::modules::MPD::handlePlayPause(GdkEventButton* const& e) {
if (e->type == GDK_2BUTTON_PRESS || e->type == GDK_3BUTTON_PRESS || connection_ == nullptr) {
return false;
}
if (e->button == 1) {
std::lock_guard guard(connection_lock_);
if (stopped()) {
mpd_run_play(connection_.get());
} else {
mpd_run_toggle_pause(connection_.get());
}
} else if (e->button == 3) {
std::lock_guard guard(connection_lock_);
mpd_run_stop(connection_.get());
}
return true;
}
bool waybar::modules::MPD::stopped() {
return connection_ == nullptr || state_ == MPD_STATE_UNKNOWN || state_ == MPD_STATE_STOP;
}
bool waybar::modules::MPD::playing() { return connection_ != nullptr && state_ == MPD_STATE_PLAY; }

View File

@ -1,36 +1,116 @@
#include <sys/eventfd.h>
#include "modules/network.hpp" #include "modules/network.hpp"
#include <spdlog/spdlog.h>
#include <sys/eventfd.h>
#include <fstream>
waybar::modules::Network::Network(const std::string& id, const Json::Value& config) namespace {
: ALabel(config, "{ifname}", 60), family_(AF_INET), efd_(-1), ev_fd_(-1),
cidr_(-1), signal_strength_dbm_(0), signal_strength_(0) constexpr const char *NETSTAT_FILE =
{ "/proc/net/netstat"; // std::ifstream does not take std::string_view as param
label_.set_name("network"); constexpr std::string_view BANDWIDTH_CATEGORY = "IpExt";
if (!id.empty()) { constexpr std::string_view BANDWIDTH_DOWN_TOTAL_KEY = "InOctets";
label_.get_style_context()->add_class(id); constexpr std::string_view BANDWIDTH_UP_TOTAL_KEY = "OutOctets";
std::ifstream netstat(NETSTAT_FILE);
std::optional<unsigned long long> read_netstat(std::string_view category, std::string_view key) {
if (!netstat) {
spdlog::warn("Failed to open netstat file {}", NETSTAT_FILE);
return {};
} }
createInfoSocket(); netstat.seekg(std::ios_base::beg);
createEventSocket();
if (config_["interface"].isString()) { // finding corresponding line (category)
ifid_ = if_nametoindex(config_["interface"].asCString()); // looks into the file for the first line starting by the 'category' string
ifname_ = config_["interface"].asString(); auto starts_with = [](const std::string &str, std::string_view start) {
if (ifid_ <= 0) { return start == std::string_view{str.data(), std::min(str.size(), start.size())};
throw std::runtime_error("Can't found network interface"); };
std::string read;
while (std::getline(netstat, read) && !starts_with(read, category))
;
if (!starts_with(read, category)) {
spdlog::warn("Category '{}' not found in netstat file {}", category, NETSTAT_FILE);
return {};
}
// finding corresponding column (key)
// looks into the fetched line for the first word (space separated) equal to 'key'
int index = 0;
auto r_it = read.begin();
auto k_it = key.begin();
while (k_it != key.end() && r_it != read.end()) {
if (*r_it != *k_it) {
r_it = std::find(r_it, read.end(), ' ');
if (r_it != read.end()) {
++r_it;
}
k_it = key.begin();
++index;
} else {
++r_it;
++k_it;
} }
}
if (r_it == read.end() && k_it != key.end()) {
spdlog::warn(
"Key '{}' not found in category '{}' of netstat file {}", key, category, NETSTAT_FILE);
return {};
}
// finally accessing value
// accesses the line right under the fetched one
std::getline(netstat, read);
assert(starts_with(read, category));
std::istringstream iss(read);
while (index--) {
std::getline(iss, read, ' ');
}
unsigned long long value;
iss >> value;
return value;
}
} // namespace
waybar::modules::Network::Network(const std::string &id, const Json::Value &config)
: ALabel(config, "network", id, "{ifname}", 60),
ifid_(-1),
family_(config["family"] == "ipv6" ? AF_INET6 : AF_INET),
efd_(-1),
ev_fd_(-1),
cidr_(-1),
signal_strength_dbm_(0),
signal_strength_(0),
frequency_(0) {
auto down_octets = read_netstat(BANDWIDTH_CATEGORY, BANDWIDTH_DOWN_TOTAL_KEY);
auto up_octets = read_netstat(BANDWIDTH_CATEGORY, BANDWIDTH_UP_TOTAL_KEY);
if (down_octets) {
bandwidth_down_total_ = *down_octets;
} else { } else {
ifid_ = getExternalInterface(); bandwidth_down_total_ = 0;
if (ifid_ > 0) { }
char ifname[IF_NAMESIZE];
if_indextoname(ifid_, ifname); if (up_octets) {
ifname_ = ifname; bandwidth_up_total_ = *up_octets;
} } else {
bandwidth_up_total_ = 0;
}
createEventSocket();
createInfoSocket();
auto default_iface = getPreferredIface();
if (default_iface != -1) {
ifid_ = default_iface;
char ifname[IF_NAMESIZE];
if_indextoname(default_iface, ifname);
ifname_ = ifname;
getInterfaceAddress();
} }
dp.emit(); dp.emit();
worker(); worker();
} }
waybar::modules::Network::~Network() waybar::modules::Network::~Network() {
{
if (ev_fd_ > -1) { if (ev_fd_ > -1) {
eventfd_write(ev_fd_, 1); eventfd_write(ev_fd_, 1);
std::this_thread::sleep_for(std::chrono::milliseconds(150)); std::this_thread::sleep_for(std::chrono::milliseconds(150));
@ -39,40 +119,45 @@ waybar::modules::Network::~Network()
if (efd_ > -1) { if (efd_ > -1) {
close(efd_); close(efd_);
} }
if (info_sock_ != nullptr) { if (ev_sock_ != nullptr) {
nl_socket_drop_membership(info_sock_, RTMGRP_LINK); nl_socket_drop_membership(ev_sock_, RTNLGRP_LINK);
nl_socket_drop_membership(info_sock_, RTMGRP_IPV4_IFADDR); if (family_ == AF_INET) {
nl_close(info_sock_); nl_socket_drop_membership(ev_sock_, RTNLGRP_IPV4_IFADDR);
nl_socket_free(info_sock_); } else {
nl_socket_drop_membership(ev_sock_, RTNLGRP_IPV6_IFADDR);
}
nl_close(ev_sock_);
nl_socket_free(ev_sock_);
} }
if (sk_ != nullptr) { if (sock_ != nullptr) {
nl_close(sk_); nl_close(sock_);
nl_socket_free(sk_); nl_socket_free(sock_);
} }
} }
void waybar::modules::Network::createInfoSocket() void waybar::modules::Network::createEventSocket() {
{ ev_sock_ = nl_socket_alloc();
info_sock_ = nl_socket_alloc(); nl_socket_disable_seq_check(ev_sock_);
if (nl_connect(info_sock_, NETLINK_ROUTE) != 0) { nl_socket_modify_cb(ev_sock_, NL_CB_VALID, NL_CB_CUSTOM, handleEvents, this);
throw std::runtime_error("Can't connect network socket"); auto groups = RTMGRP_LINK | (family_ == AF_INET ? RTMGRP_IPV4_IFADDR : RTMGRP_IPV6_IFADDR);
} nl_join_groups(ev_sock_, groups); // Deprecated
if (nl_socket_add_membership(info_sock_, RTMGRP_LINK) != 0) { if (nl_connect(ev_sock_, NETLINK_ROUTE) != 0) {
throw std::runtime_error("Can't add membership"); throw std::runtime_error("Can't connect network socket");
} }
if (nl_socket_add_membership(info_sock_, RTMGRP_IPV4_IFADDR) != 0) { nl_socket_add_membership(ev_sock_, RTNLGRP_LINK);
throw std::runtime_error("Can't add membership"); if (family_ == AF_INET) {
nl_socket_add_membership(ev_sock_, RTNLGRP_IPV4_IFADDR);
} else {
nl_socket_add_membership(ev_sock_, RTNLGRP_IPV6_IFADDR);
} }
nl_socket_disable_seq_check(info_sock_); efd_ = epoll_create1(EPOLL_CLOEXEC);
nl_socket_set_nonblocking(info_sock_);
nl_socket_modify_cb(info_sock_, NL_CB_VALID, NL_CB_CUSTOM, handleEvents, this);
efd_ = epoll_create1(0);
if (efd_ < 0) { if (efd_ < 0) {
throw std::runtime_error("Can't create epoll"); throw std::runtime_error("Can't create epoll");
} }
{ {
ev_fd_ = eventfd(0, EFD_NONBLOCK); ev_fd_ = eventfd(0, EFD_NONBLOCK);
struct epoll_event event; struct epoll_event event;
memset(&event, 0, sizeof(event));
event.events = EPOLLIN | EPOLLET; event.events = EPOLLIN | EPOLLET;
event.data.fd = ev_fd_; event.data.fd = ev_fd_;
if (epoll_ctl(efd_, EPOLL_CTL_ADD, ev_fd_, &event) == -1) { if (epoll_ctl(efd_, EPOLL_CTL_ADD, ev_fd_, &event) == -1) {
@ -80,8 +165,9 @@ void waybar::modules::Network::createInfoSocket()
} }
} }
{ {
auto fd = nl_socket_get_fd(info_sock_); auto fd = nl_socket_get_fd(ev_sock_);
struct epoll_event event; struct epoll_event event;
memset(&event, 0, sizeof(event));
event.events = EPOLLIN | EPOLLET | EPOLLRDHUP; event.events = EPOLLIN | EPOLLET | EPOLLRDHUP;
event.data.fd = fd; event.data.fd = fd;
if (epoll_ctl(efd_, EPOLL_CTL_ADD, fd, &event) == -1) { if (epoll_ctl(efd_, EPOLL_CTL_ADD, fd, &event) == -1) {
@ -90,114 +176,168 @@ void waybar::modules::Network::createInfoSocket()
} }
} }
void waybar::modules::Network::createEventSocket() void waybar::modules::Network::createInfoSocket() {
{ sock_ = nl_socket_alloc();
sk_ = nl_socket_alloc(); if (genl_connect(sock_) != 0) {
if (genl_connect(sk_) != 0) {
throw std::runtime_error("Can't connect to netlink socket"); throw std::runtime_error("Can't connect to netlink socket");
} }
if (nl_socket_modify_cb(sk_, NL_CB_VALID, NL_CB_CUSTOM, handleScan, this) < 0) { if (nl_socket_modify_cb(sock_, NL_CB_VALID, NL_CB_CUSTOM, handleScan, this) < 0) {
throw std::runtime_error("Can't set callback"); throw std::runtime_error("Can't set callback");
} }
nl80211_id_ = genl_ctrl_resolve(sk_, "nl80211"); nl80211_id_ = genl_ctrl_resolve(sock_, "nl80211");
if (nl80211_id_ < 0) { if (nl80211_id_ < 0) {
throw std::runtime_error("Can't resolve nl80211 interface"); throw std::runtime_error("Can't resolve nl80211 interface");
} }
} }
void waybar::modules::Network::worker() void waybar::modules::Network::worker() {
{
thread_timer_ = [this] { thread_timer_ = [this] {
if (ifid_ > 0) { {
getInfo(); std::lock_guard<std::mutex> lock(mutex_);
dp.emit(); if (ifid_ > 0) {
getInfo();
dp.emit();
}
} }
thread_timer_.sleep_for(interval_); thread_timer_.sleep_for(interval_);
}; };
thread_ = [this] { thread_ = [this] {
struct epoll_event events[16]; std::array<struct epoll_event, EPOLL_MAX> events{};
int ec = epoll_wait(efd_, events, 16, -1);
int ec = epoll_wait(efd_, events.data(), EPOLL_MAX, -1);
if (ec > 0) { if (ec > 0) {
for (auto i = 0; i < ec; i++) { for (auto i = 0; i < ec; i++) {
if (events[i].data.fd == nl_socket_get_fd(info_sock_)) { if (events[i].data.fd != nl_socket_get_fd(ev_sock_) || nl_recvmsgs_default(ev_sock_) < 0) {
nl_recvmsgs_default(info_sock_);
} else {
thread_.stop(); thread_.stop();
break; break;
} }
} }
} else if (ec == -1) {
thread_.stop();
} }
}; };
} }
auto waybar::modules::Network::update() -> void const std::string waybar::modules::Network::getNetworkState() const {
{ if (ifid_ == -1) return "disconnected";
std::string connectiontype; if (ipaddr_.empty()) return "linked";
if (ifid_ <= 0 || ipaddr_.empty()) { if (essid_.empty()) return "ethernet";
if (config_["format-disconnected"].isString()) { return "wifi";
default_format_ = config_["format-disconnected"].asString();
}
label_.get_style_context()->add_class("disconnected");
connectiontype = "disconnected";
} else {
if (essid_.empty()) {
if (config_["format-ethernet"].isString()) {
default_format_ = config_["format-ethernet"].asString();
}
connectiontype = "ethernet";
} else {
if (config_["format-wifi"].isString()) {
default_format_ = config_["format-wifi"].asString();
}
connectiontype = "wifi";
}
label_.get_style_context()->remove_class("disconnected");
}
if (!alt_) {
format_ = default_format_;
}
label_.set_markup(fmt::format(format_,
fmt::arg("essid", essid_),
fmt::arg("signaldBm", signal_strength_dbm_),
fmt::arg("signalStrength", signal_strength_),
fmt::arg("ifname", ifname_),
fmt::arg("netmask", netmask_),
fmt::arg("ipaddr", ipaddr_),
fmt::arg("cidr", cidr_),
fmt::arg("icon", getIcon(signal_strength_, connectiontype))
));
} }
void waybar::modules::Network::disconnected() auto waybar::modules::Network::update() -> void {
{ std::lock_guard<std::mutex> lock(mutex_);
essid_.clear(); std::string tooltip_format;
signal_strength_dbm_ = 0; auto down_octets = read_netstat(BANDWIDTH_CATEGORY, BANDWIDTH_DOWN_TOTAL_KEY);
signal_strength_ = 0; auto up_octets = read_netstat(BANDWIDTH_CATEGORY, BANDWIDTH_UP_TOTAL_KEY);
ipaddr_.clear();
netmask_.clear(); unsigned long long bandwidth_down = 0;
cidr_ = 0; if (down_octets) {
if (!config_["interface"].isString()) { bandwidth_down = *down_octets - bandwidth_down_total_;
ifname_.clear(); bandwidth_down_total_ = *down_octets;
ifid_ = -1; }
unsigned long long bandwidth_up = 0;
if (up_octets) {
bandwidth_up = *up_octets - bandwidth_up_total_;
bandwidth_up_total_ = *up_octets;
}
if (!alt_) {
auto state = getNetworkState();
if (!state_.empty() && label_.get_style_context()->has_class(state_)) {
label_.get_style_context()->remove_class(state_);
}
if (config_["format-" + state].isString()) {
default_format_ = config_["format-" + state].asString();
}
if (config_["tooltip-format-" + state].isString()) {
tooltip_format = config_["tooltip-format-" + state].asString();
}
if (!label_.get_style_context()->has_class(state)) {
label_.get_style_context()->add_class(state);
}
format_ = default_format_;
state_ = state;
}
getState(signal_strength_);
auto pow_format = [](unsigned long long value, const std::string &unit) {
if (value > 2000ull * 1000ull * 1000ull) { // > 2G
auto go = value / (1000 * 1000 * 1000);
return std::to_string(go) + "." +
std::to_string((value - go * 1000 * 1000 * 1000) / (100 * 1000 * 1000)) + "G" + unit;
} else if (value > 2000ull * 1000ull) { // > 2M
auto mo = value / (1000 * 1000);
return std::to_string(mo) + "." + std::to_string((value - mo * 1000 * 1000) / (100 * 1000)) +
"M" + unit;
} else if (value > 2000ull) { // > 2k
auto ko = value / 1000;
return std::to_string(ko) + "." + std::to_string((value - ko * 1000) / 100) + "k" + unit;
} else {
return std::to_string(value) + unit;
}
};
auto text = fmt::format(
format_,
fmt::arg("essid", essid_),
fmt::arg("signaldBm", signal_strength_dbm_),
fmt::arg("signalStrength", signal_strength_),
fmt::arg("ifname", ifname_),
fmt::arg("netmask", netmask_),
fmt::arg("ipaddr", ipaddr_),
fmt::arg("cidr", cidr_),
fmt::arg("frequency", frequency_),
fmt::arg("icon", getIcon(signal_strength_, state_)),
fmt::arg("bandwidthDownBits", pow_format(bandwidth_down * 8ull / interval_.count(), "b/s")),
fmt::arg("bandwidthUpBits", pow_format(bandwidth_up * 8ull / interval_.count(), "b/s")),
fmt::arg("bandwidthDownOctets", pow_format(bandwidth_down / interval_.count(), "o/s")),
fmt::arg("bandwidthUpOctets", pow_format(bandwidth_up / interval_.count(), "o/s")));
if (text != label_.get_label()) {
label_.set_markup(text);
}
if (tooltipEnabled()) {
if (tooltip_format.empty() && config_["tooltip-format"].isString()) {
tooltip_format = config_["tooltip-format"].asString();
}
if (!tooltip_format.empty()) {
auto tooltip_text = fmt::format(
tooltip_format,
fmt::arg("essid", essid_),
fmt::arg("signaldBm", signal_strength_dbm_),
fmt::arg("signalStrength", signal_strength_),
fmt::arg("ifname", ifname_),
fmt::arg("netmask", netmask_),
fmt::arg("ipaddr", ipaddr_),
fmt::arg("cidr", cidr_),
fmt::arg("frequency", frequency_),
fmt::arg("icon", getIcon(signal_strength_, state_)),
fmt::arg("bandwidthDownBits",
pow_format(bandwidth_down * 8ull / interval_.count(), "b/s")),
fmt::arg("bandwidthUpBits", pow_format(bandwidth_up * 8ull / interval_.count(), "b/s")),
fmt::arg("bandwidthDownOctets", pow_format(bandwidth_down / interval_.count(), "o/s")),
fmt::arg("bandwidthUpOctets", pow_format(bandwidth_up / interval_.count(), "o/s")));
if (label_.get_tooltip_text() != text) {
label_.set_tooltip_text(tooltip_text);
}
} else if (label_.get_tooltip_text() != text) {
label_.set_tooltip_text(text);
}
} }
// Need to wait otherwise we'll have the same information
thread_.sleep_for(std::chrono::seconds(1));
} }
// Based on https://gist.github.com/Yawning/c70d804d4b8ae78cc698 // Based on https://gist.github.com/Yawning/c70d804d4b8ae78cc698
int waybar::modules::Network::getExternalInterface() int waybar::modules::Network::getExternalInterface(int skip_idx) const {
{
static const uint32_t route_buffer_size = 8192; static const uint32_t route_buffer_size = 8192;
struct nlmsghdr *hdr = nullptr; struct nlmsghdr * hdr = nullptr;
struct rtmsg *rt = nullptr; struct rtmsg * rt = nullptr;
char resp[route_buffer_size] = {0}; char resp[route_buffer_size] = {0};
int ifidx = -1; int ifidx = -1;
/* Prepare request. */ /* Prepare request. */
constexpr uint32_t reqlen = NLMSG_SPACE(sizeof(*rt)); constexpr uint32_t reqlen = NLMSG_SPACE(sizeof(*rt));
char req[reqlen] = {0}; char req[reqlen] = {0};
/* Build the RTM_GETROUTE request. */ /* Build the RTM_GETROUTE request. */
hdr = reinterpret_cast<struct nlmsghdr *>(req); hdr = reinterpret_cast<struct nlmsghdr *>(req);
@ -226,7 +366,7 @@ int waybar::modules::Network::getExternalInterface()
/* Parse the response payload into netlink messages. */ /* Parse the response payload into netlink messages. */
for (hdr = reinterpret_cast<struct nlmsghdr *>(resp); NLMSG_OK(hdr, len); for (hdr = reinterpret_cast<struct nlmsghdr *>(resp); NLMSG_OK(hdr, len);
hdr = NLMSG_NEXT(hdr, len)) { hdr = NLMSG_NEXT(hdr, len)) {
if (hdr->nlmsg_type == NLMSG_DONE) { if (hdr->nlmsg_type == NLMSG_DONE) {
goto out; goto out;
} }
@ -253,10 +393,10 @@ int waybar::modules::Network::getExternalInterface()
/* Parse all the attributes for a single routing table entry. */ /* Parse all the attributes for a single routing table entry. */
struct rtattr *attr = RTM_RTA(rt); struct rtattr *attr = RTM_RTA(rt);
uint64_t attrlen = RTM_PAYLOAD(hdr); uint64_t attrlen = RTM_PAYLOAD(hdr);
bool has_gateway = false; bool has_gateway = false;
bool has_destination = false; bool has_destination = false;
int temp_idx = -1; int temp_idx = -1;
for (; RTA_OK(attr, attrlen); attr = RTA_NEXT(attr, attrlen)) { for (; RTA_OK(attr, attrlen); attr = RTA_NEXT(attr, attrlen)) {
/* Determine if this routing table entry corresponds to the default /* Determine if this routing table entry corresponds to the default
* route by seeing if it has a gateway, and if a destination addr is * route by seeing if it has a gateway, and if a destination addr is
@ -276,8 +416,8 @@ int waybar::modules::Network::getExternalInterface()
* Should be either missing, or maybe all 0s. Accept both. * Should be either missing, or maybe all 0s. Accept both.
*/ */
const uint32_t nr_zeroes = (family_ == AF_INET) ? 4 : 16; const uint32_t nr_zeroes = (family_ == AF_INET) ? 4 : 16;
unsigned char c = 0; unsigned char c = 0;
size_t dstlen = RTA_PAYLOAD(attr); size_t dstlen = RTA_PAYLOAD(attr);
if (dstlen != nr_zeroes) { if (dstlen != nr_zeroes) {
break; break;
} }
@ -289,7 +429,7 @@ int waybar::modules::Network::getExternalInterface()
} }
case RTA_OIF: case RTA_OIF:
/* The output interface index. */ /* The output interface index. */
temp_idx = *static_cast<int*>(RTA_DATA(attr)); temp_idx = *static_cast<int *>(RTA_DATA(attr));
break; break;
default: default:
break; break;
@ -298,7 +438,7 @@ int waybar::modules::Network::getExternalInterface()
/* If this is the default route, and we know the interface index, /* If this is the default route, and we know the interface index,
* we can stop parsing this message. * we can stop parsing this message.
*/ */
if (has_gateway && !has_destination && temp_idx != -1) { if (has_gateway && !has_destination && temp_idx != -1 && temp_idx != skip_idx) {
ifidx = temp_idx; ifidx = temp_idx;
break; break;
} }
@ -310,107 +450,216 @@ out:
} }
void waybar::modules::Network::getInterfaceAddress() { void waybar::modules::Network::getInterfaceAddress() {
unsigned int cidrRaw; unsigned int cidrRaw;
struct ifaddrs *ifaddr, *ifa; struct ifaddrs *ifaddr, *ifa;
ipaddr_.clear();
netmask_.clear();
cidr_ = 0; cidr_ = 0;
int success = getifaddrs(&ifaddr); int success = getifaddrs(&ifaddr);
if (success == 0) { if (success != 0) {
ifa = ifaddr; return;
while (ifa != nullptr && ipaddr_.empty() && netmask_.empty()) {
if (ifa->ifa_addr != nullptr && ifa->ifa_addr->sa_family == family_) {
if (strcmp(ifa->ifa_name, ifname_.c_str()) == 0) {
ipaddr_ = inet_ntoa(((struct sockaddr_in*)ifa->ifa_addr)->sin_addr);
netmask_ = inet_ntoa(((struct sockaddr_in*)ifa->ifa_netmask)->sin_addr);
cidrRaw = ((struct sockaddr_in *)(ifa->ifa_netmask))->sin_addr.s_addr;
unsigned int cidr = 0;
while (cidrRaw) {
cidr += cidrRaw & 1;
cidrRaw >>= 1;
}
cidr_ = cidr;
}
}
ifa = ifa->ifa_next;
}
freeifaddrs(ifaddr);
} }
ifa = ifaddr;
while (ifa != nullptr) {
if (ifa->ifa_addr != nullptr && ifa->ifa_addr->sa_family == family_ &&
ifa->ifa_name == ifname_) {
char ipaddr[INET6_ADDRSTRLEN];
ipaddr_ = inet_ntop(family_,
&reinterpret_cast<struct sockaddr_in *>(ifa->ifa_addr)->sin_addr,
ipaddr,
INET6_ADDRSTRLEN);
char netmask[INET6_ADDRSTRLEN];
auto net_addr = reinterpret_cast<struct sockaddr_in *>(ifa->ifa_netmask);
netmask_ = inet_ntop(family_, &net_addr->sin_addr, netmask, INET6_ADDRSTRLEN);
cidrRaw = net_addr->sin_addr.s_addr;
unsigned int cidr = 0;
while (cidrRaw) {
cidr += cidrRaw & 1;
cidrRaw >>= 1;
}
cidr_ = cidr;
break;
}
ifa = ifa->ifa_next;
}
freeifaddrs(ifaddr);
} }
int waybar::modules::Network::netlinkRequest(void *req, int waybar::modules::Network::netlinkRequest(void *req, uint32_t reqlen, uint32_t groups) const {
uint32_t reqlen, uint32_t groups)
{
struct sockaddr_nl sa = {}; struct sockaddr_nl sa = {};
sa.nl_family = AF_NETLINK; sa.nl_family = AF_NETLINK;
sa.nl_groups = groups; sa.nl_groups = groups;
struct iovec iov = { req, reqlen }; struct iovec iov = {req, reqlen};
struct msghdr msg = { &sa, sizeof(sa), &iov, 1, nullptr, 0, 0 }; struct msghdr msg = {
return sendmsg(nl_socket_get_fd(info_sock_), &msg, 0); .msg_name = &sa,
.msg_namelen = sizeof(sa),
.msg_iov = &iov,
.msg_iovlen = 1,
};
return sendmsg(nl_socket_get_fd(ev_sock_), &msg, 0);
} }
int waybar::modules::Network::netlinkResponse(void *resp, int waybar::modules::Network::netlinkResponse(void *resp, uint32_t resplen, uint32_t groups) const {
uint32_t resplen, uint32_t groups)
{
struct sockaddr_nl sa = {}; struct sockaddr_nl sa = {};
sa.nl_family = AF_NETLINK; sa.nl_family = AF_NETLINK;
sa.nl_groups = groups; sa.nl_groups = groups;
struct iovec iov = { resp, resplen }; struct iovec iov = {resp, resplen};
struct msghdr msg = { &sa, sizeof(sa), &iov, 1, nullptr, 0, 0 }; struct msghdr msg = {
auto ret = recvmsg(nl_socket_get_fd(info_sock_), &msg, 0); .msg_name = &sa,
.msg_namelen = sizeof(sa),
.msg_iov = &iov,
.msg_iovlen = 1,
};
auto ret = recvmsg(nl_socket_get_fd(ev_sock_), &msg, 0);
if (msg.msg_flags & MSG_TRUNC) { if (msg.msg_flags & MSG_TRUNC) {
return -1; return -1;
} }
return ret; return ret;
} }
int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { bool waybar::modules::Network::checkInterface(struct ifinfomsg *rtif, std::string name) {
auto net = static_cast<waybar::modules::Network *>(data); if (config_["interface"].isString()) {
bool need_update = false; return config_["interface"].asString() == name ||
nlmsghdr *nh = nlmsg_hdr(msg); wildcardMatch(config_["interface"].asString(), name);
if (nh->nlmsg_type == RTM_NEWADDR) {
need_update = true;
} }
if (nh->nlmsg_type < RTM_NEWADDR) { // getExternalInterface may need some delay to detect external interface
auto rtif = static_cast<struct ifinfomsg *>(NLMSG_DATA(nh)); for (uint8_t tries = 0; tries < MAX_RETRY; tries += 1) {
if (rtif->ifi_index == static_cast<int>(net->ifid_)) { auto external_iface = getExternalInterface();
need_update = true; if (external_iface > 0) {
if (!(rtif->ifi_flags & IFF_RUNNING)) { return external_iface == rtif->ifi_index;
net->disconnected(); }
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
return false;
}
int waybar::modules::Network::getPreferredIface(int skip_idx) const {
int ifid = -1;
if (config_["interface"].isString()) {
ifid = if_nametoindex(config_["interface"].asCString());
if (ifid > 0) {
return ifid;
} else {
// Try with wildcard
struct ifaddrs *ifaddr, *ifa;
int success = getifaddrs(&ifaddr);
if (success != 0) {
return -1;
}
ifa = ifaddr;
ifid = -1;
while (ifa != nullptr) {
if (wildcardMatch(config_["interface"].asString(), ifa->ifa_name)) {
ifid = if_nametoindex(ifa->ifa_name);
break;
}
ifa = ifa->ifa_next;
}
freeifaddrs(ifaddr);
return ifid;
}
}
// getExternalInterface may need some delay to detect external interface
for (uint8_t tries = 0; tries < MAX_RETRY; tries += 1) {
ifid = getExternalInterface(skip_idx);
if (ifid > 0) {
return ifid;
}
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
return -1;
}
void waybar::modules::Network::clearIface() {
essid_.clear();
ipaddr_.clear();
netmask_.clear();
cidr_ = 0;
signal_strength_dbm_ = 0;
signal_strength_ = 0;
frequency_ = 0;
}
void waybar::modules::Network::checkNewInterface(struct ifinfomsg *rtif) {
auto new_iface = getPreferredIface(rtif->ifi_index);
if (new_iface != -1) {
ifid_ = new_iface;
char ifname[IF_NAMESIZE];
if_indextoname(new_iface, ifname);
ifname_ = ifname;
getInterfaceAddress();
thread_timer_.wake_up();
} else {
ifid_ = -1;
dp.emit();
}
}
int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
auto net = static_cast<waybar::modules::Network *>(data);
std::lock_guard<std::mutex> lock(net->mutex_);
auto nh = nlmsg_hdr(msg);
auto ifi = static_cast<struct ifinfomsg *>(NLMSG_DATA(nh));
if (nh->nlmsg_type == RTM_DELADDR) {
// Check for valid interface
if (ifi->ifi_index == net->ifid_) {
net->ipaddr_.clear();
net->netmask_.clear();
net->cidr_ = 0;
if (!(ifi->ifi_flags & IFF_RUNNING)) {
net->clearIface();
// Check for a new interface and get info
net->checkNewInterface(ifi);
} else {
net->dp.emit(); net->dp.emit();
} }
return NL_OK;
} }
} } else if (nh->nlmsg_type == RTM_NEWLINK || nh->nlmsg_type == RTM_DELLINK) {
if (net->ifid_ <= 0 && !net->config_["interface"].isString()) { char ifname[IF_NAMESIZE];
for (uint8_t i = 0; i < MAX_RETRY; i += 1) { if_indextoname(ifi->ifi_index, ifname);
net->ifid_ = net->getExternalInterface(); // Check for valid interface
if (net->ifid_ > 0) { if (ifi->ifi_index != net->ifid_ && net->checkInterface(ifi, ifname)) {
break;
}
// Need to wait before get external interface
net->thread_.sleep_for(std::chrono::seconds(1));
}
if (net->ifid_ > 0) {
char ifname[IF_NAMESIZE];
if_indextoname(net->ifid_, ifname);
net->ifname_ = ifname; net->ifname_ = ifname;
need_update = true; net->ifid_ = ifi->ifi_index;
// Get Iface and WIFI info
net->getInterfaceAddress();
net->thread_timer_.wake_up();
return NL_OK;
} else if (ifi->ifi_index == net->ifid_ &&
(!(ifi->ifi_flags & IFF_RUNNING) || !(ifi->ifi_flags & IFF_UP) ||
!net->checkInterface(ifi, ifname))) {
net->clearIface();
// Check for a new interface and get info
net->checkNewInterface(ifi);
return NL_OK;
} }
} } else {
if (need_update) { char ifname[IF_NAMESIZE];
if (net->ifid_ > 0) { if_indextoname(ifi->ifi_index, ifname);
net->getInfo(); // Auto detected network can also be assigned here
if (ifi->ifi_index != net->ifid_ && net->checkInterface(ifi, ifname)) {
// If iface is different, clear data
if (ifi->ifi_index != net->ifid_) {
net->clearIface();
}
net->ifname_ = ifname;
net->ifid_ = ifi->ifi_index;
}
// Check for valid interface
if (ifi->ifi_index == net->ifid_) {
// Get Iface and WIFI info
net->getInterfaceAddress();
net->thread_timer_.wake_up();
return NL_OK;
} }
net->dp.emit();
} }
return NL_SKIP; return NL_SKIP;
} }
int waybar::modules::Network::handleScan(struct nl_msg *msg, void *data) { int waybar::modules::Network::handleScan(struct nl_msg *msg, void *data) {
auto net = static_cast<waybar::modules::Network *>(data); auto net = static_cast<waybar::modules::Network *>(data);
auto gnlh = static_cast<genlmsghdr *>(nlmsg_data(nlmsg_hdr(msg))); auto gnlh = static_cast<genlmsghdr *>(nlmsg_data(nlmsg_hdr(msg)));
struct nlattr* tb[NL80211_ATTR_MAX + 1]; struct nlattr * tb[NL80211_ATTR_MAX + 1];
struct nlattr* bss[NL80211_BSS_MAX + 1]; struct nlattr * bss[NL80211_BSS_MAX + 1];
struct nla_policy bss_policy[NL80211_BSS_MAX + 1]{}; struct nla_policy bss_policy[NL80211_BSS_MAX + 1]{};
bss_policy[NL80211_BSS_TSF].type = NLA_U64; bss_policy[NL80211_BSS_TSF].type = NLA_U64;
bss_policy[NL80211_BSS_FREQUENCY].type = NLA_U32; bss_policy[NL80211_BSS_FREQUENCY].type = NLA_U32;
@ -422,7 +671,8 @@ int waybar::modules::Network::handleScan(struct nl_msg *msg, void *data) {
bss_policy[NL80211_BSS_SIGNAL_UNSPEC].type = NLA_U8; bss_policy[NL80211_BSS_SIGNAL_UNSPEC].type = NLA_U8;
bss_policy[NL80211_BSS_STATUS].type = NLA_U32; bss_policy[NL80211_BSS_STATUS].type = NLA_U32;
if (nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), nullptr) < 0) { if (nla_parse(
tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), nullptr) < 0) {
return NL_SKIP; return NL_SKIP;
} }
if (tb[NL80211_ATTR_BSS] == nullptr) { if (tb[NL80211_ATTR_BSS] == nullptr) {
@ -436,26 +686,25 @@ int waybar::modules::Network::handleScan(struct nl_msg *msg, void *data) {
} }
net->parseEssid(bss); net->parseEssid(bss);
net->parseSignal(bss); net->parseSignal(bss);
// TODO(someone): parse quality net->parseFreq(bss);
return NL_SKIP; return NL_OK;
} }
void waybar::modules::Network::parseEssid(struct nlattr **bss) void waybar::modules::Network::parseEssid(struct nlattr **bss) {
{
essid_.clear();
if (bss[NL80211_BSS_INFORMATION_ELEMENTS] != nullptr) { if (bss[NL80211_BSS_INFORMATION_ELEMENTS] != nullptr) {
auto ies = auto ies = static_cast<char *>(nla_data(bss[NL80211_BSS_INFORMATION_ELEMENTS]));
static_cast<char*>(nla_data(bss[NL80211_BSS_INFORMATION_ELEMENTS])); auto ies_len = nla_len(bss[NL80211_BSS_INFORMATION_ELEMENTS]);
auto ies_len = nla_len(bss[NL80211_BSS_INFORMATION_ELEMENTS]);
const auto hdr_len = 2; const auto hdr_len = 2;
while (ies_len > hdr_len && ies[0] != 0) { while (ies_len > hdr_len && ies[0] != 0) {
ies_len -= ies[1] + hdr_len; ies_len -= ies[1] + hdr_len;
ies += ies[1] + hdr_len; ies += ies[1] + hdr_len;
} }
if (ies_len > hdr_len && ies_len > ies[1] + hdr_len) { if (ies_len > hdr_len && ies_len > ies[1] + hdr_len) {
auto essid_begin = ies + hdr_len; auto essid_begin = ies + hdr_len;
auto essid_end = essid_begin + ies[1]; auto essid_end = essid_begin + ies[1];
std::copy(essid_begin, essid_end, std::back_inserter(essid_)); std::string essid_raw;
std::copy(essid_begin, essid_end, std::back_inserter(essid_raw));
essid_ = Glib::Markup::escape_text(essid_raw);
} }
} }
} }
@ -468,16 +717,22 @@ void waybar::modules::Network::parseSignal(struct nlattr **bss) {
// WiFi-hardware usually operates in the range -90 to -20dBm. // WiFi-hardware usually operates in the range -90 to -20dBm.
const int hardwareMax = -20; const int hardwareMax = -20;
const int hardwareMin = -90; const int hardwareMin = -90;
signal_strength_ = ((signal_strength_dbm_ - hardwareMin) signal_strength_ =
/ double{hardwareMax - hardwareMin}) * 100; ((signal_strength_dbm_ - hardwareMin) / double{hardwareMax - hardwareMin}) * 100;
} }
if (bss[NL80211_BSS_SIGNAL_UNSPEC] != nullptr) { if (bss[NL80211_BSS_SIGNAL_UNSPEC] != nullptr) {
signal_strength_ = nla_get_u8(bss[NL80211_BSS_SIGNAL_UNSPEC]); signal_strength_ = nla_get_u8(bss[NL80211_BSS_SIGNAL_UNSPEC]);
} }
} }
bool waybar::modules::Network::associatedOrJoined(struct nlattr** bss) void waybar::modules::Network::parseFreq(struct nlattr **bss) {
{ if (bss[NL80211_BSS_FREQUENCY] != nullptr) {
// in MHz
frequency_ = nla_get_u32(bss[NL80211_BSS_FREQUENCY]);
}
}
bool waybar::modules::Network::associatedOrJoined(struct nlattr **bss) {
if (bss[NL80211_BSS_STATUS] == nullptr) { if (bss[NL80211_BSS_STATUS] == nullptr) {
return false; return false;
} }
@ -492,18 +747,57 @@ bool waybar::modules::Network::associatedOrJoined(struct nlattr** bss)
} }
} }
auto waybar::modules::Network::getInfo() -> void auto waybar::modules::Network::getInfo() -> void {
{ struct nl_msg *nl_msg = nlmsg_alloc();
getInterfaceAddress();
struct nl_msg* nl_msg = nlmsg_alloc();
if (nl_msg == nullptr) { if (nl_msg == nullptr) {
return; return;
} }
if (genlmsg_put(nl_msg, NL_AUTO_PORT, NL_AUTO_SEQ, nl80211_id_, 0, NLM_F_DUMP, if (genlmsg_put(
NL80211_CMD_GET_SCAN, 0) == nullptr nl_msg, NL_AUTO_PORT, NL_AUTO_SEQ, nl80211_id_, 0, NLM_F_DUMP, NL80211_CMD_GET_SCAN, 0) ==
|| nla_put_u32(nl_msg, NL80211_ATTR_IFINDEX, ifid_) < 0) { nullptr ||
nla_put_u32(nl_msg, NL80211_ATTR_IFINDEX, ifid_) < 0) {
nlmsg_free(nl_msg); nlmsg_free(nl_msg);
return; return;
} }
nl_send_sync(sk_, nl_msg); nl_send_sync(sock_, nl_msg);
}
// https://gist.github.com/rressi/92af77630faf055934c723ce93ae2495
bool waybar::modules::Network::wildcardMatch(const std::string &pattern,
const std::string &text) const {
auto P = int(pattern.size());
auto T = int(text.size());
auto p = 0, fallback_p = -1;
auto t = 0, fallback_t = -1;
while (t < T) {
// Wildcard match:
if (p < P && pattern[p] == '*') {
fallback_p = p++; // starting point after failures
fallback_t = t; // starting point after failures
}
// Simple match:
else if (p < P && (pattern[p] == '?' || pattern[p] == text[t])) {
p++;
t++;
}
// Failure, fall back just after last matched '*':
else if (fallback_p >= 0) {
p = fallback_p + 1; // position just after last matched '*"
t = ++fallback_t; // re-try to match text from here
}
// There were no '*' before, so we fail here:
else {
return false;
}
}
// Consume all '*' at the end of pattern:
while (p < P && pattern[p] == '*') p++;
return p == P;
} }

View File

@ -1,18 +1,16 @@
#include "modules/pulseaudio.hpp" #include "modules/pulseaudio.hpp"
waybar::modules::Pulseaudio::Pulseaudio(const std::string& id, const Json::Value &config) waybar::modules::Pulseaudio::Pulseaudio(const std::string &id, const Json::Value &config)
: ALabel(config, "{volume}%"), : ALabel(config, "pulseaudio", id, "{volume}%"),
mainloop_(nullptr), mainloop_(nullptr),
mainloop_api_(nullptr), mainloop_api_(nullptr),
context_(nullptr), context_(nullptr),
sink_idx_(0), sink_idx_(0),
volume_(0), volume_(0),
muted_(false), muted_(false),
scrolling_(false) { source_idx_(0),
label_.set_name("pulseaudio"); source_volume_(0),
if (!id.empty()) { source_muted_(false) {
label_.get_style_context()->add_class(id);
}
mainloop_ = pa_threaded_mainloop_new(); mainloop_ = pa_threaded_mainloop_new();
if (mainloop_ == nullptr) { if (mainloop_ == nullptr) {
throw std::runtime_error("pa_mainloop_new() failed."); throw std::runtime_error("pa_mainloop_new() failed.");
@ -23,10 +21,9 @@ waybar::modules::Pulseaudio::Pulseaudio(const std::string& id, const Json::Value
if (context_ == nullptr) { if (context_ == nullptr) {
throw std::runtime_error("pa_context_new() failed."); throw std::runtime_error("pa_context_new() failed.");
} }
if (pa_context_connect(context_, nullptr, PA_CONTEXT_NOAUTOSPAWN, if (pa_context_connect(context_, nullptr, PA_CONTEXT_NOAUTOSPAWN, nullptr) < 0) {
nullptr) < 0) { auto err =
auto err = fmt::format("pa_context_connect() failed: {}", fmt::format("pa_context_connect() failed: {}", pa_strerror(pa_context_errno(context_)));
pa_strerror(pa_context_errno(context_)));
throw std::runtime_error(err); throw std::runtime_error(err);
} }
pa_context_set_state_callback(context_, contextStateCb, this); pa_context_set_state_callback(context_, contextStateCb, this);
@ -34,15 +31,8 @@ waybar::modules::Pulseaudio::Pulseaudio(const std::string& id, const Json::Value
throw std::runtime_error("pa_mainloop_run() failed."); throw std::runtime_error("pa_mainloop_run() failed.");
} }
pa_threaded_mainloop_unlock(mainloop_); pa_threaded_mainloop_unlock(mainloop_);
event_box_.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);
// define the pulse scroll events only when no user provided event_box_.signal_scroll_event().connect(sigc::mem_fun(*this, &Pulseaudio::handleScroll));
// events are configured
if (!config["on-scroll-up"].isString() &&
!config["on-scroll-down"].isString()) {
event_box_.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);
event_box_.signal_scroll_event().connect(
sigc::mem_fun(*this, &Pulseaudio::handleScroll));
}
} }
waybar::modules::Pulseaudio::~Pulseaudio() { waybar::modules::Pulseaudio::~Pulseaudio() {
@ -51,8 +41,7 @@ waybar::modules::Pulseaudio::~Pulseaudio() {
pa_threaded_mainloop_free(mainloop_); pa_threaded_mainloop_free(mainloop_);
} }
void waybar::modules::Pulseaudio::contextStateCb(pa_context *c, void *data) void waybar::modules::Pulseaudio::contextStateCb(pa_context *c, void *data) {
{
auto pa = static_cast<waybar::modules::Pulseaudio *>(data); auto pa = static_cast<waybar::modules::Pulseaudio *>(data);
switch (pa_context_get_state(c)) { switch (pa_context_get_state(c)) {
case PA_CONTEXT_TERMINATED: case PA_CONTEXT_TERMINATED:
@ -61,104 +50,114 @@ void waybar::modules::Pulseaudio::contextStateCb(pa_context *c, void *data)
case PA_CONTEXT_READY: case PA_CONTEXT_READY:
pa_context_get_server_info(c, serverInfoCb, data); pa_context_get_server_info(c, serverInfoCb, data);
pa_context_set_subscribe_callback(c, subscribeCb, data); pa_context_set_subscribe_callback(c, subscribeCb, data);
pa_context_subscribe(c, PA_SUBSCRIPTION_MASK_SINK, nullptr, nullptr); pa_context_subscribe(
c,
static_cast<enum pa_subscription_mask>(static_cast<int>(PA_SUBSCRIPTION_MASK_SINK) |
static_cast<int>(PA_SUBSCRIPTION_MASK_SOURCE)),
nullptr,
nullptr);
break;
case PA_CONTEXT_FAILED:
pa->mainloop_api_->quit(pa->mainloop_api_, 1);
break; break;
case PA_CONTEXT_CONNECTING: case PA_CONTEXT_CONNECTING:
case PA_CONTEXT_AUTHORIZING: case PA_CONTEXT_AUTHORIZING:
case PA_CONTEXT_SETTING_NAME: case PA_CONTEXT_SETTING_NAME:
break;
case PA_CONTEXT_FAILED:
default: default:
pa->mainloop_api_->quit(pa->mainloop_api_, 1);
break; break;
} }
} }
bool waybar::modules::Pulseaudio::handleScroll(GdkEventScroll *e) { bool waybar::modules::Pulseaudio::handleScroll(GdkEventScroll *e) {
// Avoid concurrent scroll event // change the pulse volume only when no user provided
bool direction_up = false; // events are configured
uint16_t change = config_["scroll-step"].isUInt() ? config_["scroll-step"].asUInt() * 100 : 100; if (config_["on-scroll-up"].isString() || config_["on-scroll-down"].isString()) {
pa_cvolume pa_volume = pa_volume_; return AModule::handleScroll(e);
if (scrolling_) {
return false;
} }
scrolling_ = true; auto dir = AModule::getScrollDir(e);
if (e->direction == GDK_SCROLL_UP) { if (dir == SCROLL_DIR::NONE) {
direction_up = true; return true;
} }
if (e->direction == GDK_SCROLL_DOWN) { double volume_tick = static_cast<double>(PA_VOLUME_NORM) / 100;
direction_up = false; pa_volume_t change = volume_tick;
pa_cvolume pa_volume = pa_volume_;
// isDouble returns true for integers as well, just in case
if (config_["scroll-step"].isDouble()) {
change = round(config_["scroll-step"].asDouble() * volume_tick);
} }
if (dir == SCROLL_DIR::UP) {
if (e->direction == GDK_SCROLL_SMOOTH) { if (volume_ + 1 < 100) {
gdouble delta_x, delta_y; pa_cvolume_inc(&pa_volume, change);
gdk_event_get_scroll_deltas(reinterpret_cast<const GdkEvent *>(e), &delta_x, }
&delta_y); } else if (dir == SCROLL_DIR::DOWN) {
if (delta_y < 0) { if (volume_ - 1 >= 0) {
direction_up = true; pa_cvolume_dec(&pa_volume, change);
} else if (delta_y > 0) {
direction_up = false;
} }
} }
pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume, volumeModifyCb, this);
if (direction_up) {
if (volume_ + 1 < 100) pa_cvolume_inc(&pa_volume, change);
} else {
if (volume_ - 1 > 0) pa_cvolume_dec(&pa_volume, change);
}
pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume,
volumeModifyCb, this);
return true; return true;
} }
/* /*
* Called when an event we subscribed to occurs. * Called when an event we subscribed to occurs.
*/ */
void waybar::modules::Pulseaudio::subscribeCb(pa_context* context, void waybar::modules::Pulseaudio::subscribeCb(pa_context * context,
pa_subscription_event_type_t type, uint32_t idx, void* data) pa_subscription_event_type_t type, uint32_t idx,
{ void *data) {
unsigned facility = type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK; unsigned facility = type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK;
unsigned operation = type & PA_SUBSCRIPTION_EVENT_TYPE_MASK;
switch (facility) { if (operation != PA_SUBSCRIPTION_EVENT_CHANGE) {
case PA_SUBSCRIPTION_EVENT_SINK: return;
pa_context_get_sink_info_by_index(context, idx, sinkInfoCb, data); }
break; if (facility == PA_SUBSCRIPTION_EVENT_SINK) {
default: pa_context_get_sink_info_by_index(context, idx, sinkInfoCb, data);
break; } else if (facility == PA_SUBSCRIPTION_EVENT_SOURCE) {
pa_context_get_source_info_by_index(context, idx, sourceInfoCb, data);
} }
} }
/* /*
* Called in response to a volume change request * Called in response to a volume change request
*/ */
void waybar::modules::Pulseaudio::volumeModifyCb(pa_context *c, int success, void waybar::modules::Pulseaudio::volumeModifyCb(pa_context *c, int success, void *data) {
void *data) {
auto pa = static_cast<waybar::modules::Pulseaudio *>(data); auto pa = static_cast<waybar::modules::Pulseaudio *>(data);
if (success) { if (success != 0) {
pa_context_get_sink_info_by_index(pa->context_, pa->sink_idx_, sinkInfoCb, pa_context_get_sink_info_by_index(pa->context_, pa->sink_idx_, sinkInfoCb, data);
data); }
}
/*
* Called when the requested source information is ready.
*/
void waybar::modules::Pulseaudio::sourceInfoCb(pa_context * /*context*/, const pa_source_info *i,
int /*eol*/, void *data) {
if (i != nullptr) {
auto self = static_cast<waybar::modules::Pulseaudio *>(data);
auto source_volume = static_cast<float>(pa_cvolume_avg(&(i->volume))) / float{PA_VOLUME_NORM};
self->source_volume_ = std::round(source_volume * 100.0F);
self->source_idx_ = i->index;
self->source_muted_ = i->mute != 0;
self->source_desc_ = i->description;
self->source_port_name_ = i->active_port != nullptr ? i->active_port->name : "Unknown";
self->dp.emit();
} }
} }
/* /*
* Called when the requested sink information is ready. * Called when the requested sink information is ready.
*/ */
void waybar::modules::Pulseaudio::sinkInfoCb(pa_context * /*context*/, void waybar::modules::Pulseaudio::sinkInfoCb(pa_context * /*context*/, const pa_sink_info *i,
const pa_sink_info *i, int /*eol*/, int /*eol*/, void * data) {
void *data) {
if (i != nullptr) { if (i != nullptr) {
auto pa = static_cast<waybar::modules::Pulseaudio *>(data); auto pa = static_cast<waybar::modules::Pulseaudio *>(data);
pa->pa_volume_ = i->volume; pa->pa_volume_ = i->volume;
float volume = static_cast<float>(pa_cvolume_avg(&(pa->pa_volume_))) / float volume = static_cast<float>(pa_cvolume_avg(&(pa->pa_volume_))) / float{PA_VOLUME_NORM};
float{PA_VOLUME_NORM};
pa->sink_idx_ = i->index; pa->sink_idx_ = i->index;
pa->volume_ = std::round(volume * 100.0f); pa->volume_ = std::round(volume * 100.0F);
pa->muted_ = i->mute != 0; pa->muted_ = i->mute != 0;
pa->desc_ = i->description; pa->desc_ = i->description;
pa->port_name_ = i->active_port ? i->active_port->name : "Unknown"; pa->monitor_ = i->monitor_source_name;
pa->port_name_ = i->active_port != nullptr ? i->active_port->name : "Unknown";
pa->dp.emit(); pa->dp.emit();
} }
} }
@ -167,16 +166,13 @@ void waybar::modules::Pulseaudio::sinkInfoCb(pa_context * /*context*/,
* Called when the requested information on the server is ready. This is * Called when the requested information on the server is ready. This is
* used to find the default PulseAudio sink. * used to find the default PulseAudio sink.
*/ */
void waybar::modules::Pulseaudio::serverInfoCb(pa_context *context, void waybar::modules::Pulseaudio::serverInfoCb(pa_context *context, const pa_server_info *i,
const pa_server_info *i, void *data) void *data) {
{ pa_context_get_sink_info_by_name(context, i->default_sink_name, sinkInfoCb, data);
pa_context_get_sink_info_by_name(context, i->default_sink_name, pa_context_get_source_info_by_name(context, i->default_source_name, sourceInfoCb, data);
sinkInfoCb, data);
} }
const std::string waybar::modules::Pulseaudio::getPortIcon() const static const std::array<std::string, 9> ports = {
{
std::vector<std::string> ports = {
"headphones", "headphones",
"speaker", "speaker",
"hdmi", "hdmi",
@ -186,39 +182,50 @@ const std::string waybar::modules::Pulseaudio::getPortIcon() const
"car", "car",
"hifi", "hifi",
"phone", "phone",
}; };
for (auto const& port : ports) {
if (port_name_.find(port) != std::string::npos) { const std::string waybar::modules::Pulseaudio::getPortIcon() const {
std::string nameLC = port_name_;
std::transform(nameLC.begin(), nameLC.end(), nameLC.begin(), ::tolower);
for (auto const &port : ports) {
if (nameLC.find(port) != std::string::npos) {
return port; return port;
} }
} }
return port_name_; return port_name_;
} }
auto waybar::modules::Pulseaudio::update() -> void auto waybar::modules::Pulseaudio::update() -> void {
{
auto format = format_; auto format = format_;
if (muted_) { std::string format_name = "format";
format = if (monitor_.find("a2dp_sink") != std::string::npos) {
config_["format-muted"].isString() ? config_["format-muted"].asString() : format; format_name = format_name + "-bluetooth";
label_.get_style_context()->add_class("bluetooth");
} else {
label_.get_style_context()->remove_class("bluetooth");
}
if (muted_ ) {
format_name = format_name + "-muted";
label_.get_style_context()->add_class("muted"); label_.get_style_context()->add_class("muted");
} else { } else {
label_.get_style_context()->remove_class("muted"); label_.get_style_context()->remove_class("muted");
if (port_name_.find("a2dp_sink") != std::string::npos) {
format = config_["format-bluetooth"].isString()
? config_["format-bluetooth"].asString() : format;
label_.get_style_context()->add_class("bluetooth");
} else {
label_.get_style_context()->remove_class("bluetooth");
}
} }
label_.set_markup( format =
fmt::format(format, fmt::arg("volume", volume_), config_[format_name].isString() ? config_[format_name].asString() : format;
fmt::arg("icon", getIcon(volume_, getPortIcon())))); // TODO: find a better way to split source/sink
std::string format_source = "{volume}%";
if (source_muted_ && config_["format-source-muted"].isString()) {
format_source = config_["format-source-muted"].asString();
} else if (!source_muted_ && config_["format-source"].isString()) {
format_source = config_["format-source"].asString();
}
format_source = fmt::format(format_source, fmt::arg("volume", source_volume_));
label_.set_markup(fmt::format(format,
fmt::arg("volume", volume_),
fmt::arg("format_source", format_source),
fmt::arg("icon", getIcon(volume_, getPortIcon()))));
getState(volume_);
if (tooltipEnabled()) { if (tooltipEnabled()) {
label_.set_tooltip_text(desc_); label_.set_tooltip_text(desc_);
} }
if (scrolling_) {
scrolling_ = false;
}
} }

View File

@ -1,99 +1,102 @@
#include "modules/sni/host.hpp" #include "modules/sni/host.hpp"
#include <fmt/ostream.h>
#include <spdlog/spdlog.h>
#include <iostream> namespace waybar::modules::SNI {
using namespace waybar::modules::SNI; Host::Host(const std::size_t id, const Json::Value& config,
const std::function<void(std::unique_ptr<Item>&)>& on_add,
const std::function<void(std::unique_ptr<Item>&)>& on_remove)
: bus_name_("org.kde.StatusNotifierHost-" + std::to_string(getpid()) + "-" +
std::to_string(id)),
object_path_("/StatusNotifierHost/" + std::to_string(id)),
bus_name_id_(Gio::DBus::own_name(Gio::DBus::BusType::BUS_TYPE_SESSION, bus_name_,
sigc::mem_fun(*this, &Host::busAcquired))),
config_(config),
on_add_(on_add),
on_remove_(on_remove) {}
Host::Host(const std::size_t id, const Json::Value &config, Host::~Host() {
const std::function<void(std::unique_ptr<Item>&)>& on_add, if (bus_name_id_ > 0) {
const std::function<void(std::unique_ptr<Item>&)>& on_remove) Gio::DBus::unwatch_name(bus_name_id_);
: bus_name_("org.kde.StatusNotifierHost-" + std::to_string(getpid()) + "-" + std::to_string(id)), bus_name_id_ = 0;
object_path_("/StatusNotifierHost/" + std::to_string(id)), }
bus_name_id_(Gio::DBus::own_name(Gio::DBus::BusType::BUS_TYPE_SESSION, bus_name_, if (watcher_id_ > 0) {
sigc::mem_fun(*this, &Host::busAcquired))), Gio::DBus::unwatch_name(watcher_id_);
config_(config), on_add_(on_add), on_remove_(on_remove) watcher_id_ = 0;
{ }
g_cancellable_cancel(cancellable_);
g_clear_object(&cancellable_);
g_clear_object(&watcher_);
} }
Host::~Host() void Host::busAcquired(const Glib::RefPtr<Gio::DBus::Connection>& conn, Glib::ustring name) {
{ watcher_id_ = Gio::DBus::watch_name(conn,
Gio::DBus::unwatch_name(bus_name_id_); "org.kde.StatusNotifierWatcher",
} sigc::mem_fun(*this, &Host::nameAppeared),
sigc::mem_fun(*this, &Host::nameVanished));
void Host::busAcquired(const Glib::RefPtr<Gio::DBus::Connection>& conn, Glib::ustring name)
{
watcher_id_ = Gio::DBus::watch_name(conn, "org.kde.StatusNotifierWatcher",
sigc::mem_fun(*this, &Host::nameAppeared), sigc::mem_fun(*this, &Host::nameVanished));
} }
void Host::nameAppeared(const Glib::RefPtr<Gio::DBus::Connection>& conn, const Glib::ustring name, void Host::nameAppeared(const Glib::RefPtr<Gio::DBus::Connection>& conn, const Glib::ustring name,
const Glib::ustring& name_owner) const Glib::ustring& name_owner) {
{
if (cancellable_ != nullptr) { if (cancellable_ != nullptr) {
// TODO // TODO
return; return;
} }
cancellable_ = g_cancellable_new(); cancellable_ = g_cancellable_new();
sn_watcher_proxy_new( sn_watcher_proxy_new(conn->gobj(),
conn->gobj(), G_DBUS_PROXY_FLAGS_NONE,
G_DBUS_PROXY_FLAGS_NONE, "org.kde.StatusNotifierWatcher",
"org.kde.StatusNotifierWatcher", "/StatusNotifierWatcher",
"/StatusNotifierWatcher", cancellable_,
cancellable_, &Host::proxyReady, this); &Host::proxyReady,
this);
} }
void Host::nameVanished(const Glib::RefPtr<Gio::DBus::Connection>& conn, const Glib::ustring name) void Host::nameVanished(const Glib::RefPtr<Gio::DBus::Connection>& conn, const Glib::ustring name) {
{
g_cancellable_cancel(cancellable_); g_cancellable_cancel(cancellable_);
g_clear_object(&cancellable_); g_clear_object(&cancellable_);
g_clear_object(&watcher_); g_clear_object(&watcher_);
items_.clear(); items_.clear();
} }
void Host::proxyReady(GObject* src, GAsyncResult* res, void Host::proxyReady(GObject* src, GAsyncResult* res, gpointer data) {
gpointer data) GError* error = nullptr;
{
GError* error = nullptr;
SnWatcher* watcher = sn_watcher_proxy_new_finish(res, &error); SnWatcher* watcher = sn_watcher_proxy_new_finish(res, &error);
if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
std::cerr << error->message << std::endl; spdlog::error("Host: {}", error->message);
g_error_free(error); g_error_free(error);
return; return;
} }
auto host = static_cast<SNI::Host *>(data); auto host = static_cast<SNI::Host*>(data);
host->watcher_ = watcher; host->watcher_ = watcher;
if (error != nullptr) { if (error != nullptr) {
std::cerr << error->message << std::endl; spdlog::error("Host: {}", error->message);
g_error_free(error); g_error_free(error);
return; return;
} }
sn_watcher_call_register_host( sn_watcher_call_register_host(
host->watcher_, host->object_path_.c_str(), host->cancellable_, host->watcher_, host->object_path_.c_str(), host->cancellable_, &Host::registerHost, data);
&Host::registerHost, data);
} }
void Host::registerHost(GObject* src, GAsyncResult* res, void Host::registerHost(GObject* src, GAsyncResult* res, gpointer data) {
gpointer data)
{
GError* error = nullptr; GError* error = nullptr;
sn_watcher_call_register_host_finish(SN_WATCHER(src), res, &error); sn_watcher_call_register_host_finish(SN_WATCHER(src), res, &error);
if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
std::cerr << error->message << std::endl; spdlog::error("Host: {}", error->message);
g_error_free(error); g_error_free(error);
return; return;
} }
auto host = static_cast<SNI::Host *>(data); auto host = static_cast<SNI::Host*>(data);
if (error != nullptr) { if (error != nullptr) {
std::cerr << error->message << std::endl; spdlog::error("Host: {}", error->message);
g_error_free(error); g_error_free(error);
return; return;
} }
g_signal_connect(host->watcher_, "item-registered", g_signal_connect(host->watcher_, "item-registered", G_CALLBACK(&Host::itemRegistered), data);
G_CALLBACK(&Host::itemRegistered), data); g_signal_connect(host->watcher_, "item-unregistered", G_CALLBACK(&Host::itemUnregistered), data);
g_signal_connect(host->watcher_, "item-unregistered",
G_CALLBACK(&Host::itemUnregistered), data);
auto items = sn_watcher_dup_registered_items(host->watcher_); auto items = sn_watcher_dup_registered_items(host->watcher_);
if (items) { if (items != nullptr) {
for (uint32_t i = 0; items[i] != nullptr; i += 1) { for (uint32_t i = 0; items[i] != nullptr; i += 1) {
host->addRegisteredItem(items[i]); host->addRegisteredItem(items[i]);
} }
@ -101,16 +104,13 @@ void Host::registerHost(GObject* src, GAsyncResult* res,
g_strfreev(items); g_strfreev(items);
} }
void Host::itemRegistered(SnWatcher* watcher, const gchar* service, gpointer data) void Host::itemRegistered(SnWatcher* watcher, const gchar* service, gpointer data) {
{ auto host = static_cast<SNI::Host*>(data);
auto host = static_cast<SNI::Host *>(data);
host->addRegisteredItem(service); host->addRegisteredItem(service);
} }
void Host::itemUnregistered( void Host::itemUnregistered(SnWatcher* watcher, const gchar* service, gpointer data) {
SnWatcher* watcher, const gchar* service, gpointer data) auto host = static_cast<SNI::Host*>(data);
{
auto host = static_cast<SNI::Host *>(data);
auto [bus_name, object_path] = host->getBusNameAndObjectPath(service); auto [bus_name, object_path] = host->getBusNameAndObjectPath(service);
for (auto it = host->items_.begin(); it != host->items_.end(); ++it) { for (auto it = host->items_.begin(); it != host->items_.end(); ++it) {
if ((*it)->bus_name == bus_name && (*it)->object_path == object_path) { if ((*it)->bus_name == bus_name && (*it)->object_path == object_path) {
@ -121,19 +121,24 @@ void Host::itemUnregistered(
} }
} }
std::tuple<std::string, std::string> Host::getBusNameAndObjectPath( std::tuple<std::string, std::string> Host::getBusNameAndObjectPath(const std::string service) {
const std::string service) auto it = service.find('/');
{
auto it = service.find("/");
if (it != std::string::npos) { if (it != std::string::npos) {
return {service.substr(0, it), service.substr(it)}; return {service.substr(0, it), service.substr(it)};
} }
return {service, "/StatusNotifierItem"}; return {service, "/StatusNotifierItem"};
} }
void Host::addRegisteredItem(std::string service) void Host::addRegisteredItem(std::string service) {
{ std::string bus_name, object_path;
auto [bus_name, object_path] = getBusNameAndObjectPath(service); std::tie(bus_name, object_path) = getBusNameAndObjectPath(service);
items_.emplace_back(new Item(bus_name, object_path, config_)); auto it = std::find_if(items_.begin(), items_.end(), [&bus_name, &object_path](const auto& item) {
on_add_(items_.back()); return bus_name == item->bus_name && object_path == item->object_path;
});
if (it == items_.end()) {
items_.emplace_back(new Item(bus_name, object_path, config_));
on_add_(items_.back());
}
} }
} // namespace waybar::modules::SNI

View File

@ -1,136 +1,217 @@
#include "modules/sni/item.hpp" #include "modules/sni/item.hpp"
#include <glibmm/main.h>
#include <spdlog/spdlog.h>
#include <iostream> template <>
struct fmt::formatter<Glib::ustring> : formatter<std::string> {
template <typename FormatContext>
auto format(const Glib::ustring& value, FormatContext& ctx) {
return formatter<std::string>::format(value, ctx);
}
};
waybar::modules::SNI::Item::Item(std::string bn, std::string op, const Json::Value& config) template <>
: bus_name(bn), object_path(op), icon_size(16), effective_icon_size(0) struct fmt::formatter<Glib::VariantBase> : formatter<std::string> {
{ bool is_printable(const Glib::VariantBase& value) {
auto type = value.get_type_string();
/* Print only primitive (single character excluding 'v') and short complex types */
return (type.length() == 1 && islower(type[0]) && type[0] != 'v') || value.get_size() <= 32;
}
template <typename FormatContext>
auto format(const Glib::VariantBase& value, FormatContext& ctx) {
if (is_printable(value)) {
return formatter<std::string>::format(value.print(), ctx);
} else {
return formatter<std::string>::format(value.get_type_string(), ctx);
}
}
};
namespace waybar::modules::SNI {
static const Glib::ustring SNI_INTERFACE_NAME = sn_item_interface_info()->name;
static const unsigned UPDATE_DEBOUNCE_TIME = 10;
Item::Item(const std::string& bn, const std::string& op, const Json::Value& config)
: bus_name(bn),
object_path(op),
icon_size(16),
effective_icon_size(0),
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();
} }
event_box.add(image); event_box.add(image);
event_box.add_events(Gdk::BUTTON_PRESS_MASK); event_box.add_events(Gdk::BUTTON_PRESS_MASK);
event_box.signal_button_press_event().connect( event_box.signal_button_press_event().connect(sigc::mem_fun(*this, &Item::handleClick));
sigc::mem_fun(*this, &Item::handleClick));
cancellable_ = g_cancellable_new(); cancellable_ = Gio::Cancellable::create();
sn_item_proxy_new_for_bus(
G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_NONE, bus_name.c_str(), auto interface = Glib::wrap(sn_item_interface_info(), true);
object_path.c_str(), cancellable_, &Item::proxyReady, this); Gio::DBus::Proxy::create_for_bus(Gio::DBus::BusType::BUS_TYPE_SESSION,
bus_name,
object_path,
SNI_INTERFACE_NAME,
sigc::mem_fun(*this, &Item::proxyReady),
cancellable_,
interface);
} }
void waybar::modules::SNI::Item::proxyReady(GObject *obj, GAsyncResult *res, void Item::proxyReady(Glib::RefPtr<Gio::AsyncResult>& result) {
gpointer data) { try {
GError *error = nullptr; this->proxy_ = Gio::DBus::Proxy::create_for_bus_finish(result);
SnItem *proxy = sn_item_proxy_new_for_bus_finish(res, &error); /* Properties are already cached during object creation */
if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { auto cached_properties = this->proxy_->get_cached_property_names();
g_error_free(error); for (const auto& name : cached_properties) {
return; Glib::VariantBase value;
} this->proxy_->get_cached_property(value, name);
auto item = static_cast<SNI::Item *>(data); setProperty(name, value);
item->proxy_ = proxy;
if (error) {
std::cerr << error->message << std::endl;
g_error_free(error);
return;
}
auto conn = g_dbus_proxy_get_connection(G_DBUS_PROXY(proxy));
g_dbus_connection_call(conn, item->bus_name.c_str(),
item->object_path.c_str(), "org.freedesktop.DBus.Properties", "GetAll",
g_variant_new("(s)", "org.kde.StatusNotifierItem"),
G_VARIANT_TYPE("(a{sv})"), G_DBUS_CALL_FLAGS_NONE, -1, item->cancellable_,
&Item::getAll, data);
}
void waybar::modules::SNI::Item::getAll(GObject *obj, GAsyncResult *res,
gpointer data) {
GError *error = nullptr;
auto conn = G_DBUS_CONNECTION(obj);
GVariant *properties = g_dbus_connection_call_finish(conn, res, &error);
if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
g_error_free(error);
return;
}
auto item = static_cast<SNI::Item *>(data);
if (error) {
std::cerr << error->message << std::endl;
g_error_free(error);
return;
}
GVariantIter *it = nullptr;
g_variant_get(properties, "(a{sv})", &it);
gchar *key;
GVariant *value;
while (g_variant_iter_next(it, "{sv}", &key, &value)) {
if (g_strcmp0(key, "Category") == 0) {
item->category = g_variant_dup_string(value, nullptr);
} else if (g_strcmp0(key, "Id") == 0) {
item->id = g_variant_dup_string(value, nullptr);
} else if (g_strcmp0(key, "Title") == 0) {
item->title = g_variant_dup_string(value, nullptr);
} else if (g_strcmp0(key, "Status") == 0) {
item->status = g_variant_dup_string(value, nullptr);
} else if (g_strcmp0(key, "WindowId") == 0) {
item->window_id = g_variant_get_int32(value);
} else if (g_strcmp0(key, "IconName") == 0) {
item->icon_name = g_variant_dup_string(value, nullptr);
} else if (g_strcmp0(key, "IconPixmap") == 0) {
item->icon_pixmap = item->extractPixBuf(value);
} else if (g_strcmp0(key, "OverlayIconName") == 0) {
item->overlay_icon_name = g_variant_dup_string(value, nullptr);
} else if (g_strcmp0(key, "OverlayIconPixmap") == 0) {
// TODO: overlay_icon_pixmap
} else if (g_strcmp0(key, "AttentionIconName") == 0) {
item->attention_icon_name = g_variant_dup_string(value, nullptr);
} else if (g_strcmp0(key, "AttentionIconPixmap") == 0) {
// TODO: attention_icon_pixmap
} else if (g_strcmp0(key, "AttentionMovieName") == 0) {
item->attention_movie_name = g_variant_dup_string(value, nullptr);
} else if (g_strcmp0(key, "ToolTip") == 0) {
// TODO: tooltip
} else if (g_strcmp0(key, "IconThemePath") == 0) {
item->icon_theme_path = g_variant_dup_string(value, nullptr);
} else if (g_strcmp0(key, "Menu") == 0) {
item->menu = g_variant_dup_string(value, nullptr);
} else if (g_strcmp0(key, "ItemIsMenu") == 0) {
item->item_is_menu = g_variant_get_boolean(value);
} }
g_variant_unref(value);
g_free(key); this->proxy_->signal_signal().connect(sigc::mem_fun(*this, &Item::onSignal));
if (this->id.empty() || this->category.empty() || this->status.empty()) {
spdlog::error("Invalid Status Notifier Item: {}, {}", bus_name, object_path);
return;
}
this->updateImage();
// this->event_box.set_tooltip_text(this->title);
} catch (const Glib::Error& err) {
spdlog::error("Failed to create DBus Proxy for {} {}: {}", bus_name, object_path, err.what());
} catch (const std::exception& err) {
spdlog::error("Failed to create DBus Proxy for {} {}: {}", bus_name, object_path, err.what());
} }
g_variant_iter_free(it);
g_variant_unref(properties);
if (item->id.empty() || item->category.empty() || item->status.empty()) {
std::cerr << "Invalid Status Notifier Item: " + item->bus_name + "," +
item->object_path << std::endl;
return;
}
if (!item->icon_theme_path.empty()) {
GtkIconTheme *icon_theme = gtk_icon_theme_get_default();
gtk_icon_theme_append_search_path(icon_theme,
item->icon_theme_path.c_str());
}
item->updateImage();
// item->event_box.set_tooltip_text(item->title);
// TODO: handle change
} }
Glib::RefPtr<Gdk::Pixbuf> template <typename T>
waybar::modules::SNI::Item::extractPixBuf(GVariant *variant) { T get_variant(Glib::VariantBase& value) {
GVariantIter *it; return Glib::VariantBase::cast_dynamic<Glib::Variant<T>>(value).get();
}
void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) {
try {
spdlog::trace("Set tray item property: {}.{} = {}", id.empty() ? bus_name : id, name, value);
if (name == "Category") {
category = get_variant<std::string>(value);
} else if (name == "Id") {
id = get_variant<std::string>(value);
} else if (name == "Title") {
title = get_variant<std::string>(value);
} else if (name == "Status") {
status = get_variant<std::string>(value);
} else if (name == "WindowId") {
window_id = get_variant<int32_t>(value);
} else if (name == "IconName") {
icon_name = get_variant<std::string>(value);
} else if (name == "IconPixmap") {
icon_pixmap = this->extractPixBuf(value.gobj());
} else if (name == "OverlayIconName") {
overlay_icon_name = get_variant<std::string>(value);
} else if (name == "OverlayIconPixmap") {
// TODO: overlay_icon_pixmap
} else if (name == "AttentionIconName") {
attention_icon_name = get_variant<std::string>(value);
} else if (name == "AttentionIconPixmap") {
// TODO: attention_icon_pixmap
} else if (name == "AttentionMovieName") {
attention_movie_name = get_variant<std::string>(value);
} else if (name == "ToolTip") {
// TODO: tooltip
} else if (name == "IconThemePath") {
icon_theme_path = get_variant<std::string>(value);
if (!icon_theme_path.empty()) {
icon_theme->set_search_path({icon_theme_path});
}
} else if (name == "Menu") {
menu = get_variant<std::string>(value);
makeMenu();
} else if (name == "ItemIsMenu") {
item_is_menu = get_variant<bool>(value);
}
} catch (const Glib::Error& err) {
spdlog::warn("Failed to set tray item property: {}.{}, value = {}, err = {}",
id.empty() ? bus_name : id,
name,
value,
err.what());
} catch (const std::exception& err) {
spdlog::warn("Failed to set tray item property: {}.{}, value = {}, err = {}",
id.empty() ? bus_name : id,
name,
value,
err.what());
}
}
void Item::getUpdatedProperties() {
update_pending_ = false;
auto params = Glib::VariantContainerBase::create_tuple(
{Glib::Variant<Glib::ustring>::create(SNI_INTERFACE_NAME)});
proxy_->call("org.freedesktop.DBus.Properties.GetAll",
sigc::mem_fun(*this, &Item::processUpdatedProperties),
params);
};
void Item::processUpdatedProperties(Glib::RefPtr<Gio::AsyncResult>& _result) {
try {
auto result = proxy_->call_finish(_result);
// extract "a{sv}" from VariantContainerBase
Glib::Variant<std::map<Glib::ustring, Glib::VariantBase>> properties_variant;
result.get_child(properties_variant);
auto properties = properties_variant.get();
for (const auto& [name, value] : properties) {
Glib::VariantBase old_value;
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));
}
}
this->updateImage();
// this->event_box.set_tooltip_text(this->title);
} catch (const Glib::Error& err) {
spdlog::warn("Failed to update properties: {}", err.what());
} catch (const std::exception& err) {
spdlog::warn("Failed to update properties: {}", err.what());
}
}
void Item::onSignal(const Glib::ustring& sender_name, const Glib::ustring& signal_name,
const Glib::VariantContainerBase& arguments) {
spdlog::trace("Tray item '{}' got signal {}", id, signal_name);
if (!update_pending_ && signal_name.compare(0, 3, "New") == 0) {
/* Debounce signals and schedule update of all properties.
* Based on behavior of Plasma dataengine for StatusNotifierItem.
*/
update_pending_ = true;
Glib::signal_timeout().connect_once(sigc::mem_fun(*this, &Item::getUpdatedProperties),
UPDATE_DEBOUNCE_TIME);
}
}
static void pixbuf_data_deleter(const guint8* data) { g_free((void*)data); }
Glib::RefPtr<Gdk::Pixbuf> Item::extractPixBuf(GVariant* variant) {
GVariantIter* it;
g_variant_get(variant, "a(iiay)", &it); g_variant_get(variant, "a(iiay)", &it);
if (it == nullptr) { if (it == nullptr) {
return Glib::RefPtr<Gdk::Pixbuf>{}; return Glib::RefPtr<Gdk::Pixbuf>{};
} }
GVariant *val; GVariant* val;
gint lwidth = 0; gint lwidth = 0;
gint lheight = 0; gint lheight = 0;
gint width; gint width;
gint height; gint height;
guchar *array = nullptr; guchar* array = nullptr;
while (g_variant_iter_loop(it, "(ii@ay)", &width, &height, &val)) { while (g_variant_iter_loop(it, "(ii@ay)", &width, &height, &val)) {
if (width > 0 && height > 0 && val != nullptr && if (width > 0 && height > 0 && val != nullptr && width * height > lwidth * lheight) {
width * height > lwidth * lheight) {
auto size = g_variant_get_size(val); auto size = g_variant_get_size(val);
/* Sanity check */ /* Sanity check */
if (size == 4U * width * height) { if (size == 4U * width * height) {
@ -140,7 +221,7 @@ waybar::modules::SNI::Item::extractPixBuf(GVariant *variant) {
if (array != nullptr) { if (array != nullptr) {
g_free(array); g_free(array);
} }
array = static_cast<guchar *>(g_memdup(data, size)); array = static_cast<guchar*>(g_memdup(data, size));
lwidth = width; lwidth = width;
lheight = height; lheight = height;
} }
@ -157,14 +238,19 @@ waybar::modules::SNI::Item::extractPixBuf(GVariant *variant) {
array[i + 2] = array[i + 3]; array[i + 2] = array[i + 3];
array[i + 3] = alpha; array[i + 3] = alpha;
} }
return Gdk::Pixbuf::create_from_data(array, Gdk::Colorspace::COLORSPACE_RGB, return Gdk::Pixbuf::create_from_data(array,
true, 8, lwidth, lheight, 4 * lwidth); Gdk::Colorspace::COLORSPACE_RGB,
true,
8,
lwidth,
lheight,
4 * lwidth,
&pixbuf_data_deleter);
} }
return Glib::RefPtr<Gdk::Pixbuf>{}; return Glib::RefPtr<Gdk::Pixbuf>{};
} }
void waybar::modules::SNI::Item::updateImage() void Item::updateImage() {
{
image.set_from_icon_name("image-missing", Gtk::ICON_SIZE_MENU); image.set_from_icon_name("image-missing", Gtk::ICON_SIZE_MENU);
image.set_pixel_size(icon_size); image.set_pixel_size(icon_size);
if (!icon_name.empty()) { if (!icon_name.empty()) {
@ -179,87 +265,97 @@ void waybar::modules::SNI::Item::updateImage()
if (pixbuf->gobj() != nullptr) { if (pixbuf->gobj() != nullptr) {
// An icon specified by path and filename may be the wrong size for // An icon specified by path and filename may be the wrong size for
// the tray // the tray
pixbuf = pixbuf->scale_simple(icon_size, icon_size, pixbuf = pixbuf->scale_simple(icon_size, icon_size, Gdk::InterpType::INTERP_BILINEAR);
Gdk::InterpType::INTERP_BILINEAR);
image.set(pixbuf); image.set(pixbuf);
} }
} else { } else {
image.set(getIconByName(icon_name, icon_size)); image.set(getIconByName(icon_name, icon_size));
} }
} catch (Glib::Error &e) { } catch (Glib::Error& e) {
std::cerr << "Exception: " << e.what() << std::endl; spdlog::error("Item '{}': {}", id, static_cast<std::string>(e.what()));
} }
} else if (icon_pixmap) { } else if (icon_pixmap) {
// An icon extracted may be the wrong size for the tray // An icon extracted may be the wrong size for the tray
icon_pixmap = icon_pixmap->scale_simple(icon_size, icon_size, icon_pixmap = icon_pixmap->scale_simple(icon_size, icon_size, Gdk::InterpType::INTERP_BILINEAR);
Gdk::InterpType::INTERP_BILINEAR);
image.set(icon_pixmap); image.set(icon_pixmap);
} }
} }
Glib::RefPtr<Gdk::Pixbuf> Glib::RefPtr<Gdk::Pixbuf> Item::getIconByName(const std::string& name, int request_size) {
waybar::modules::SNI::Item::getIconByName(std::string name, int request_size) {
int tmp_size = 0; int tmp_size = 0;
Glib::RefPtr<Gtk::IconTheme> icon_theme = Gtk::IconTheme::get_default();
icon_theme->rescan_if_needed(); icon_theme->rescan_if_needed();
auto sizes = icon_theme->get_icon_sizes(name.c_str()); auto sizes = icon_theme->get_icon_sizes(name.c_str());
for (auto const &size : sizes) { for (auto const& size : sizes) {
// -1 == scalable // -1 == scalable
if (size == request_size || size == -1) { if (size == request_size || size == -1) {
tmp_size = request_size; tmp_size = request_size;
break; break;
} else if (size < request_size || (size > tmp_size && tmp_size > 0)) { } else if (size < request_size) {
tmp_size = size; tmp_size = size;
} else if (size > tmp_size && tmp_size > 0) {
tmp_size = request_size;
break;
} }
} }
if (tmp_size == 0) { if (tmp_size == 0) {
tmp_size = request_size; tmp_size = request_size;
} }
return icon_theme->load_icon(name.c_str(), tmp_size, if (!icon_theme_path.empty() &&
Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE); icon_theme->lookup_icon(
name.c_str(), tmp_size, Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE)) {
return icon_theme->load_icon(
name.c_str(), tmp_size, Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE);
}
Glib::RefPtr<Gtk::IconTheme> default_theme = Gtk::IconTheme::get_default();
default_theme->rescan_if_needed();
return default_theme->load_icon(
name.c_str(), tmp_size, Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE);
} }
void waybar::modules::SNI::Item::onMenuDestroyed(Item *self) void Item::onMenuDestroyed(Item* self, GObject* old_menu_pointer) {
{ if (old_menu_pointer == reinterpret_cast<GObject*>(self->dbus_menu)) {
self->gtk_menu = nullptr; self->gtk_menu = nullptr;
self->dbus_menu = nullptr; self->dbus_menu = nullptr;
}
} }
bool waybar::modules::SNI::Item::makeMenu(GdkEventButton *const &ev) void Item::makeMenu() {
{ if (gtk_menu == nullptr && !menu.empty()) {
if (gtk_menu == nullptr) { dbus_menu = dbusmenu_gtkmenu_new(bus_name.data(), menu.data());
if (!menu.empty()) { if (dbus_menu != nullptr) {
dbus_menu = dbusmenu_gtkmenu_new(bus_name.data(), menu.data()); g_object_ref_sink(G_OBJECT(dbus_menu));
if (dbus_menu != nullptr) { g_object_weak_ref(G_OBJECT(dbus_menu), (GWeakNotify)onMenuDestroyed, this);
g_object_ref_sink(G_OBJECT(dbus_menu)); gtk_menu = Glib::wrap(GTK_MENU(dbus_menu));
g_object_weak_ref(G_OBJECT(dbus_menu), (GWeakNotify)onMenuDestroyed, this); gtk_menu->attach_to_widget(event_box);
gtk_menu = Glib::wrap(GTK_MENU(dbus_menu));
gtk_menu->attach_to_widget(event_box);
}
} }
} }
if (gtk_menu != nullptr) { }
bool Item::handleClick(GdkEventButton* const& ev) {
auto parameters = Glib::VariantContainerBase::create_tuple(
{Glib::Variant<int>::create(ev->x), Glib::Variant<int>::create(ev->y)});
if ((ev->button == 1 && item_is_menu) || ev->button == 3) {
makeMenu();
if (gtk_menu != nullptr) {
#if GTK_CHECK_VERSION(3, 22, 0) #if GTK_CHECK_VERSION(3, 22, 0)
gtk_menu->popup_at_pointer(reinterpret_cast<GdkEvent*>(ev)); gtk_menu->popup_at_pointer(reinterpret_cast<GdkEvent*>(ev));
#else #else
gtk_menu->popup(ev->button, ev->time); gtk_menu->popup(ev->button, ev->time);
#endif #endif
return true;
} else {
proxy_->call("ContextMenu", parameters);
return true;
}
} else if (ev->button == 1) {
proxy_->call("Activate", parameters);
return true;
} else if (ev->button == 2) {
proxy_->call("SecondaryActivate", parameters);
return true; return true;
} }
return false; return false;
} }
bool waybar::modules::SNI::Item::handleClick(GdkEventButton *const &ev) { } // namespace waybar::modules::SNI
if ((ev->button == 1 && item_is_menu) || ev->button == 3) {
if (!makeMenu(ev)) {
return sn_item_call_context_menu_sync(proxy_, ev->x, ev->y, nullptr, nullptr);
}
} else if (ev->button == 1) {
return sn_item_call_activate_sync(proxy_, ev->x, ev->y, nullptr, nullptr);
} else if (ev->button == 2) {
return sn_item_call_secondary_activate_sync(proxy_, ev->x, ev->y,
nullptr, nullptr);
}
return false;
}

View File

@ -1,40 +1,45 @@
#include "modules/sni/tray.hpp" #include "modules/sni/tray.hpp"
#include <spdlog/spdlog.h>
#include <iostream> namespace waybar::modules::SNI {
waybar::modules::SNI::Tray::Tray(const std::string& id, const Json::Value &config) Tray::Tray(const std::string& id, const Bar& bar, const Json::Value& config)
: config_(config), watcher_(), host_(nb_hosts_, config, : AModule(config, "tray", id),
std::bind(&Tray::onAdd, this, std::placeholders::_1), box_(bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0),
std::bind(&Tray::onRemove, this, std::placeholders::_1)) watcher_(nb_hosts_),
{ host_(nb_hosts_, config, std::bind(&Tray::onAdd, this, std::placeholders::_1),
std::cout << "Tray is in beta, so there may be bugs or even be unusable." << std::endl; std::bind(&Tray::onRemove, this, std::placeholders::_1)) {
spdlog::warn(
"For a functional tray you must have libappindicator-* installed and export "
"XDG_CURRENT_DESKTOP=Unity");
box_.set_name("tray");
event_box_.add(box_);
if (!id.empty()) {
box_.get_style_context()->add_class(id);
}
if (config_["spacing"].isUInt()) { if (config_["spacing"].isUInt()) {
box_.set_spacing(config_["spacing"].asUInt()); box_.set_spacing(config_["spacing"].asUInt());
} }
nb_hosts_ += 1; nb_hosts_ += 1;
dp.emit();
} }
void waybar::modules::SNI::Tray::onAdd(std::unique_ptr<Item>& item) void Tray::onAdd(std::unique_ptr<Item>& item) {
{
box_.pack_start(item->event_box); box_.pack_start(item->event_box);
dp.emit(); dp.emit();
} }
void waybar::modules::SNI::Tray::onRemove(std::unique_ptr<Item>& item) void Tray::onRemove(std::unique_ptr<Item>& item) {
{
box_.remove(item->event_box); box_.remove(item->event_box);
dp.emit(); dp.emit();
} }
auto waybar::modules::SNI::Tray::update() -> void { auto Tray::update() -> void {
if (box_.get_children().size() > 0) { if (box_.get_children().empty()) {
box_.set_name("tray"); box_.hide();
box_.show_all();
} else { } else {
box_.set_name(""); box_.show_all();
} }
} }
waybar::modules::SNI::Tray::operator Gtk::Widget &() { } // namespace waybar::modules::SNI
return box_;
}

View File

@ -1,37 +1,52 @@
#include "modules/sni/watcher.hpp" #include "modules/sni/watcher.hpp"
#include <spdlog/spdlog.h>
#include <iostream>
using namespace waybar::modules::SNI; using namespace waybar::modules::SNI;
Watcher::Watcher() Watcher::Watcher(std::size_t id)
: bus_name_id_(Gio::DBus::own_name(Gio::DBus::BusType::BUS_TYPE_SESSION, : bus_name_id_(Gio::DBus::own_name(Gio::DBus::BusType::BUS_TYPE_SESSION,
"org.kde.StatusNotifierWatcher", sigc::mem_fun(*this, &Watcher::busAcquired), "org.kde.StatusNotifierWatcher",
Gio::DBus::SlotNameAcquired(), Gio::DBus::SlotNameLost(), sigc::mem_fun(*this, &Watcher::busAcquired),
Gio::DBus::BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT | Gio::DBus::BUS_NAME_OWNER_FLAGS_REPLACE)), Gio::DBus::SlotNameAcquired(), Gio::DBus::SlotNameLost(),
watcher_(sn_watcher_skeleton_new()) Gio::DBus::BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT |
{ Gio::DBus::BUS_NAME_OWNER_FLAGS_REPLACE)),
watcher_id_(id),
watcher_(sn_watcher_skeleton_new()) {}
Watcher::~Watcher() {
if (hosts_ != nullptr) {
g_slist_free_full(hosts_, gfWatchFree);
hosts_ = nullptr;
}
if (items_ != nullptr) {
g_slist_free_full(items_, gfWatchFree);
items_ = nullptr;
}
auto iface = G_DBUS_INTERFACE_SKELETON(watcher_);
g_dbus_interface_skeleton_unexport(iface);
} }
void Watcher::busAcquired(const Glib::RefPtr<Gio::DBus::Connection>& conn, Glib::ustring name) void Watcher::busAcquired(const Glib::RefPtr<Gio::DBus::Connection>& conn, Glib::ustring name) {
{
GError* error = nullptr; GError* error = nullptr;
g_dbus_interface_skeleton_export(G_DBUS_INTERFACE_SKELETON(watcher_), g_dbus_interface_skeleton_export(
conn->gobj(), "/StatusNotifierWatcher", &error); G_DBUS_INTERFACE_SKELETON(watcher_), conn->gobj(), "/StatusNotifierWatcher", &error);
if (error != nullptr) { if (error != nullptr) {
std::cerr << error->message << std::endl; // Don't print an error when a watcher is already present
if (error->code != 2) {
spdlog::error("Watcher {}: {}", watcher_id_, error->message);
}
g_error_free(error); g_error_free(error);
return; return;
} }
g_signal_connect_swapped(watcher_, "handle-register-item", g_signal_connect_swapped(
G_CALLBACK(&Watcher::handleRegisterItem), this); watcher_, "handle-register-item", G_CALLBACK(&Watcher::handleRegisterItem), this);
g_signal_connect_swapped(watcher_, "handle-register-host", g_signal_connect_swapped(
G_CALLBACK(&Watcher::handleRegisterHost), this); watcher_, "handle-register-host", G_CALLBACK(&Watcher::handleRegisterHost), this);
} }
gboolean Watcher::handleRegisterHost(Watcher* obj, gboolean Watcher::handleRegisterHost(Watcher* obj, GDBusMethodInvocation* invocation,
GDBusMethodInvocation* invocation, const gchar* service) const gchar* service) {
{
const gchar* bus_name = service; const gchar* bus_name = service;
const gchar* object_path = "/StatusNotifierHost"; const gchar* object_path = "/StatusNotifierHost";
@ -40,15 +55,22 @@ gboolean Watcher::handleRegisterHost(Watcher* obj,
object_path = service; object_path = service;
} }
if (g_dbus_is_name(bus_name) == FALSE) { if (g_dbus_is_name(bus_name) == FALSE) {
g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR, g_dbus_method_invocation_return_error(invocation,
G_DBUS_ERROR_INVALID_ARGS, "D-Bus bus name '%s' is not valid", bus_name); G_DBUS_ERROR,
G_DBUS_ERROR_INVALID_ARGS,
"D-Bus bus name '%s' is not valid",
bus_name);
return TRUE; return TRUE;
} }
auto watch = gfWatchFind(obj->hosts_, bus_name, object_path); auto watch = gfWatchFind(obj->hosts_, bus_name, object_path);
if (watch != nullptr) { if (watch != nullptr) {
g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR, g_dbus_method_invocation_return_error(
G_DBUS_ERROR_INVALID_ARGS, "Status Notifier Host with bus name '%s' and object path '%s' is already registered", invocation,
bus_name, object_path); G_DBUS_ERROR,
G_DBUS_ERROR_INVALID_ARGS,
"Status Notifier Host with bus name '%s' and object path '%s' is already registered",
bus_name,
object_path);
return TRUE; return TRUE;
} }
watch = gfWatchNew(GF_WATCH_TYPE_HOST, service, bus_name, object_path, obj); watch = gfWatchNew(GF_WATCH_TYPE_HOST, service, bus_name, object_path, obj);
@ -61,9 +83,8 @@ gboolean Watcher::handleRegisterHost(Watcher* obj,
return TRUE; return TRUE;
} }
gboolean Watcher::handleRegisterItem(Watcher* obj, gboolean Watcher::handleRegisterItem(Watcher* obj, GDBusMethodInvocation* invocation,
GDBusMethodInvocation* invocation, const gchar* service) const gchar* service) {
{
const gchar* bus_name = service; const gchar* bus_name = service;
const gchar* object_path = "/StatusNotifierItem"; const gchar* object_path = "/StatusNotifierItem";
@ -72,14 +93,18 @@ gboolean Watcher::handleRegisterItem(Watcher* obj,
object_path = service; object_path = service;
} }
if (g_dbus_is_name(bus_name) == FALSE) { if (g_dbus_is_name(bus_name) == FALSE) {
g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR, g_dbus_method_invocation_return_error(invocation,
G_DBUS_ERROR_INVALID_ARGS, "D-Bus bus name '%s' is not valid", bus_name); G_DBUS_ERROR,
G_DBUS_ERROR_INVALID_ARGS,
"D-Bus bus name '%s' is not valid",
bus_name);
return TRUE; return TRUE;
} }
auto watch = gfWatchFind(obj->items_, bus_name, object_path); auto watch = gfWatchFind(obj->items_, bus_name, object_path);
if (watch != nullptr) { if (watch != nullptr) {
g_warning("Status Notifier Item with bus name '%s' and object path '%s' is already registered", g_warning("Status Notifier Item with bus name '%s' and object path '%s' is already registered",
bus_name, object_path); bus_name,
object_path);
sn_watcher_complete_register_item(obj->watcher_, invocation); sn_watcher_complete_register_item(obj->watcher_, invocation);
return TRUE; return TRUE;
} }
@ -94,40 +119,54 @@ gboolean Watcher::handleRegisterItem(Watcher* obj,
} }
Watcher::GfWatch* Watcher::gfWatchFind(GSList* list, const gchar* bus_name, Watcher::GfWatch* Watcher::gfWatchFind(GSList* list, const gchar* bus_name,
const gchar* object_path) const gchar* object_path) {
{ for (GSList* l = list; l != nullptr; l = g_slist_next(l)) {
for (GSList* l = list; l != nullptr; l = g_slist_next (l)) { auto watch = static_cast<GfWatch*>(l->data);
GfWatch* watch = static_cast<GfWatch*>(l->data); if (g_strcmp0(watch->bus_name, bus_name) == 0 &&
if (g_strcmp0 (watch->bus_name, bus_name) == 0 g_strcmp0(watch->object_path, object_path) == 0) {
&& g_strcmp0 (watch->object_path, object_path) == 0) {
return watch; return watch;
} }
} }
return nullptr; return nullptr;
} }
Watcher::GfWatch* Watcher::gfWatchNew(GfWatchType type, const gchar* service, void Watcher::gfWatchFree(gpointer data) {
const gchar* bus_name, const gchar* object_path, Watcher* watcher) auto watch = static_cast<GfWatch*>(data);
{
if (watch->watch_id > 0) {
g_bus_unwatch_name(watch->watch_id);
}
g_free(watch->service);
g_free(watch->bus_name);
g_free(watch->object_path);
g_free(watch);
}
Watcher::GfWatch* Watcher::gfWatchNew(GfWatchType type, const gchar* service, const gchar* bus_name,
const gchar* object_path, Watcher* watcher) {
GfWatch* watch = g_new0(GfWatch, 1); GfWatch* watch = g_new0(GfWatch, 1);
watch->type = type; watch->type = type;
watch->watcher = watcher; watch->watcher = watcher;
watch->service = g_strdup(service); watch->service = g_strdup(service);
watch->bus_name = g_strdup(bus_name); watch->bus_name = g_strdup(bus_name);
watch->object_path = g_strdup(object_path); watch->object_path = g_strdup(object_path);
watch->watch_id = g_bus_watch_name(G_BUS_TYPE_SESSION, bus_name, watch->watch_id = g_bus_watch_name(G_BUS_TYPE_SESSION,
G_BUS_NAME_WATCHER_FLAGS_NONE, nullptr, &Watcher::nameVanished, watch, bus_name,
nullptr); G_BUS_NAME_WATCHER_FLAGS_NONE,
nullptr,
&Watcher::nameVanished,
watch,
nullptr);
return watch; return watch;
} }
void Watcher::nameVanished(GDBusConnection* connection, const char* name, void Watcher::nameVanished(GDBusConnection* connection, const char* name, gpointer data) {
gpointer data) auto watch = static_cast<GfWatch*>(data);
{
auto watch = static_cast<GfWatch *>(data);
if (watch->type == GF_WATCH_TYPE_HOST) { if (watch->type == GF_WATCH_TYPE_HOST) {
watch->watcher->hosts_ = g_slist_remove(watch->watcher->hosts_, watch); watch->watcher->hosts_ = g_slist_remove(watch->watcher->hosts_, watch);
if (watch->watcher->hosts_ == nullptr) { if (watch->watcher->hosts_ == nullptr) {
sn_watcher_set_is_host_registered(watch->watcher->watcher_, FALSE); sn_watcher_set_is_host_registered(watch->watcher->watcher_, FALSE);
sn_watcher_emit_host_registered(watch->watcher->watcher_); sn_watcher_emit_host_registered(watch->watcher->watcher_);
} }
@ -140,17 +179,16 @@ void Watcher::nameVanished(GDBusConnection* connection, const char* name,
} }
} }
void Watcher::updateRegisteredItems(SnWatcher* obj) void Watcher::updateRegisteredItems(SnWatcher* obj) {
{
GVariantBuilder builder; GVariantBuilder builder;
g_variant_builder_init(&builder, G_VARIANT_TYPE("as")); g_variant_builder_init(&builder, G_VARIANT_TYPE("as"));
for (GSList* l = items_; l != nullptr; l = g_slist_next(l)) { for (GSList* l = items_; l != nullptr; l = g_slist_next(l)) {
GfWatch* watch = static_cast<GfWatch*>(l->data); auto watch = static_cast<GfWatch*>(l->data);
gchar* item = g_strdup_printf("%s%s", watch->bus_name, watch->object_path); gchar* item = g_strdup_printf("%s%s", watch->bus_name, watch->object_path);
g_variant_builder_add(&builder, "s", item); g_variant_builder_add(&builder, "s", item);
g_free(item); g_free(item);
} }
GVariant* variant = g_variant_builder_end(&builder); GVariant* variant = g_variant_builder_end(&builder);
const gchar** items = g_variant_get_strv(variant, nullptr); const gchar** items = g_variant_get_strv(variant, nullptr);
sn_watcher_set_registered_items(obj, items); sn_watcher_set_registered_items(obj, items);
g_variant_unref(variant); g_variant_unref(variant);

View File

@ -1,33 +1,38 @@
#include "modules/sway/ipc/client.hpp" #include "modules/sway/ipc/client.hpp"
#include <fcntl.h>
waybar::modules::sway::Ipc::Ipc() namespace waybar::modules::sway {
{
Ipc::Ipc() {
const std::string& socketPath = getSocketPath(); const std::string& socketPath = getSocketPath();
fd_ = open(socketPath); fd_ = open(socketPath);
fd_event_ = open(socketPath); fd_event_ = open(socketPath);
} }
waybar::modules::sway::Ipc::~Ipc() Ipc::~Ipc() {
{
// To fail the IPC header // To fail the IPC header
write(fd_, "close-sway-ipc", 14); write(fd_, "close-sway-ipc", 14);
write(fd_event_, "close-sway-ipc", 14); write(fd_event_, "close-sway-ipc", 14);
if (fd_ > 0) {
close(fd_); close(fd_);
close(fd_event_); fd_ = -1;
}
if (fd_event_ > 0) {
close(fd_event_);
fd_event_ = -1;
}
} }
const std::string waybar::modules::sway::Ipc::getSocketPath() const const std::string Ipc::getSocketPath() const {
{ const char* env = getenv("SWAYSOCK");
const char *env = getenv("SWAYSOCK");
if (env != nullptr) { if (env != nullptr) {
return std::string(env); return std::string(env);
} }
std::string str; std::string str;
{ {
std::string str_buf; std::string str_buf;
FILE* in; FILE* in;
char buf[512] = { 0 }; char buf[512] = {0};
if ((in = popen("sway --get-socketpath 2>/dev/null", "r")) == nullptr) { if ((in = popen("sway --get-socketpath 2>/dev/null", "r")) == nullptr) {
throw std::runtime_error("Failed to get socket path"); throw std::runtime_error("Failed to get socket path");
} }
@ -36,6 +41,9 @@ const std::string waybar::modules::sway::Ipc::getSocketPath() const
} }
pclose(in); pclose(in);
str = str_buf; str = str_buf;
if (str.empty()) {
throw std::runtime_error("Socket path is empty");
}
} }
if (str.back() == '\n') { if (str.back() == '\n') {
str.pop_back(); str.pop_back();
@ -43,39 +51,41 @@ const std::string waybar::modules::sway::Ipc::getSocketPath() const
return str; return str;
} }
int waybar::modules::sway::Ipc::open(const std::string& socketPath) const int Ipc::open(const std::string& socketPath) const {
{ int32_t fd = socket(AF_UNIX, SOCK_STREAM, 0);
struct sockaddr_un addr = {0}; if (fd == -1) {
int fd = -1;
if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
throw std::runtime_error("Unable to open Unix socket"); throw std::runtime_error("Unable to open Unix socket");
} }
(void)fcntl(fd, F_SETFD, FD_CLOEXEC);
struct sockaddr_un addr;
memset(&addr, 0, sizeof(struct sockaddr_un));
addr.sun_family = AF_UNIX; addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, socketPath.c_str(), sizeof(addr.sun_path) - 1); strncpy(addr.sun_path, socketPath.c_str(), sizeof(addr.sun_path) - 1);
addr.sun_path[sizeof(addr.sun_path) - 1] = 0; addr.sun_path[sizeof(addr.sun_path) - 1] = 0;
int l = sizeof(struct sockaddr_un); int l = sizeof(struct sockaddr_un);
if (::connect(fd, reinterpret_cast<struct sockaddr *>(&addr), l) == -1) { if (::connect(fd, reinterpret_cast<struct sockaddr*>(&addr), l) == -1) {
throw std::runtime_error("Unable to connect to Sway"); throw std::runtime_error("Unable to connect to Sway");
} }
return fd; return fd;
} }
struct waybar::modules::sway::Ipc::ipc_response struct Ipc::ipc_response Ipc::recv(int fd) {
waybar::modules::sway::Ipc::recv(int fd) const
{
std::string header; std::string header;
header.reserve(ipc_header_size_); header.resize(ipc_header_size_);
auto data32 = reinterpret_cast<uint32_t *>(header.data() + ipc_magic_.size()); auto data32 = reinterpret_cast<uint32_t*>(header.data() + ipc_magic_.size());
size_t total = 0; size_t total = 0;
while (total < ipc_header_size_) { while (total < ipc_header_size_) {
auto res = ::recv(fd, header.data() + total, ipc_header_size_ - total, 0); auto res = ::recv(fd, header.data() + total, ipc_header_size_ - total, 0);
if (fd_event_ == -1 || fd_ == -1) {
// IPC is closed so just return an empty response
return {0, 0, ""};
}
if (res <= 0) { if (res <= 0) {
throw std::runtime_error("Unable to receive IPC header"); throw std::runtime_error("Unable to receive IPC header");
} }
total += res; total += res;
} }
auto magic = std::string(header.data(), header.data() + ipc_magic_.size()); auto magic = std::string(header.data(), header.data() + ipc_magic_.size());
if (magic != ipc_magic_) { if (magic != ipc_magic_) {
throw std::runtime_error("Invalid IPC magic"); throw std::runtime_error("Invalid IPC magic");
@ -83,25 +93,24 @@ struct waybar::modules::sway::Ipc::ipc_response
total = 0; total = 0;
std::string payload; std::string payload;
payload.reserve(data32[0] + 1); payload.resize(data32[0]);
while (total < data32[0]) { while (total < data32[0]) {
auto res = ::recv(fd, payload.data() + total, data32[0] - total, 0); auto res = ::recv(fd, payload.data() + total, data32[0] - total, 0);
if (res < 0) { if (res < 0) {
if (errno == EINTR || errno == EAGAIN) {
continue;
}
throw std::runtime_error("Unable to receive IPC payload"); throw std::runtime_error("Unable to receive IPC payload");
} }
total += res; total += res;
} }
payload[data32[0]] = 0; return {data32[0], data32[1], &payload.front()};
return { data32[0], data32[1], &payload.front() };
} }
struct waybar::modules::sway::Ipc::ipc_response struct Ipc::ipc_response Ipc::send(int fd, uint32_t type, const std::string& payload) {
waybar::modules::sway::Ipc::send(int fd, uint32_t type,
const std::string& payload) const
{
std::string header; std::string header;
header.reserve(ipc_header_size_); header.resize(ipc_header_size_);
auto data32 = reinterpret_cast<uint32_t *>(header.data() + ipc_magic_.size()); auto data32 = reinterpret_cast<uint32_t*>(header.data() + ipc_magic_.size());
memcpy(header.data(), ipc_magic_.c_str(), ipc_magic_.size()); memcpy(header.data(), ipc_magic_.c_str(), ipc_magic_.size());
data32[0] = payload.size(); data32[0] = payload.size();
data32[1] = type; data32[1] = type;
@ -112,26 +121,25 @@ struct waybar::modules::sway::Ipc::ipc_response
if (::send(fd, payload.c_str(), payload.size(), 0) == -1) { if (::send(fd, payload.c_str(), payload.size(), 0) == -1) {
throw std::runtime_error("Unable to send IPC payload"); throw std::runtime_error("Unable to send IPC payload");
} }
return recv(fd); return Ipc::recv(fd);
} }
struct waybar::modules::sway::Ipc::ipc_response void Ipc::sendCmd(uint32_t type, const std::string& payload) {
waybar::modules::sway::Ipc::sendCmd(uint32_t type, std::lock_guard<std::mutex> lock(mutex_);
const std::string& payload) const const auto res = Ipc::send(fd_, type, payload);
{ signal_cmd.emit(res);
return send(fd_, type, payload);
} }
void waybar::modules::sway::Ipc::subscribe(const std::string& payload) const void Ipc::subscribe(const std::string& payload) {
{ auto res = Ipc::send(fd_event_, IPC_SUBSCRIBE, payload);
auto res = send(fd_event_, IPC_SUBSCRIBE, payload);
if (res.payload != "{\"success\": true}") { if (res.payload != "{\"success\": true}") {
throw std::runtime_error("Unable to subscribe ipc event"); throw std::runtime_error("Unable to subscribe ipc event");
} }
} }
struct waybar::modules::sway::Ipc::ipc_response void Ipc::handleEvent() {
waybar::modules::sway::Ipc::handleEvent() const const auto res = Ipc::recv(fd_event_);
{ signal_event.emit(res);
return recv(fd_event_);
} }
} // namespace waybar::modules::sway

View File

@ -1,38 +1,47 @@
#include "modules/sway/mode.hpp" #include "modules/sway/mode.hpp"
#include <spdlog/spdlog.h>
waybar::modules::sway::Mode::Mode(const std::string& id, const Bar& bar, const Json::Value& config) namespace waybar::modules::sway {
: ALabel(config, "{}"), bar_(bar)
{ Mode::Mode(const std::string& id, const Json::Value& config)
label_.set_name("mode"); : ALabel(config, "mode", id, "{}", 0, true) {
if (!id.empty()) { ipc_.subscribe(R"(["mode"])");
label_.get_style_context()->add_class(id); ipc_.signal_event.connect(sigc::mem_fun(*this, &Mode::onEvent));
}
ipc_.subscribe("[ \"mode\" ]");
// Launch worker // Launch worker
worker(); worker();
dp.emit(); dp.emit();
} }
void waybar::modules::sway::Mode::worker() void Mode::onEvent(const struct Ipc::ipc_response& res) {
{ try {
std::lock_guard<std::mutex> lock(mutex_);
auto payload = parser_.parse(res.payload);
if (payload["change"] != "default") {
if (payload["pango_markup"].asBool()) {
mode_ = payload["change"].asString();
} else {
mode_ = Glib::Markup::escape_text(payload["change"].asString());
}
} else {
mode_.clear();
}
dp.emit();
} catch (const std::exception& e) {
spdlog::error("Mode: {}", e.what());
}
}
void Mode::worker() {
thread_ = [this] { thread_ = [this] {
try { try {
auto res = ipc_.handleEvent(); ipc_.handleEvent();
auto parsed = parser_.parse(res.payload);
if (parsed["change"] != "default") {
mode_ = parsed["change"].asString();
} else {
mode_.clear();
}
dp.emit();
} catch (const std::exception& e) { } catch (const std::exception& e) {
std::cerr << "Mode: " << e.what() << std::endl; spdlog::error("Mode: {}", e.what());
} }
}; };
} }
auto waybar::modules::sway::Mode::update() -> void auto Mode::update() -> void {
{
if (mode_.empty()) { if (mode_.empty()) {
event_box_.hide(); event_box_.hide();
} else { } else {
@ -43,3 +52,5 @@ auto waybar::modules::sway::Mode::update() -> void
event_box_.show(); event_box_.show();
} }
} }
} // namespace waybar::modules::sway

View File

@ -1,83 +1,107 @@
#include "modules/sway/window.hpp" #include "modules/sway/window.hpp"
#include <spdlog/spdlog.h>
waybar::modules::sway::Window::Window(const std::string& id, const Bar &bar, const Json::Value& config) namespace waybar::modules::sway {
: ALabel(config, "{}"), bar_(bar), windowId_(-1)
{ Window::Window(const std::string& id, const Bar& bar, const Json::Value& config)
label_.set_name("window"); : ALabel(config, "window", id, "{}", 0, true), bar_(bar), windowId_(-1) {
if (!id.empty()) { ipc_.subscribe(R"(["window","workspace"])");
label_.get_style_context()->add_class(id); ipc_.signal_event.connect(sigc::mem_fun(*this, &Window::onEvent));
} ipc_.signal_cmd.connect(sigc::mem_fun(*this, &Window::onCmd));
if (label_.get_max_width_chars() == -1) { // Get Initial focused window
label_.set_hexpand(true); getTree();
label_.set_ellipsize(Pango::EllipsizeMode::ELLIPSIZE_END);
}
ipc_.subscribe("[\"window\",\"workspace\"]");
getFocusedWindow();
// Launch worker // Launch worker
worker(); worker();
} }
void waybar::modules::sway::Window::worker() void Window::onEvent(const struct Ipc::ipc_response& res) { getTree(); }
{
void Window::onCmd(const struct Ipc::ipc_response& res) {
try {
std::lock_guard<std::mutex> lock(mutex_);
auto payload = parser_.parse(res.payload);
auto output = payload["ouput"].isString() ? payload["output"].asString() : "";
std::tie(app_nb_, windowId_, window_, app_id_) = getFocusedNode(payload["nodes"], output);
dp.emit();
} catch (const std::exception& e) {
spdlog::error("Window: {}", e.what());
}
}
void Window::worker() {
thread_ = [this] { thread_ = [this] {
try { try {
auto res = ipc_.handleEvent(); ipc_.handleEvent();
auto parsed = parser_.parse(res.payload);
// Check for waybar prevents flicker when hovering window module
if ((parsed["change"] == "focus" || parsed["change"] == "title")
&& parsed["container"]["focused"].asBool()
&& parsed["container"]["name"].asString() != "waybar") {
window_ = Glib::Markup::escape_text(parsed["container"]["name"].asString());
windowId_ = parsed["container"]["id"].asInt();
dp.emit();
} else if ((parsed["change"] == "close"
&& parsed["container"]["focused"].asBool()
&& windowId_ == parsed["container"]["id"].asInt())
|| (parsed["change"] == "focus" && parsed["current"]["focus"].isArray()
&& parsed["current"]["focus"].empty())) {
window_.clear();
windowId_ = -1;
dp.emit();
}
} catch (const std::exception& e) { } catch (const std::exception& e) {
std::cerr << "Window: " << e.what() << std::endl; spdlog::error("Window: {}", e.what());
} }
}; };
} }
auto waybar::modules::sway::Window::update() -> void auto Window::update() -> void {
{ if (!old_app_id_.empty()) {
bar_.window.get_style_context()->remove_class(old_app_id_);
}
if (app_nb_ == 0) {
bar_.window.get_style_context()->remove_class("solo");
if (!bar_.window.get_style_context()->has_class("empty")) {
bar_.window.get_style_context()->add_class("empty");
}
} else if (app_nb_ == 1) {
bar_.window.get_style_context()->remove_class("empty");
if (!bar_.window.get_style_context()->has_class("solo")) {
bar_.window.get_style_context()->add_class("solo");
}
if (!app_id_.empty() && !bar_.window.get_style_context()->has_class(app_id_)) {
bar_.window.get_style_context()->add_class(app_id_);
old_app_id_ = app_id_;
}
} else {
bar_.window.get_style_context()->remove_class("solo");
bar_.window.get_style_context()->remove_class("empty");
}
label_.set_markup(fmt::format(format_, window_)); label_.set_markup(fmt::format(format_, window_));
if (tooltipEnabled()) { if (tooltipEnabled()) {
label_.set_tooltip_text(window_); label_.set_tooltip_text(window_);
} }
} }
std::tuple<int, std::string> waybar::modules::sway::Window::getFocusedNode( std::tuple<std::size_t, int, std::string, std::string> Window::getFocusedNode(
Json::Value nodes) const Json::Value& nodes, std::string& output) {
{
for (auto const& node : nodes) { for (auto const& node : nodes) {
if (node["focused"].asBool() && node["type"] == "con") { if (node["output"].isString()) {
return { node["id"].asInt(), node["name"].asString() }; output = node["output"].asString();
} }
auto [id, name] = getFocusedNode(node["nodes"]); if (node["focused"].asBool() && (node["type"] == "con" || node["type"] == "floating_con")) {
if ((!config_["all-outputs"].asBool() && output == bar_.output->name) ||
config_["all-outputs"].asBool()) {
auto app_id = node["app_id"].isString() ? node["app_id"].asString()
: node["window_properties"]["instance"].asString();
return {nodes.size(),
node["id"].asInt(),
Glib::Markup::escape_text(node["name"].asString()),
app_id};
}
}
auto [nb, id, name, app_id] = getFocusedNode(node["nodes"], output);
if (id > -1 && !name.empty()) { if (id > -1 && !name.empty()) {
return { id, name }; return {nb, id, name, app_id};
}
// Search for floating node
std::tie(nb, id, name, app_id) = getFocusedNode(node["floating_nodes"], output);
if (id > -1 && !name.empty()) {
return {nb, id, name, app_id};
} }
} }
return { -1, std::string() }; return {0, -1, "", ""};
} }
void waybar::modules::sway::Window::getFocusedWindow() void Window::getTree() {
{
try { try {
auto res = ipc_.sendCmd(IPC_GET_TREE); ipc_.sendCmd(IPC_GET_TREE);
auto parsed = parser_.parse(res.payload); } catch (const std::exception& e) {
auto [id, name] = getFocusedNode(parsed["nodes"]); spdlog::error("Window: {}", e.what());
windowId_ = id;
window_ = name;
Glib::signal_idle().connect_once(sigc::mem_fun(*this, &Window::update));
} catch (const std::exception &e) {
std::cerr << e.what() << std::endl;
} }
} }
} // namespace waybar::modules::sway

View File

@ -1,144 +1,207 @@
#include "modules/sway/workspaces.hpp" #include "modules/sway/workspaces.hpp"
#include <spdlog/spdlog.h>
waybar::modules::sway::Workspaces::Workspaces(const std::string& id, const Bar& bar, namespace waybar::modules::sway {
const Json::Value& config)
: bar_(bar), config_(config), scrolling_(false) Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value &config)
{ : AModule(config, "workspaces", id, false, !config["disable-scroll"].asBool()),
bar_(bar),
box_(bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0) {
box_.set_name("workspaces"); box_.set_name("workspaces");
if (!id.empty()) { if (!id.empty()) {
box_.get_style_context()->add_class(id); box_.get_style_context()->add_class(id);
} }
ipc_.subscribe("[ \"workspace\" ]"); event_box_.add(box_);
ipc_.subscribe(R"(["workspace"])");
ipc_.signal_event.connect(sigc::mem_fun(*this, &Workspaces::onEvent));
ipc_.signal_cmd.connect(sigc::mem_fun(*this, &Workspaces::onCmd));
ipc_.sendCmd(IPC_GET_WORKSPACES);
if (config["enable-bar-scroll"].asBool()) {
auto &window = const_cast<Bar &>(bar_).window;
window.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);
window.signal_scroll_event().connect(sigc::mem_fun(*this, &Workspaces::handleScroll));
}
// Launch worker // Launch worker
worker(); worker();
} }
void waybar::modules::sway::Workspaces::worker() void Workspaces::onEvent(const struct Ipc::ipc_response &res) {
{ try {
thread_ = [this] { ipc_.sendCmd(IPC_GET_WORKSPACES);
} catch (const std::exception &e) {
spdlog::error("Workspaces: {}", e.what());
}
}
void Workspaces::onCmd(const struct Ipc::ipc_response &res) {
if (res.type == IPC_GET_WORKSPACES) {
try { try {
if (!workspaces_.empty()) {
ipc_.handleEvent();
}
{ {
std::lock_guard<std::mutex> lock(mutex_); std::lock_guard<std::mutex> lock(mutex_);
auto res = ipc_.sendCmd(IPC_GET_WORKSPACES); auto payload = parser_.parse(res.payload);
if (thread_.isRunning()) { workspaces_.clear();
workspaces_ = parser_.parse(res.payload); std::copy_if(payload.begin(),
payload.end(),
std::back_inserter(workspaces_),
[&](const auto &workspace) {
return !config_["all-outputs"].asBool()
? workspace["output"].asString() == bar_.output->name
: true;
});
// adding persistant workspaces (as per the config file)
if (config_["persistant_workspaces"].isObject()) {
const Json::Value & p_workspaces = config_["persistant_workspaces"];
const std::vector<std::string> p_workspaces_names = p_workspaces.getMemberNames();
for (const std::string &p_w_name : p_workspaces_names) {
const Json::Value &p_w = p_workspaces[p_w_name];
auto it =
std::find_if(payload.begin(), payload.end(), [&p_w_name](const Json::Value &node) {
return node["name"].asString() == p_w_name;
});
if (it != payload.end()) {
continue; // already displayed by some bar
}
if (p_w.isArray() && !p_w.empty()) {
// Adding to target outputs
for (const Json::Value &output : p_w) {
if (output.asString() == bar_.output->name) {
Json::Value v;
v["name"] = p_w_name;
v["target_output"] = bar_.output->name;
workspaces_.emplace_back(std::move(v));
break;
}
}
} else {
// Adding to all outputs
Json::Value v;
v["name"] = p_w_name;
v["target_output"] = "";
workspaces_.emplace_back(std::move(v));
}
}
std::sort(workspaces_.begin(),
workspaces_.end(),
[](const Json::Value &lhs, const Json::Value &rhs) {
return lhs["name"].asString() < rhs["name"].asString();
});
} }
} }
dp.emit(); dp.emit();
} catch (const std::exception& e) { } catch (const std::exception &e) {
std::cerr << "Workspaces: " << e.what() << std::endl; spdlog::error("Workspaces: {}", e.what());
}
}
}
void Workspaces::worker() {
thread_ = [this] {
try {
ipc_.handleEvent();
} catch (const std::exception &e) {
spdlog::error("Workspaces: {}", e.what());
} }
}; };
} }
auto waybar::modules::sway::Workspaces::update() -> void bool Workspaces::filterButtons() {
{
bool needReorder = false; bool needReorder = false;
std::lock_guard<std::mutex> lock(mutex_);
for (auto it = buttons_.begin(); it != buttons_.end();) { for (auto it = buttons_.begin(); it != buttons_.end();) {
auto ws = std::find_if(workspaces_.begin(), workspaces_.end(), auto ws = std::find_if(workspaces_.begin(), workspaces_.end(), [it](const auto &node) {
[it](auto node) -> bool { return node["name"].asString() == it->first; }); return node["name"].asString() == it->first;
});
if (ws == workspaces_.end() || if (ws == workspaces_.end() ||
(!config_["all-outputs"].asBool() && (*ws)["output"].asString() != bar_.output_name)) { (!config_["all-outputs"].asBool() && (*ws)["output"].asString() != bar_.output->name)) {
it = buttons_.erase(it); it = buttons_.erase(it);
needReorder = true; needReorder = true;
} else { } else {
++it; ++it;
} }
} }
for (auto const& node : workspaces_) { return needReorder;
if (!config_["all-outputs"].asBool() }
&& bar_.output_name != node["output"].asString()) {
continue; auto Workspaces::update() -> void {
} std::lock_guard<std::mutex> lock(mutex_);
auto it = buttons_.find(node["name"].asString()); bool needReorder = filterButtons();
if (it == buttons_.end()) { for (auto it = workspaces_.begin(); it != workspaces_.end(); ++it) {
addWorkspace(node); auto bit = buttons_.find((*it)["name"].asString());
if (bit == buttons_.end()) {
needReorder = true; needReorder = true;
} else {
auto &button = it->second;
if (node["focused"].asBool()) {
button.get_style_context()->add_class("focused");
} else {
button.get_style_context()->remove_class("focused");
}
if (node["visible"].asBool()) {
button.get_style_context()->add_class("visible");
} else {
button.get_style_context()->remove_class("visible");
}
if (node["urgent"].asBool()) {
button.get_style_context()->add_class("urgent");
} else {
button.get_style_context()->remove_class("urgent");
}
if (needReorder) {
box_.reorder_child(button, getWorkspaceIndex(node["name"].asString()));
}
auto icon = getIcon(node["name"].asString(), node);
if (config_["format"].isString()) {
auto format = config_["format"].asString();
button.set_label(fmt::format(format, fmt::arg("icon", icon),
fmt::arg("name", trimWorkspaceName(node["name"].asString())),
fmt::arg("index", node["num"].asString())));
} else {
button.set_label(icon);
}
button.show();
} }
} auto &button = bit == buttons_.end() ? addButton(*it) : bit->second;
if (scrolling_) { if ((*it)["focused"].asBool()) {
scrolling_ = false; button.get_style_context()->add_class("focused");
} else {
button.get_style_context()->remove_class("focused");
}
if ((*it)["visible"].asBool()) {
button.get_style_context()->add_class("visible");
} else {
button.get_style_context()->remove_class("visible");
}
if ((*it)["urgent"].asBool()) {
button.get_style_context()->add_class("urgent");
} else {
button.get_style_context()->remove_class("urgent");
}
if ((*it)["target_output"].isString()) {
button.get_style_context()->add_class("persistant");
} else {
button.get_style_context()->remove_class("persistant");
}
if (needReorder) {
box_.reorder_child(button, it - workspaces_.begin());
}
std::string output = getIcon((*it)["name"].asString(), *it);
if (config_["format"].isString()) {
auto format = config_["format"].asString();
output = fmt::format(format,
fmt::arg("icon", output),
fmt::arg("name", trimWorkspaceName((*it)["name"].asString())),
fmt::arg("index", (*it)["num"].asString()));
}
if (!config_["disable-markup"].asBool()) {
static_cast<Gtk::Label *>(button.get_children()[0])->set_markup(output);
} else {
button.set_label(output);
}
onButtonReady(*it, button);
} }
} }
void waybar::modules::sway::Workspaces::addWorkspace(const Json::Value &node) Gtk::Button &Workspaces::addButton(const Json::Value &node) {
{ auto pair = buttons_.emplace(node["name"].asString(), node["name"].asString());
auto icon = getIcon(node["name"].asString(), node); auto &&button = pair.first->second;
auto format = config_["format"].isString()
? fmt::format(config_["format"].asString(), fmt::arg("icon", icon),
fmt::arg("name", trimWorkspaceName(node["name"].asString())),
fmt::arg("index", node["num"].asString()))
: icon;
auto pair = buttons_.emplace(node["name"].asString(), format);
auto &button = pair.first->second;
box_.pack_start(button, false, false, 0); box_.pack_start(button, false, false, 0);
button.set_relief(Gtk::RELIEF_NONE); button.set_relief(Gtk::RELIEF_NONE);
button.signal_clicked().connect([this, pair] { button.signal_clicked().connect([this, node] {
try { try {
std::lock_guard<std::mutex> lock(mutex_); if (node["target_output"].isString()) {
auto cmd = fmt::format("workspace \"{}\"", pair.first->first); ipc_.sendCmd(
ipc_.sendCmd(IPC_COMMAND, cmd); IPC_COMMAND,
} catch (const std::exception& e) { fmt::format("workspace \"{}\"; move workspace to output \"{}\"; workspace \"{}\"",
std::cerr << e.what() << std::endl; node["name"].asString(),
node["target_output"].asString(),
node["name"].asString()));
} else {
ipc_.sendCmd(IPC_COMMAND, fmt::format("workspace \"{}\"", node["name"].asString()));
}
} catch (const std::exception &e) {
spdlog::error("Workspaces: {}", e.what());
} }
}); });
button.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK); return button;
if (!config_["disable-scroll"].asBool()) {
button.signal_scroll_event()
.connect(sigc::mem_fun(*this, &Workspaces::handleScroll));
}
box_.reorder_child(button, getWorkspaceIndex(node["name"].asString()));
if (node["focused"].asBool()) {
button.get_style_context()->add_class("focused");
}
if (node["visible"].asBool()) {
button.get_style_context()->add_class("visible");
}
if (node["urgent"].asBool()) {
button.get_style_context()->add_class("urgent");
}
button.show();
} }
std::string waybar::modules::sway::Workspaces::getIcon(const std::string &name, std::string Workspaces::getIcon(const std::string &name, const Json::Value &node) {
const Json::Value &node) std::vector<std::string> keys = {name, "urgent", "focused", "visible", "default"};
{ for (auto const &key : keys) {
std::vector<std::string> keys = { name, "urgent", "focused", "visible", "default" };
for (auto const& key : keys) {
if (key == "focused" || key == "visible" || key == "urgent") { if (key == "focused" || key == "visible" || key == "urgent") {
if (config_["format-icons"][key].isString() && node[key].asBool()) { if (config_["format-icons"][key].isString() && node[key].asBool()) {
return config_["format-icons"][key].asString(); return config_["format-icons"][key].asString();
@ -150,107 +213,76 @@ std::string waybar::modules::sway::Workspaces::getIcon(const std::string &name,
return name; return name;
} }
bool waybar::modules::sway::Workspaces::handleScroll(GdkEventScroll *e) bool Workspaces::handleScroll(GdkEventScroll *e) {
{ auto dir = AModule::getScrollDir(e);
// Avoid concurrent scroll event if (dir == SCROLL_DIR::NONE) {
if (scrolling_) { return true;
return false;
} }
scrolling_ = true;
std::string name; std::string name;
uint16_t idx = 0;
{ {
std::lock_guard<std::mutex> lock(mutex_); std::lock_guard<std::mutex> lock(mutex_);
for (; idx < workspaces_.size(); idx += 1) { auto it = std::find_if(workspaces_.begin(), workspaces_.end(), [](const auto &workspace) {
if (workspaces_[idx]["focused"].asBool()) { return workspace["focused"].asBool();
name = workspaces_[idx]["name"].asString(); });
break; if (it == workspaces_.end()) {
} return true;
}
if (dir == SCROLL_DIR::DOWN || dir == SCROLL_DIR::RIGHT) {
name = getCycleWorkspace(it, false);
} else if (dir == SCROLL_DIR::UP || dir == SCROLL_DIR::LEFT) {
name = getCycleWorkspace(it, true);
} else {
return true;
}
if (name == (*it)["name"].asString()) {
return true;
} }
} }
if (name.empty()) { try {
scrolling_ = false;
return false;
}
if (e->direction == GDK_SCROLL_UP) {
name = getNextWorkspace();
}
if (e->direction == GDK_SCROLL_DOWN) {
name = getPrevWorkspace();
}
if (e->direction == GDK_SCROLL_SMOOTH) {
gdouble delta_x, delta_y;
gdk_event_get_scroll_deltas(reinterpret_cast<const GdkEvent *>(e),
&delta_x, &delta_y);
if (delta_y < 0) {
name = getNextWorkspace();
} else if (delta_y > 0) {
name = getPrevWorkspace();
}
}
if (!name.empty()) {
std::lock_guard<std::mutex> lock(mutex_);
if (name == workspaces_[idx]["name"].asString()) {
scrolling_ = false;
return false;
}
ipc_.sendCmd(IPC_COMMAND, fmt::format("workspace \"{}\"", name)); ipc_.sendCmd(IPC_COMMAND, fmt::format("workspace \"{}\"", name));
std::this_thread::sleep_for(std::chrono::milliseconds(150)); } catch (const std::exception &e) {
spdlog::error("Workspaces: {}", e.what());
} }
return true; return true;
} }
std::string waybar::modules::sway::Workspaces::getPrevWorkspace() const std::string Workspaces::getCycleWorkspace(std::vector<Json::Value>::iterator it,
{ bool prev) const {
for (uint16_t i = 0; i < workspaces_.size(); i += 1) { if (prev && it == workspaces_.begin() && !config_["disable-scroll-wraparound"].asBool()) {
if (workspaces_[i]["focused"].asBool()) { return (*(--workspaces_.end()))["name"].asString();
if (i > 0) {
return workspaces_[i - 1]["name"].asString();
}
return workspaces_[workspaces_.size() - 1]["name"].asString();
}
} }
return ""; if (prev && it != workspaces_.begin())
} --it;
else if (!prev && it != workspaces_.end())
std::string waybar::modules::sway::Workspaces::getNextWorkspace() ++it;
{ if (!prev && it == workspaces_.end()) {
for (uint16_t i = 0; i < workspaces_.size(); i += 1) { if (config_["disable-scroll-wraparound"].asBool()) {
if (workspaces_[i]["focused"].asBool()) { --it;
if (i + 1U < workspaces_.size()) {
return workspaces_[i + 1]["name"].asString();
}
return workspaces_[0]["String"].asString();
}
}
return "";
}
uint16_t waybar::modules::sway::Workspaces::getWorkspaceIndex(const std::string &name)
{
uint16_t idx = 0;
for (const auto &workspace : workspaces_) {
if (workspace["name"].asString() == name) {
return idx;
}
if (!config_["all-outputs"].asBool() && workspace["output"].asString() != bar_.output_name) {
// Nothing here
} else { } else {
idx += 1; return (*(workspaces_.begin()))["name"].asString();
} }
} }
return workspaces_.size(); return (*it)["name"].asString();
} }
std::string waybar::modules::sway::Workspaces::trimWorkspaceName(std::string name) std::string Workspaces::trimWorkspaceName(std::string name) {
{ std::size_t found = name.find(':');
std::size_t found = name.find(":"); if (found != std::string::npos) {
if (found!=std::string::npos) { return name.substr(found + 1);
return name.substr(found+1);
} }
return name; return name;
} }
waybar::modules::sway::Workspaces::operator Gtk::Widget &() { void Workspaces::onButtonReady(const Json::Value &node, Gtk::Button &button) {
return box_; if (config_["current-only"].asBool()) {
if (node["focused"].asBool()) {
button.show();
} else {
button.hide();
}
} else {
button.show();
}
} }
} // namespace waybar::modules::sway

View File

@ -0,0 +1,57 @@
#include "modules/temperature.hpp"
waybar::modules::Temperature::Temperature(const std::string& id, const Json::Value& config)
: ALabel(config, "temperature", id, "{temperatureC}°C", 10) {
if (config_["hwmon-path"].isString()) {
file_path_ = config_["hwmon-path"].asString();
} else {
auto zone = config_["thermal-zone"].isInt() ? config_["thermal-zone"].asInt() : 0;
file_path_ = fmt::format("/sys/class/thermal/thermal_zone{}/temp", zone);
}
std::ifstream temp(file_path_);
if (!temp.is_open()) {
throw std::runtime_error("Can't open " + file_path_);
}
thread_ = [this] {
dp.emit();
thread_.sleep_for(interval_);
};
}
auto waybar::modules::Temperature::update() -> void {
auto [temperature_c, temperature_f] = getTemperature();
auto critical = isCritical(temperature_c);
auto format = format_;
if (critical) {
format = config_["format-critical"].isString() ? config_["format-critical"].asString() : format;
label_.get_style_context()->add_class("critical");
} else {
label_.get_style_context()->remove_class("critical");
}
auto max_temp = config_["critical-threshold"].isInt() ? config_["critical-threshold"].asInt() : 0;
label_.set_markup(fmt::format(format,
fmt::arg("temperatureC", temperature_c),
fmt::arg("temperatureF", temperature_f),
fmt::arg("icon", getIcon(temperature_c, "", max_temp))));
}
std::tuple<uint16_t, uint16_t> waybar::modules::Temperature::getTemperature() {
std::ifstream temp(file_path_);
if (!temp.is_open()) {
throw std::runtime_error("Can't open " + file_path_);
}
std::string line;
if (temp.good()) {
getline(temp, line);
}
temp.close();
auto temperature_c = std::strtol(line.c_str(), nullptr, 10) / 1000.0;
auto temperature_f = temperature_c * 1.8 + 32;
std::tuple<uint16_t, uint16_t> temperatures(std::round(temperature_c), std::round(temperature_f));
return temperatures;
}
bool waybar::modules::Temperature::isCritical(uint16_t temperature_c) {
return config_["critical-threshold"].isInt() &&
temperature_c >= config_["critical-threshold"].asInt();
}

View File

@ -1,10 +1,10 @@
[wrap-file] [wrap-file]
directory = fmt-5.2.1 directory = fmt-5.3.0
source_url = https://github.com/fmtlib/fmt/archive/5.2.1.tar.gz source_url = https://github.com/fmtlib/fmt/archive/5.3.0.tar.gz
source_filename = fmt-5.2.1.tar.gz source_filename = fmt-5.3.0.tar.gz
source_hash = 3c812a18e9f72a88631ab4732a97ce9ef5bcbefb3235e9fd465f059ba204359b source_hash = defa24a9af4c622a7134076602070b45721a43c51598c8456ec6f2c4dbb51c89
patch_url = https://wrapdb.mesonbuild.com/v1/projects/fmt/5.2.1/1/get_zip patch_url = https://wrapdb.mesonbuild.com/v1/projects/fmt/5.3.0/1/get_zip
patch_filename = fmt-5.2.1-1-wrap.zip patch_filename = fmt-5.3.0-1-wrap.zip
patch_hash = 7add08bb4e168c0809e88c6aa64ed5c3494b74deb6be12a93e1e4dc5bb3a1fc1 patch_hash = 18f21a3b8833949c35d4ac88a7059577d5fa24b98786e4b1b2d3d81bb811440f

10
subprojects/spdlog.wrap Normal file
View File

@ -0,0 +1,10 @@
[wrap-file]
directory = spdlog-1.3.1
source_url = https://github.com/gabime/spdlog/archive/v1.3.1.tar.gz
source_filename = v1.3.1.tar.gz
source_hash = 160845266e94db1d4922ef755637f6901266731c4cb3b30b45bf41efa0e6ab70
patch_url = https://wrapdb.mesonbuild.com/v1/projects/spdlog/1.3.1/1/get_zip
patch_filename = spdlog-1.3.1-1-wrap.zip
patch_hash = 715a0229781019b853d409cc0bf891ee4b9d3a17bec0cf87f4ad30b28bbecc87