Compare commits

..

403 Commits
0.0.6 ... 0.6.0

Author SHA1 Message Date
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
f47492c371 chore: v0.4.0 2019-03-01 17:12:02 +01:00
79b0367e6c feat(custom): escape option 2019-03-01 17:03:01 +01:00
2c411b9848 Merge pull request #199 from ianhattendorf/fix/backlight-disable-not-found
Disable backlight module if no backlights found
2019-03-01 09:31:49 +01:00
ac6d833d4d Ignore all potential build directories
This is useful when using multiple build directories, eg:
- build-debug
- build-release
- build-sanitize
2019-02-28 19:57:49 -07:00
39de8e544c Disable backlight module if no backlight found 2019-02-28 19:50:57 -07:00
0271e9bc1a Revert "fix: Escape user controlled input"
This reverts commit 7247360e29.
2019-02-28 19:30:27 +01:00
470f539346 Merge pull request #193 from Robinhuett/custom_alt
feat(custom): Add field for additional data to json
2019-02-26 12:18:47 +01:00
6633e34bbd Merge branch 'master' into custom_alt 2019-02-26 12:18:34 +01:00
4b3725ec31 Merge pull request #194 from ianhattendorf/fix/backlight-crash-unplug-monitor
Fix crash when monitor unplugged
2019-02-26 12:18:19 +01:00
c1295c8fd6 Fix crash when monitor unplugged
`Backlight#devices_` was being destructed before
`Backlight#udev_thread_`. Also check if thread is still running after
`epoll_wait`
2019-02-25 20:05:44 -07:00
39bf403505 feat(custom): Add field for additional data to json 2019-02-25 22:04:09 +01:00
4499a23e51 Merge pull request #192 from DutchMofo/clock-tooltip
Fix tooltip format
2019-02-25 20:39:31 +01:00
821a009c32 Fix tooltip format 2019-02-25 19:25:19 +01:00
961a05bedf Merge pull request #191 from Robinhuett/escape_userinput
fix: Escape user controlled input
2019-02-25 18:46:17 +01:00
7247360e29 fix: Escape user controlled input 2019-02-25 18:28:56 +01:00
abfa428dab Merge pull request #190 from Robinhuett/fix_backlight_fmtalt
fix(backlight) Allow format-alt
2019-02-25 10:52:42 +01:00
63e97df9ff fix(backlight) Allow format-alt 2019-02-24 22:15:41 +01:00
5a7f801922 Merge pull request #189 from DutchMofo/clock-tooltip
Added clock tooltip with optional alternate format
2019-02-24 12:10:01 +01:00
8fa30f8ce3 Forgot comma in json 2019-02-24 11:54:59 +01:00
da4661f97c Removed clock tooltip from example config 2019-02-24 11:49:58 +01:00
7245fb5d0a Added clock tooltip with optional alternate format 2019-02-24 09:25:34 +01:00
b4f36436c3 style: update idle inhibitor color 2019-02-22 17:34:13 +01:00
eb7e265eb1 Merge pull request #187 from jonfin/idle_inhibitor
Add module Idle inhibitor
2019-02-22 17:31:51 +01:00
c2ed0cb832 Add the possibility to disable the tooltip 2019-02-22 16:58:36 +01:00
d708ce2be9 Add idle inhibitor module 2019-02-22 16:55:46 +01:00
83a6475510 feat: can disable tooltip 2019-02-22 11:35:47 +01:00
331b28393a Merge pull request #181 from ianhattendorf/feature/module-brightness
Add backlight module
2019-02-21 16:04:32 +01:00
afbf2de1b9 Merge branch 'master' into feature/module-brightness 2019-02-21 15:29:17 +01:00
e67347f6ad Gate backlight module behind libudev availability 2019-02-18 21:11:18 -07:00
bef8520937 Remove opensuse ci for now 2019-02-18 12:33:40 +01:00
8bfcb106c0 style: comment about workspace hover effect 2019-02-18 12:30:41 +01:00
875306804c Add backlight module
Monitor the backlight level via udev. Poll every `interval` as well,
in case backlight udev events aren't supported.
2019-02-17 15:29:49 -07:00
6bf4f65228 fix: two finger scroll 2019-02-16 09:56:53 +01:00
0c9edb0c4b fix(custom): check exist_status in endless script before clean the output 2019-02-16 09:48:27 +01:00
3e18673451 fix: remove bluetooth class on else 2019-02-12 17:55:50 +01:00
d0370acb21 refactor(network): better network disconnection 2019-02-11 19:06:39 +01:00
aeec80f375 fix(Tray): big icon 2019-02-07 13:34:30 +01:00
dc9fe04d11 refactor: add retry to get external interface 2019-02-06 10:33:12 +01:00
01cec9fcb7 fix(bar): multi screens 2019-02-04 22:09:01 +01:00
6fb25ade7e chore: update opensuse Dockerfile 2019-02-04 11:20:18 +01:00
e10e9554ab Update README.md 2019-02-04 10:32:16 +01:00
93173851d3 fix(Workspaces): index 2019-02-02 12:07:59 +01:00
add9e925f4 fix(Workspaces): button order 2019-02-02 00:36:52 +01:00
60af51fc17 chore: update opensuse dockerfile 2019-02-01 21:56:34 +01:00
3021ef2d51 Update README.md 2019-02-01 21:52:38 +01:00
0ddcf26a45 feat: output configuration 2019-02-01 21:45:59 +01:00
4d3c2191cb chore: v0.3.0 2019-01-28 19:38:58 +01:00
38fc62ea27 feat: bar id 2019-01-28 19:26:16 +01:00
2f7b2677d7 Merge pull request #155 from Robinhuett/window_ignore_self_fix
fix(window): Fix #154
2019-01-27 12:06:58 +01:00
6d03d9f6a9 fix(window): Fix #154 2019-01-27 11:59:07 +01:00
6f2d784d4e Merge pull request #154 from Robinhuett/window_ignore_self
fix(window): Ignore waybar
2019-01-27 02:09:44 +01:00
ce6816737c fix(window): Ignore waybar
With this, the window title waybar will be ignored so it wont flicker as decribed in #148
2019-01-26 19:02:11 +01:00
a0fd99b112 Merge pull request #152 from jubalh/fmt
Add fmt dependency to README.md
2019-01-25 16:42:41 +01:00
12a48b70ae Add fmt dependency to README.md 2019-01-25 16:38:38 +01:00
aedf133b16 Merge pull request #146 from Alexays/network_alt
fix(Network): format-alt
2019-01-14 09:05:52 +01:00
02aed73295 Merge branch 'master' into network_alt 2019-01-13 22:39:04 +01:00
9348e88592 Merge pull request #130 from Alexays/destructor
refactor: proper modules destruction
2019-01-13 22:37:19 +01:00
eace8c549f fix(Network): format-alt 2019-01-13 22:36:37 +01:00
f8116132a7 fix(Workspaces): check thread is running before parse response 2019-01-13 22:23:09 +01:00
171e0e5ae3 revert: don't disable seq check 2019-01-13 22:22:22 +01:00
30781757e3 revert: prefer nl_send_sync 2019-01-13 22:22:22 +01:00
800d2f388e refactor(network): proper signal strength type 2019-01-13 22:22:22 +01:00
1647e31b48 refactor: free netlink message 2019-01-13 22:22:22 +01:00
399f61df98 refactor: proper modules destruction 2019-01-13 22:22:22 +01:00
8af813c2f1 Merge pull request #140 from Robinhuett/headers
refactor: replace all gtkmm.h includes
2019-01-09 01:00:00 +01:00
29a2ee1744 refactor: Replace all occurencec of gtkmm.h and only use the necessary headers 2019-01-08 21:05:44 +01:00
afa1cc8287 Update README.md 2018-12-29 00:06:59 +01:00
ab78698ffd Merge pull request #139 from cjbassi/master
Remove ws index from sway ws names
2018-12-28 18:45:58 +01:00
8ea0659ee2 Remove ws index from sway ws names 2018-12-28 09:36:02 -08:00
aa6da11ba4 Merge pull request #138 from cjbassi/master
Add install and build-debug commands to makefile
2018-12-28 15:58:51 +01:00
6a5a4881ad Add install and build-debug commands to makefile 2018-12-28 06:37:07 -08:00
3a9bf932b4 Merge pull request #133 from cjbassi/feature/makefile
Add simple makefile
2018-12-28 01:23:23 +01:00
a078be991a Merge branch 'master' into feature/makefile 2018-12-28 01:16:56 +01:00
83ae5ec5c9 Merge pull request #134 from cjbassi/fix/typo
Fix typo
2018-12-28 01:16:31 +01:00
b4d38294a7 Fix typo 2018-12-27 16:03:29 -08:00
7804514c5c Add simple makefile 2018-12-27 15:56:47 -08:00
513278597a Merge pull request #129 from Robinhuett/custom-module-icon
Add format-icons to custom module
2018-12-26 11:42:25 +01:00
4698c9d2cf chore(custom): Change int conversion 2018-12-26 11:35:58 +01:00
11c98f13e3 feat(custom): Add format-icons to custom module
This commit allows custom modules (json only) to set a percentage. This can be displayed either by using {percentage} or by using {icon} with format-icons set.
2018-12-26 03:52:05 +01:00
76bbdd0425 Merge pull request #128 from Robinhuett/wifiicon
Use SignalStrength for format icons
2018-12-25 21:07:25 +01:00
34df2b0695 fix(ALabel): Better fix for getIcon 2018-12-25 21:03:13 +01:00
ad638357b5 feat(network): Use Signal Strength for format-icons 2018-12-25 14:17:34 +01:00
7404f80122 fix(ALabel): getIcon 2018-12-25 14:15:59 +01:00
de0ee9fcb2 fix(battery): adapter status 2018-12-24 12:17:07 +01:00
755fad6bc3 fix(battery): typo 2018-12-24 08:50:58 +01:00
e3c0624c48 fix(battery): typo 2018-12-24 08:38:37 +01:00
87e55ea993 feat(battery): check adapter online as fallback when battery status report unknown 2018-12-24 08:37:10 +01:00
3b8bfb08a4 fix(network): typo 2018-12-22 14:00:56 +01:00
00728fe877 Merge pull request #125 from chep/master
fix(pulseaudio): return puleseaudio port string if unknown
2018-12-20 10:24:55 +01:00
a4062455cd fix(pulseaudio): return puleseaudio port string if unknown 2018-12-20 09:54:10 +01:00
3691d84543 chore: v0.2.3 2018-12-18 17:42:35 +01:00
b554094c7e feat: args && class id 2018-12-18 17:30:54 +01:00
66ad2864c2 fix(Pulseaudio): use markup 2018-12-17 09:00:40 +01:00
c7b0639f32 fix(workspaces): check thread is running 2018-12-09 10:49:28 +01:00
0acc50264e fix(custom): failed getline after closed pipe 2018-12-08 13:57:56 +01:00
1b13f9e38c fix(custom): close endless scripts 2018-12-08 12:58:47 +01:00
7befd27059 fix: clock interval 2018-12-04 09:38:08 +01:00
2ec34e4adb feat: add $HOME to valid path 2018-12-03 20:12:34 +01:00
812a7a9861 fix: typo 2018-12-03 11:13:56 +01:00
1d96d57b75 feat: warn user about stopped endless custom module 2018-12-03 09:35:10 +01:00
691fb88057 refactor(modules): const bar 2018-12-01 00:10:41 +01:00
281bab4943 feat(ci): opensuse 2018-11-30 21:48:42 +01:00
8e93fd3ae0 revert: Update to new Sway IPC protocol 2018-11-29 09:50:00 +01:00
37c7e586d6 Merge pull request #110 from tokyovigilante/ipc-fix
Update to new Sway IPC protocol
2018-11-29 00:40:57 +01:00
3a5a470d90 Update to new Sway IPC protocol 2018-11-28 22:27:06 +00:00
c051b517ca Merge pull request #108 from SibrenVasse/spotify
fix(custom): fix conditional statement
2018-11-26 20:09:21 +01:00
a0cdef569b fix(custom): fix conditional statement 2018-11-26 19:35:03 +01:00
a123711a8d fix(network): disconnected forced interface 2018-11-25 21:32:50 +01:00
391a7de94a fix(window): escape window name 2018-11-25 15:21:00 +01:00
952d877652 Merge pull request #103 from SibrenVasse/spotify
Event based mediaplayer script
2018-11-24 18:13:08 +01:00
5356d25a9b Merge branch 'master' into spotify 2018-11-24 18:11:48 +01:00
65b9911df4 Merge pull request #105 from David96/master
Allow custom plugins that don't execute anything
2018-11-24 18:01:31 +01:00
2aed121903 Allow custom plugins that don't execute anything 2018-11-24 17:24:02 +01:00
08bfdda4cb revert(network): wait interface 2018-11-24 16:01:22 +01:00
8f8ec3b999 feat(Label): on-click-right 2018-11-24 15:56:16 +01:00
a17220054d feat(client): pefix module name on update error 2018-11-24 11:20:03 +01:00
4cc48b3cfd fix(client): catch error on update 2018-11-24 11:13:52 +01:00
087de4e956 refactor(client): lambda to method 2018-11-24 11:04:56 +01:00
2c2a0473f4 feat(client): throw when we don't have required resources files 2018-11-23 19:31:40 +01:00
2e1f8b2fc5 fix(network): check len of netlinkResponse 2018-11-23 17:52:10 +01:00
686bc4828e refactor(network): only get info when there is an interface 2018-11-23 17:46:14 +01:00
f6c2a8d9b7 fix(network): free the message instead of the socket 2018-11-23 17:42:26 +01:00
baa7f52e21 refactor(network): wait for new address 2018-11-23 16:04:29 +01:00
c4c0c01a2f feat(custom): event based mediaplayer script 2018-11-23 12:08:15 +01:00
2b05b8e69a chore: v0.2.2 2018-11-23 12:03:23 +01:00
ad7400d5ce refactor(ALabel): add interval 2018-11-23 11:57:37 +01:00
36652158ad refactor(tray): more cpp binding 2018-11-23 10:46:58 +01:00
8db94f7efd revert(ALabel): ref on format 2018-11-22 16:50:42 +01:00
aaaa17d94f fix(config): remove duplicate tray 2018-11-22 16:21:46 +01:00
0b1b0eb1a7 feat(tray): multiple hosts 2018-11-22 16:20:49 +01:00
f00be0b552 refactor(tray): remove useless variable 2018-11-22 15:51:55 +01:00
ba79b4d397 refactor(tray): cleanup and fixes 2018-11-22 15:47:23 +01:00
bd62c4cbc5 feat(Label): use set_markup 2018-11-21 20:49:09 +01:00
b3559328f1 fix(window): ellipsize 2018-11-20 23:24:33 +01:00
69fceb1c92 fix(tray): sigsev on click when multiple icons is available 2018-11-16 12:01:03 +01:00
33f138c16e chore: v0.2.1 2018-11-16 10:15:27 +01:00
01692b719a chore: update README 2018-11-16 10:07:20 +01:00
8c26a6aab7 chore: update README 2018-11-16 10:06:24 +01:00
e42fae32ab feat(network): network info interval 2018-11-16 10:02:12 +01:00
c910767378 refactor: remove usless using 2018-11-15 14:48:49 +01:00
94b9f0a399 feat(cpu): add both usage and load 2018-11-15 14:44:43 +01:00
1665003d23 fix: sigsev 2018-11-14 19:14:51 +01:00
e5573c20e6 Merge pull request #96 from Robinhuett/module_network_ipaddr
Module network ipaddr
2018-11-14 10:44:27 +01:00
75cc1bc318 refactor(network): codestyle and error handling 2018-11-14 10:31:17 +01:00
50e782e028 chore: update example config 2018-11-13 21:40:47 +01:00
5c66b1a770 feat(network): display ip address and subnetmask 2018-11-13 21:31:26 +01:00
3dc0f7ccf9 Merge pull request #94 from Robinhuett/cpu_fix
Use /proc/stat for cpu load
2018-11-11 13:41:11 +01:00
e1d98f0ad9 fix(cpu): show correct load
feat(cpu): show cores in tooltip
2018-11-11 03:11:32 +01:00
7222668326 refactor: disable battery timer stop on inotify event for now 2018-11-09 23:02:46 +01:00
315e2defde Merge pull request #93 from Robinhuett/pulseaudio_scroll
Add config option for volume change scroll step size
2018-11-09 22:55:51 +01:00
45bb8b1a1f refactor: simpler memory code 2018-11-09 22:55:25 +01:00
e21df5ae36 Add config option for volume change scroll step size 2018-11-09 22:48:27 +01:00
a5bca24f9b Merge pull request #91 from Robinhuett/ram_free_used
Use /proc/meminfo for Memory module
2018-11-09 22:34:11 +01:00
c07037d6b8 Update README.md 2018-11-09 17:27:35 +01:00
a9751545fa fix: update travis 2018-11-09 17:15:19 +01:00
9ea0815dea Use ifstream to reaad /proc/meminfo 2018-11-09 16:24:13 +01:00
b8b799a187 [ci skip] remove .SRCINFO at root 2018-11-09 15:36:21 +01:00
39dfa66261 feat(ci): archlinux 2018-11-09 15:19:17 +01:00
13702012a4 CI (#90) 2018-11-09 12:07:16 +01:00
6b62079d8a rewind stream instead of opening a new one 2018-11-09 00:17:30 +01:00
ac0963c608 Use /proc/meminfo for Memory module 2018-11-08 21:09:56 +01:00
2d2fb88040 fix: fmt 2018-11-08 09:57:24 +01:00
0933aad75f Merge pull request #87 from David96/master
Fix workspaces not being removed from bar when moved to another output
2018-11-05 21:06:53 +01:00
adcd956c24 Fix workspaces not being removed from bar when moved to another output 2018-11-05 20:59:28 +01:00
6c9e37699b Merge pull request #86 from David96/master
Fix clicking and scrolling through workspaces
2018-11-05 20:35:48 +01:00
168415440f Fix clicking and scrolling through workspaces
The way waybar used the workspace "num", clicking a workspace called "1:
something" resulted in going to a newly created workspace called "1",
because the workspace ipc command expects the workspace name, not its number.
2018-11-05 20:16:19 +01:00
d6af63d84a chore: add travis 2018-11-05 11:59:05 +01:00
26182c222b Merge pull request #79 from vberger/master
Don't call layer_surface.set_size on configure
2018-11-03 13:36:04 +01:00
43cd80fb31 chore: 0.2.0 2018-11-03 13:20:05 +01:00
2f6abfda59 Don't call layer_surface.set_size on configure 2018-11-03 13:16:13 +01:00
5ece0d98ee Merge pull request #78 from mithodin/filesystem-experimental
add option for when filesystem still lives in the experimental namespace
2018-11-03 13:00:04 +01:00
0637888460 even simpler check 2018-11-03 12:44:15 +01:00
ebbdaa168c automatically detect where filesystem lives 2018-11-02 23:15:42 +01:00
6ab01b1ad4 fix(style): not charging 2018-11-02 23:00:38 +01:00
cf921a5e14 Merge pull request #76 from mithodin/charging-full
Add class for full battery and give option to interpret unknown as full
2018-11-02 22:51:57 +01:00
25f31b19f6 formatting is hard. 2018-11-02 22:50:01 +01:00
d8b6201632 ...and fix the function signature in the header 2018-11-02 22:15:54 +01:00
123ce083b4 fix typo and initialize old_status_ 2018-11-02 22:08:55 +01:00
0522577fe5 make status and state fully configurable formats 2018-11-02 22:04:43 +01:00
1ff9fd06af Merge pull request #77 from mithodin/old-gdbus-codegen
fix compilation on systems with old gdbus-codegen
2018-11-02 21:23:10 +01:00
b6cad05489 fix formatting 2018-11-02 21:13:57 +01:00
236be90c2f add option for when filesystem still lives in the experimental namespace 2018-11-02 20:59:41 +01:00
f137090d55 fix compilation on systems with old gdbus-codegen 2018-11-02 20:13:09 +01:00
9c57df505c Add class for full battery and give option to interpret unknown as full 2018-11-02 19:41:00 +01:00
00e7e87f55 fix: style 2018-11-02 17:39:00 +01:00
836c543c62 fix: style 2018-11-02 17:07:51 +01:00
7bca5fd6bd feat(Bar): add a warning about minimum height 2018-11-02 12:35:26 +01:00
61e9f0803d Merge pull request #75 from ForTheReallys/proper_height
Fix #54
2018-11-02 12:26:14 +01:00
9b201c77d7 feat: battery states && format-full/charging 2018-11-02 11:23:29 +01:00
4b68840212 Fix #54 2018-11-01 16:00:38 -05:00
9d4048983d refactor: remove useless tmp variable 2018-11-01 09:27:00 +01:00
0670225e69 Merge pull request #72 from Robinhuett/custom_module_states
Custom modules can control tooltip and CSS class
2018-11-01 09:11:15 +01:00
e23fbd0add Added return-type json to custom module 2018-11-01 00:40:44 +01:00
341d3300fa Custom modules can control tooltip and CSS class 2018-10-30 21:28:31 +01:00
c3e185546d Merge pull request #68 from harishkrupo/master
Add configuration options for widgets on mouse events
2018-10-30 16:32:37 +01:00
0e93de9c0a Merge pull request #71 from Robinhuett/configurable_battery_levels
Added second warning stage to battery module
2018-10-30 16:31:01 +01:00
3e34137ac7 pulseaudio: Change volume on scroll event
Subscribe for mouse scroll events on the pulseaudio widget
and change volume when event is received.
Scroll up increments the volume and scroll down decrements it.
These events are only subscibed when there are no user defined
commands present for them.

Signed-off-by: Harish Krupo <harishkrupo@gmail.com>
2018-10-30 20:53:43 +05:30
4c8621c7a5 Added second warning stage to battery module
Also naming is a bit more consistent
2018-10-30 16:23:36 +01:00
d7d1ebd736 ALabel: Add support for configurable mouse events
This patch adds 3 new configuration options applicable for
subclasses of ALabel. The options can be used to execute
user defined code in response to the 3 mouse events:
* on-click: The left mouse button click
* on-scroll-up
* on-scroll-down
This patch also modifies the behaviour of the format-alt toggle
such that when the on-click event is configured, format-alt is
toggled on any mouse click other than left click. When on-click
is not defined, any mouse button would toggle format-alt.

Signed-off-by: Harish Krupo <harishkrupo@gmail.com>
2018-10-30 20:52:23 +05:30
e93c5e7957 Merge pull request #70 from Robinhuett/mode_module
Add module to show sway binding mode
2018-10-30 14:26:43 +01:00
668b7b736c Added default config for sway binding mode 2018-10-30 13:44:44 +01:00
a042eea384 Add module to show sway binding mode 2018-10-30 13:39:30 +01:00
c9a8a07976 fix(window): title on new workspace 2018-10-29 21:52:53 +01:00
4307e4fd8e chore: upgrade fmt to 5.2.0 2018-10-28 14:40:25 +01:00
daf613f8ca feat: add debug about tray beta 2018-10-28 08:43:48 +01:00
3f2eb0b492 chore: 0.1.3 2018-10-28 08:39:33 +01:00
4f773ea268 Merge pull request #65 from maxice8/fix-musl
add missing <cstring> include for strncpy, fixes musl
2018-10-28 08:29:56 +01:00
047473e5a4 add missing <cstring> include for strncpy, fixes musl
I/usr/include/libdbusmenu-glib-0.4 -flto -fdiagnostics-color=always -DNDEBUG -pipe -D_FILE_OFFSET_BITS=64 -std=c++17 -DHAVE_SWAY -DHAVE_LIBPULSE -DHAVE_DBUSMENU -D_FORTIFY_SOURCE=2 -mtune=generic -O2 -D_REENTRANT -pthread  -MD -MQ 'waybar@exe/src_modules_sway_ipc_client.cpp.o' -MF 'waybar@exe/src_modules_sway_ipc_client.cpp.o.d' -o 'waybar@exe/src_modules_sway_ipc_client.cpp.o' -c ../src/modules/sway/ipc/client.cpp
../src/modules/sway/ipc/client.cpp: In member function 'int waybar::modules::sway::Ipc::open(const string&) const':
../src/modules/sway/ipc/client.cpp:47:3: error: 'strncpy' was not declared in this scope
   strncpy(addr.sun_path, socketPath.c_str(), sizeof(addr.sun_path) - 1);
   ^~~~~~~
../src/modules/sway/ipc/client.cpp:47:3: note: 'strncpy' is defined in header '<cstring>'; did you forget to '#include <cstring>'?
../src/modules/sway/ipc/client.cpp:2:1:
+#include <cstring>

../src/modules/sway/ipc/client.cpp:47:3:
   strncpy(addr.sun_path, socketPath.c_str(), sizeof(addr.sun_path) - 1);
   ^~~~~~~
[36/44] Compiling C++ object 'waybar@exe/src_modules_custom.cpp.o'.
[37/44] Compiling C++ object 'waybar@exe/src_client.cpp.o'.
[38/44] Compiling C++ object 'waybar@exe/src_modules_cpu.cpp.o'.
ninja: build stopped: subcommand failed.
2018-10-28 04:06:07 -03:00
ed3e4b1395 fix(pulseaudio): check active_port is set 2018-10-27 11:23:43 +02:00
16b01e1059 Merge pull request #62 from colemickens/giounix20
meson: fix 'gio-unix-2.0' dependency
2018-10-27 09:35:47 +02:00
1ae490c8f7 Merge pull request #61 from colemickens/outdir
meson: make extra output directory configurable
2018-10-27 09:16:11 +02:00
0d0a3be483 meson: fix 'gio-unix-2.0' dependency 2018-10-26 23:21:03 -07:00
a1c4b9bb0c meson: make extra output directory configurable 2018-10-26 23:20:38 -07:00
a55a1ae866 fix(tray): icons size 2018-10-26 14:53:39 +02:00
07d8dfb3d6 feat(tray): spacing config 2018-10-26 12:08:50 +02:00
5010227e6b fix(tray): icons 2018-10-26 11:59:03 +02:00
e8f3c1c6b3 chore: v0.1.2 2018-10-26 11:21:04 +02:00
7e6c701659 chore: update README 2018-10-26 11:16:17 +02:00
adc38c3dfe feat(sni): set protocol version 2018-10-26 10:56:45 +02:00
b10907ee44 refactor: remove useless code 2018-10-26 10:39:25 +02:00
0c9699b076 fix: check before set is host registered 2018-10-26 10:27:15 +02:00
63e86fbe9e fix: check type 2018-10-26 10:12:34 +02:00
f20441fa92 refactor: simpler sni naming 2018-10-26 10:05:54 +02:00
3f269ff463 fix: check json::value type 2018-10-26 09:27:16 +02:00
fd76e98552 fix: ifdef include 2018-10-25 19:12:28 +02:00
9fae5efc06 feat: use interval thread until got inotify event 2018-10-25 17:39:15 +02:00
7f1f217d84 feat: multiple config per modules 2018-10-25 17:30:26 +02:00
1ea0c1f9dd chore: find gdbus-codegen once 2018-10-25 16:42:01 +02:00
4626cbef63 fix(pulseaudio): round volume 2018-10-25 13:57:35 +02:00
85f845ca43 refactor: remove debug 2018-10-25 13:49:30 +02:00
68d9d2c347 fix: ifdef 2018-10-25 12:24:39 +02:00
f3fe57dd24 chore: v0.1.0 2018-10-25 12:15:52 +02:00
d4b97d5d09 feat: optional tray 2018-10-25 11:47:03 +02:00
f3975e6634 feat: gdbus dbus-menu 2018-10-25 11:44:04 +02:00
0dedcc0126 Merge pull request #39 from Alexays/tray-gdbus
Tray beta
2018-10-25 11:40:33 +02:00
0e6147b644 Merge branch 'master' into tray-gdbus 2018-10-25 11:36:35 +02:00
45847847b9 style: disallow resize 2018-10-25 10:43:37 +02:00
c912d8c86a fix(workspaces): index 2018-10-25 10:22:11 +02:00
da0674debc Merge pull request #53 from dangerousdan/fix-clock-interval
fix custom intervals causing cpu to go mental
2018-10-23 03:02:23 +02:00
137c5fb712 fix custom intervals causing cpu to go mental 2018-10-23 00:06:46 +01:00
5a6e05dcde feat: workspaces index 2018-10-22 10:41:52 +02:00
290f3a79e9 Merge pull request #46 from DanySpin97/build-options
Add options for pulseaudio and libnl
2018-10-21 12:31:14 +02:00
73553802f9 Add options for pulseaudio and libnl 2018-10-21 09:58:35 +00:00
a13ce6e227 Merge branch 'tray-gdbus' into tray-gdbus 2018-10-05 21:20:36 +02:00
091b460d03 feat(Tray): handle click 2018-10-04 18:53:50 +02:00
56e55fa4aa fix: remove TODO 2018-10-04 18:53:50 +02:00
dc799adf45 feat(Tray): icon pixmap 2018-10-04 18:53:50 +02:00
75c9477aa8 feat(Tray): handle item unregister 2018-10-04 18:53:50 +02:00
75cf1d70fd feat(WIP): tray
feat(wip): tray

feat(wip): tray

feat(WIP): gdbus

feat(WIP): tray
2018-10-04 18:53:50 +02:00
9a333088e5 Merge branch 'tray-gdbus' of github.com:topisani/Waybar into tray-gdbus 2018-10-04 18:47:06 +02:00
b231054b69 Merge remote-tracking branch 'origin/master' into tray-gdbus 2018-10-04 18:04:36 +02:00
108b1092e5 WIP sni dbus-menu support. 2018-10-04 18:03:01 +02:00
a63650aa67 chore: optimize preview 2018-10-01 18:56:58 +02:00
0e8b3f71b8 fix(Custom): pclose if continuous script end 2018-09-18 23:21:08 +02:00
00959c7d65 feat(Custom): handle continuous script 2018-09-18 23:15:37 +02:00
d5d620e72d feat(Window): handle closed window 2018-09-18 21:16:35 +02:00
d914429194 feat(Workspaces): format 2018-09-18 20:58:11 +02:00
fcdb8387af feat(Tray): handle click 2018-09-17 23:32:05 +02:00
3e2e1a7018 fix: remove TODO 2018-09-15 19:01:28 +02:00
86958f264e feat(Tray): icon pixmap 2018-09-15 19:01:28 +02:00
20ff2cab9e feat(Tray): handle item unregister 2018-09-15 19:01:28 +02:00
fc6e42d748 feat(WIP): tray
feat(wip): tray

feat(wip): tray

feat(WIP): gdbus

feat(WIP): tray
2018-09-15 19:00:45 +02:00
55e1905284 fix(Sway): compile without sway 2018-09-10 11:25:53 +02:00
0abaaf2f7f style: fix 2018-09-10 11:16:57 +02:00
f78ef0d491 fix(Meson): optional sway 2018-09-10 11:00:53 +02:00
de5df09fcd fix(Custom): loop script block main loop 2018-09-05 19:20:19 +02:00
7020af7653 feat(Workspaces): urgent, visible, focused icons 2018-09-05 00:16:56 +02:00
0eee8eade7 feat(WIP): tray
feat(wip): tray

feat(wip): tray

feat(WIP): gdbus

feat(WIP): tray
2018-09-02 17:29:16 +02:00
28c65c64e6 chore: add default build type 2018-08-30 11:30:20 +02:00
4f75d5e33b fix: config 2018-08-30 00:04:43 +02:00
aa05304139 feat(Pulseadio): config icons 2018-08-29 23:54:23 +02:00
6dd9b5ccc4 feat(Pulseadio): port icons 2018-08-29 23:50:41 +02:00
d0933ab50f fix(thread): check before detach 2018-08-29 21:07:58 +02:00
9a1b8bb831 fix(Custom): only set id when getting an output 2018-08-28 11:10:36 +02:00
77 changed files with 6315 additions and 1338 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

4
.gitignore vendored
View File

@ -4,8 +4,8 @@ vgcore.*
/.vscode
*.swp
packagecache
/subprojects/fmt-4.1.0
/build
/subprojects/**/
/build*
/dist
/meson.egg-info

18
.travis.yml Normal file
View File

@ -0,0 +1,18 @@
sudo: false
services:
- docker
env:
- distro: debian
- distro: archlinux
# - distro: opensuse
before_install:
- docker pull alexays/waybar:${distro}
script:
- echo FROM alexays/waybar:${distro} > Dockerfile
- echo ADD . /root >> Dockerfile
- 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"

4
Dockerfiles/archlinux Normal file
View File

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

5
Dockerfiles/debian Normal file
View File

@ -0,0 +1,5 @@
FROM debian:sid
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 clean

5
Dockerfiles/opensuse Normal file
View File

@ -0,0 +1,5 @@
FROM opensuse/tumbleweed:latest
RUN zypper -n up && \
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

20
Makefile Normal file
View File

@ -0,0 +1,20 @@
.PHONY: build build-debug run clean default install
default: run
build:
meson build
ninja -C build
build-debug:
meson build --buildtype=debug
ninja -C build
install: build
ninja -C build install
run: build
./build/waybar
clean:
rm -rf build

View File

@ -1,22 +1,25 @@
# Waybar [![Licence](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)<br>![Waybar](https://raw.githubusercontent.com/alexays/waybar/master/preview-2.png)
**Proof of concept**
# 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)
> 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)
**Current features**
- Sway Workspaces
- Sway focused window name
- Sway (Workspaces, Binding mode, Focused window name)
- Tray [#21](https://github.com/Alexays/Waybar/issues/21)
- Local time
- Battery
- Network
- Pulseaudio
- Memory
- Cpu load average
- Temperature
- MPD
- Custom scripts
- Multiple output configuration
- And much more customizations
**Configuration and Customization**
**Configuration and Styling**
[See the wiki for more details](https://github.com/Alexays/Waybar/wiki).
@ -24,9 +27,30 @@
```bash
$ git clone https://github.com/Alexays/Waybar
$ cd Waybar
$ meson build
$ ninja -C build
$ ./build/waybar
# If you want to install it
$ ninja -C build install
$ waybar
```
**Dependencies**
```
gtkmm3
jsoncpp
libinput
libsigc++
fmt
wayland
wlroots
libpulse [Pulseaudio module]
libnl [Network module]
sway [Sway modules]
libdbusmenu-gtk3 [Tray module]
libmpdclient [MPD module]
```
Contributions welcome! - have fun :)<br>

View File

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

View File

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

View File

@ -1,59 +1,71 @@
#pragma once
#include <glibmm/refptr.h>
#include <gtkmm/cssprovider.h>
#include <gtkmm/main.h>
#include <gtkmm/window.h>
#include <json/json.h>
#include <gtkmm.h>
#include "IModule.hpp"
#include "idle-inhibit-unstable-v1-client-protocol.h"
#include "wlr-layer-shell-unstable-v1-client-protocol.h"
#include "xdg-output-unstable-v1-client-protocol.h"
#include "IModule.hpp"
namespace waybar {
class Client;
class Factory;
class Bar {
public:
Bar(const Client&, std::unique_ptr<struct wl_output *>&&, uint32_t);
Bar(const Bar&) = delete;
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 *);
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_;
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_;
struct waybar_output {
struct wl_output * output;
std::string name;
uint32_t wl_name;
struct zxdg_output_v1 *xdg_output;
};
}
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 inline const std::string MIN_HEIGHT_MSG =
"Requested height: {} exceeds the minimum height: {} required by the modules";
static inline const std::string MIN_WIDTH_MSG =
"Requested width: {} exceeds the minimum width: {} required by the modules";
static inline const std::string BAR_SIZE_MSG =
"Bar configured (width: {}, height: {}) for output: {}";
static inline const std::string 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 onWindowRealize();
auto setupWidgets() -> void;
void getModules(const Factory &, const std::string &);
void setupAltFormatKeyForModule(const std::string &module_name);
void setupAltFormatKeyForModuleList(const char *module_list_name);
uint32_t width_ = 0;
uint32_t height_ = 1;
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_;
};
} // namespace waybar

View File

@ -1,43 +1,56 @@
#pragma once
#include <unistd.h>
#include <wordexp.h>
#include <fmt/format.h>
#include <gdk/gdk.h>
#include <wayland-client.h>
#include <gdk/gdkwayland.h>
#include <unistd.h>
#include <wayland-client.h>
#include <wordexp.h>
#include "bar.hpp"
namespace waybar {
class Client {
public:
Client(int argc, char *argv[]);
int main(int argc, char *argv[]);
public:
static Client *inst();
int main(int argc, char *argv[]);
Glib::RefPtr<Gtk::Application> gtk_app;
std::string css_file;
std::string config_file;
Glib::RefPtr<Gdk::Display> gdk_display;
struct wl_display *wl_display = nullptr;
struct wl_registry *registry = nullptr;
struct zwlr_layer_shell_v1 *layer_shell = nullptr;
struct zxdg_output_manager_v1 *xdg_output_manager = nullptr;
struct wl_seat *seat = nullptr;
std::vector<std::unique_ptr<Bar>> bars;
Glib::RefPtr<Gtk::Application> gtk_app;
Glib::RefPtr<Gdk::Display> gdk_display;
struct wl_display * wl_display = nullptr;
struct wl_registry * registry = nullptr;
struct zwlr_layer_shell_v1 * layer_shell = nullptr;
struct zxdg_output_manager_v1 * xdg_output_manager = nullptr;
struct zwp_idle_inhibit_manager_v1 *idle_inhibit_manager = nullptr;
std::vector<std::unique_ptr<Bar>> bars;
private:
void bindInterfaces();
auto setupCss();
private:
Client() = default;
void setupConfigs(const std::string &config, const std::string &style);
void bindInterfaces();
const std::string getValidPath(const std::vector<std::string> &paths);
void handleOutput(std::unique_ptr<struct waybar_output> &output);
bool isValidOutput(const Json::Value &config, std::unique_ptr<struct waybar_output> &output);
auto setupConfig() -> void;
auto setupCss() -> 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,
uint32_t name, const char *interface, uint32_t version);
static void handleGlobalRemove(void *data,
struct wl_registry *registry, uint32_t name);
static void handleGlobal(void *data, struct wl_registry *registry, uint32_t name,
const char *interface, uint32_t version);
static void handleGlobalRemove(void *data, 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_;
std::string css_file_;
std::string config_file_;
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

@ -3,29 +3,43 @@
#include <json/json.h>
#include "modules/clock.hpp"
#ifdef HAVE_SWAY
#include "modules/sway/workspaces.hpp"
#include "modules/sway/mode.hpp"
#include "modules/sway/window.hpp"
#include "modules/sway/workspaces.hpp"
#endif
#include "modules/battery.hpp"
#include "modules/memory.hpp"
#include "modules/cpu.hpp"
#include "modules/idle_inhibitor.hpp"
#include "modules/memory.hpp"
#ifdef HAVE_DBUSMENU
#include "modules/sni/tray.hpp"
#endif
#ifdef HAVE_LIBNL
#include "modules/network.hpp"
#endif
#ifdef HAVE_LIBUDEV
#include "modules/backlight.hpp"
#endif
#ifdef HAVE_LIBPULSE
#include "modules/pulseaudio.hpp"
#endif
#ifdef HAVE_LIBMPDCLIENT
#include "modules/mpd.hpp"
#endif
#include "bar.hpp"
#include "modules/custom.hpp"
#include "modules/temperature.hpp"
namespace waybar {
class Factory {
public:
Factory(Bar& bar, const Json::Value& config);
IModule* makeModule(const std::string &name) const;
private:
Bar& bar_;
const Json::Value& config_;
public:
Factory(const Bar& bar, const Json::Value& config);
IModule* makeModule(const std::string& name) const;
private:
const Bar& bar_;
const Json::Value& config_;
};
}
} // namespace waybar

View File

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

View File

@ -1,31 +1,48 @@
#pragma once
#ifdef FILESYSTEM_EXPERIMENTAL
#include <experimental/filesystem>
#else
#include <filesystem>
#include <fstream>
#include <iostream>
#endif
#include <fmt/format.h>
#include <sys/inotify.h>
#include <algorithm>
#include "util/chrono.hpp"
#include <fstream>
#include <iostream>
#include "ALabel.hpp"
#include "util/sleeper_thread.hpp"
namespace waybar::modules {
#ifdef FILESYSTEM_EXPERIMENTAL
namespace fs = std::experimental::filesystem;
#else
namespace fs = std::filesystem;
#endif
class Battery : public ALabel {
public:
Battery(const Json::Value&);
~Battery();
auto update() -> void;
private:
static inline const fs::path data_dir_ = "/sys/class/power_supply/";
public:
Battery(const std::string&, const Json::Value&);
~Battery();
auto update() -> void;
void worker();
private:
static inline const fs::path data_dir_ = "/sys/class/power_supply/";
util::SleeperThread thread_;
std::vector<fs::path> batteries_;
int fd_;
void getBatteries();
void worker();
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_;
util::SleeperThread thread_timer_;
std::vector<fs::path> batteries_;
fs::path adapter_;
int fd_;
std::vector<int> wds_;
std::string old_status_;
};
}
} // namespace waybar::modules

View File

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

View File

@ -2,17 +2,29 @@
#include <fmt/format.h>
#include <sys/sysinfo.h>
#include "util/chrono.hpp"
#include <fstream>
#include <iostream>
#include <numeric>
#include <vector>
#include "ALabel.hpp"
#include "util/sleeper_thread.hpp"
namespace waybar::modules {
class Cpu : public ALabel {
public:
Cpu(const Json::Value&);
auto update() -> void;
private:
waybar::util::SleeperThread thread_;
public:
Cpu(const std::string&, const Json::Value&);
~Cpu() = default;
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_;
waybar::util::SleeperThread thread_;
};
}
} // namespace waybar::modules

View File

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

View File

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

View File

@ -1,18 +1,24 @@
#pragma once
#include <fmt/format.h>
#include <sys/sysinfo.h>
#include "util/chrono.hpp"
#include <fstream>
#include "ALabel.hpp"
#include "util/sleeper_thread.hpp"
namespace waybar::modules {
class Memory : public ALabel {
public:
Memory(const Json::Value&);
auto update() -> void;
private:
waybar::util::SleeperThread thread_;
public:
Memory(const std::string&, const Json::Value&);
~Memory() = default;
auto update() -> void;
private:
static inline const std::string data_dir_ = "/proc/meminfo";
unsigned long memtotal_;
unsigned long memfree_;
void parseMeminfo();
waybar::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,46 +1,63 @@
#pragma once
#include <net/if.h>
#include <netlink/netlink.h>
#include <netlink/genl/genl.h>
#include <netlink/genl/ctrl.h>
#include <linux/nl80211.h>
#include <arpa/inet.h>
#include <fmt/format.h>
#include "util/chrono.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 "util/sleeper_thread.hpp"
namespace waybar::modules {
class Network : public ALabel {
public:
Network(const Json::Value&);
~Network();
auto update() -> void;
private:
static uint64_t netlinkRequest(int, void*, uint32_t, uint32_t groups = 0);
static uint64_t netlinkResponse(int, void*, uint32_t, uint32_t groups = 0);
static int scanCb(struct nl_msg*, void*);
public:
Network(const std::string&, const Json::Value&);
~Network();
auto update() -> void;
void disconnected();
void initNL80211();
int getExternalInterface();
void parseEssid(struct nlattr**);
void parseSignal(struct nlattr**);
bool associatedOrJoined(struct nlattr**);
auto getInfo() -> void;
private:
static const uint8_t MAX_RETRY = 5;
static const uint8_t EPOLL_MAX = 200;
waybar::util::SleeperThread thread_;
int ifid_;
sa_family_t family_;
int sock_fd_;
struct sockaddr_nl nladdr_ = {0};
struct nl_sock* sk_ = nullptr;
int nl80211_id_;
static int handleEvents(struct nl_msg*, void*);
static int handleScan(struct nl_msg*, void*);
std::string essid_;
std::string ifname_;
int signal_strength_dbm_;
uint16_t signal_strength_;
void worker();
void disconnected();
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_;
waybar::util::SleeperThread thread_timer_;
int ifid_;
sa_family_t family_;
struct sockaddr_nl nladdr_ = {0};
struct nl_sock* sk_ = nullptr;
struct nl_sock* info_sock_ = nullptr;
int efd_;
int ev_fd_;
int nl80211_id_;
std::string essid_;
std::string ifname_;
std::string ipaddr_;
std::string netmask_;
int cidr_;
int32_t signal_strength_dbm_;
uint8_t signal_strength_;
};
}
} // namespace waybar::modules

View File

@ -1,31 +1,39 @@
#pragma once
#include <pulse/pulseaudio.h>
#include <fmt/format.h>
#include <pulse/pulseaudio.h>
#include <pulse/volume.h>
#include <algorithm>
#include "ALabel.hpp"
namespace waybar::modules {
class Pulseaudio : public ALabel {
public:
Pulseaudio(const Json::Value&);
~Pulseaudio();
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*);
public:
Pulseaudio(const std::string&, const Json::Value&);
~Pulseaudio();
auto update() -> void;
pa_threaded_mainloop* mainloop_;
pa_mainloop_api* mainloop_api_;
pa_context* context_;
uint32_t sink_idx_{0};
uint16_t volume_;
bool muted_;
std::string desc_;
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;
pa_threaded_mainloop* mainloop_;
pa_mainloop_api* mainloop_api_;
pa_context* context_;
uint32_t sink_idx_{0};
uint16_t volume_;
pa_cvolume pa_volume_;
bool muted_;
std::string port_name_;
std::string desc_;
bool scrolling_;
};
}
} // namespace waybar::modules

View File

@ -0,0 +1,43 @@
#pragma once
#include <dbus-status-notifier-watcher.h>
#include <giomm.h>
#include <glibmm/refptr.h>
#include <json/json.h>
#include <tuple>
#include "modules/sni/item.hpp"
namespace waybar::modules::SNI {
class Host {
public:
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>&)>&);
~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);
void addRegisteredItem(std::string service);
std::vector<std::unique_ptr<Item>> items_;
const std::string bus_name_;
const std::string object_path_;
std::size_t bus_name_id_;
std::size_t watcher_id_;
GCancellable* cancellable_ = nullptr;
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

@ -0,0 +1,71 @@
#pragma once
#include <dbus-status-notifier-item.h>
#include <giomm/dbusproxy.h>
#include <glibmm/refptr.h>
#include <gtkmm/eventbox.h>
#include <gtkmm/icontheme.h>
#include <gtkmm/image.h>
#include <gtkmm/menu.h>
#include <json/json.h>
#include <libdbusmenu-gtk/dbusmenu-gtk.h>
#include <sigc++/trackable.h>
#ifdef FILESYSTEM_EXPERIMENTAL
#include <experimental/filesystem>
#else
#include <filesystem>
#endif
namespace waybar::modules::SNI {
class Item : public sigc::trackable {
public:
Item(const std::string&, const std::string&, const Json::Value&);
~Item() = default;
std::string bus_name;
std::string object_path;
int icon_size;
int effective_icon_size;
Gtk::Image image;
Gtk::EventBox event_box;
std::string category;
std::string id;
std::string status;
std::string title;
int32_t window_id;
std::string icon_name;
Glib::RefPtr<Gdk::Pixbuf> icon_pixmap;
Glib::RefPtr<Gtk::IconTheme> icon_theme;
std::string overlay_icon_name;
std::string attention_icon_name;
std::string attention_movie_name;
std::string icon_theme_path;
std::string menu;
DbusmenuGtkMenu* dbus_menu = nullptr;
Gtk::Menu* gtk_menu = nullptr;
bool item_is_menu;
private:
void proxyReady(Glib::RefPtr<Gio::AsyncResult>& result);
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();
Glib::RefPtr<Gdk::Pixbuf> extractPixBuf(GVariant* variant);
Glib::RefPtr<Gdk::Pixbuf> getIconByName(const std::string& name, int size);
static void onMenuDestroyed(Item* self);
bool makeMenu(GdkEventButton* const& ev);
bool handleClick(GdkEventButton* const& /*ev*/);
Glib::RefPtr<Gio::Cancellable> cancellable_;
Glib::RefPtr<Gio::DBus::Proxy> proxy_;
bool update_pending_;
};
} // namespace waybar::modules::SNI

View File

@ -0,0 +1,30 @@
#pragma once
#include <fmt/format.h>
#include "IModule.hpp"
#include "bar.hpp"
#include "modules/sni/host.hpp"
#include "modules/sni/watcher.hpp"
#include "util/json.hpp"
namespace waybar::modules::SNI {
class Tray : public IModule {
public:
Tray(const std::string&, const Bar&, const Json::Value&);
~Tray() = default;
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;
const Json::Value& config_;
Gtk::Box box_;
SNI::Watcher watcher_;
SNI::Host host_;
};
} // namespace waybar::modules::SNI

View File

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

View File

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

View File

@ -3,30 +3,30 @@
#define event_mask(ev) (1u << (ev & 0x7F))
enum ipc_command_type {
// i3 command types - see i3's I3_REPLY_TYPE constants
IPC_COMMAND = 0,
IPC_GET_WORKSPACES = 1,
IPC_SUBSCRIBE = 2,
IPC_GET_OUTPUTS = 3,
IPC_GET_TREE = 4,
IPC_GET_MARKS = 5,
IPC_GET_BAR_CONFIG = 6,
IPC_GET_VERSION = 7,
IPC_GET_BINDING_MODES = 8,
IPC_GET_CONFIG = 9,
IPC_SEND_TICK = 10,
// i3 command types - see i3's I3_REPLY_TYPE constants
IPC_COMMAND = 0,
IPC_GET_WORKSPACES = 1,
IPC_SUBSCRIBE = 2,
IPC_GET_OUTPUTS = 3,
IPC_GET_TREE = 4,
IPC_GET_MARKS = 5,
IPC_GET_BAR_CONFIG = 6,
IPC_GET_VERSION = 7,
IPC_GET_BINDING_MODES = 8,
IPC_GET_CONFIG = 9,
IPC_SEND_TICK = 10,
// sway-specific command types
IPC_GET_INPUTS = 100,
IPC_GET_SEATS = 101,
// sway-specific command types
IPC_GET_INPUTS = 100,
IPC_GET_SEATS = 101,
// Events sent from sway to clients. Events have the highest bits set.
IPC_EVENT_WORKSPACE = ((1<<31) | 0),
IPC_EVENT_OUTPUT = ((1<<31) | 1),
IPC_EVENT_MODE = ((1<<31) | 2),
IPC_EVENT_WINDOW = ((1<<31) | 3),
IPC_EVENT_BARCONFIG_UPDATE = ((1<<31) | 4),
IPC_EVENT_BINDING = ((1<<31) | 5),
IPC_EVENT_SHUTDOWN = ((1<<31) | 6),
IPC_EVENT_TICK = ((1<<31) | 7),
// Events sent from sway to clients. Events have the highest bits set.
IPC_EVENT_WORKSPACE = ((1 << 31) | 0),
IPC_EVENT_OUTPUT = ((1 << 31) | 1),
IPC_EVENT_MODE = ((1 << 31) | 2),
IPC_EVENT_WINDOW = ((1 << 31) | 3),
IPC_EVENT_BARCONFIG_UPDATE = ((1 << 31) | 4),
IPC_EVENT_BINDING = ((1 << 31) | 5),
IPC_EVENT_SHUTDOWN = ((1 << 31) | 6),
IPC_EVENT_TICK = ((1 << 31) | 7),
};

View File

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

View File

@ -1,29 +1,34 @@
#pragma once
#include <fmt/format.h>
#include <tuple>
#include "ALabel.hpp"
#include "bar.hpp"
#include "client.hpp"
#include "util/chrono.hpp"
#include "util/json.hpp"
#include "ALabel.hpp"
#include "modules/sway/ipc/client.hpp"
#include "util/sleeper_thread.hpp"
namespace waybar::modules::sway {
class Window : public ALabel {
public:
Window(waybar::Bar&, const Json::Value&);
auto update() -> void;
private:
void worker();
std::string getFocusedNode(Json::Value nodes);
void getFocusedWindow();
public:
Window(const std::string&, const waybar::Bar&, const Json::Value&);
~Window() = default;
auto update() -> void;
Bar& bar_;
waybar::util::SleeperThread thread_;
util::JsonParser parser_;
Ipc ipc_;
std::string window_;
private:
void onEvent(const struct Ipc::ipc_response&);
void onCmd(const struct Ipc::ipc_response&);
void worker();
std::tuple<std::size_t, int, std::string, std::string> getFocusedNode(const Json::Value& nodes);
void getTree();
const Bar& bar_;
waybar::util::SleeperThread thread_;
Ipc ipc_;
std::string window_;
int windowId_;
std::string app_id_;
};
}
} // namespace waybar::modules::sway

View File

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

View File

@ -0,0 +1,29 @@
#pragma once
#include <fmt/format.h>
#include <fstream>
#include "ALabel.hpp"
#include "util/sleeper_thread.hpp"
#ifdef FILESYSTEM_EXPERIMENTAL
#include <experimental/filesystem>
#else
#include <filesystem>
#endif
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_;
waybar::util::SleeperThread thread_;
};
} // namespace waybar::modules

View File

@ -1,87 +0,0 @@
#pragma once
#include <chrono>
#include <ctime>
#include <functional>
#include <condition_variable>
#include <thread>
#include <gtkmm.h>
namespace waybar::chrono {
using namespace std::chrono;
using clock = std::chrono::system_clock;
using duration = clock::duration;
using time_point = std::chrono::time_point<clock, duration>;
}
namespace waybar::util {
struct SleeperThread {
SleeperThread() = default;
SleeperThread(std::function<void()> func)
: thread_{[this, func] {
while(true) {
{
std::lock_guard<std::mutex> lock(mutex_);
if (!do_run_) {
break;
}
}
func();
}
}}
{}
SleeperThread& operator=(std::function<void()> func)
{
thread_ = std::thread([this, func] {
while(true) {
{
std::lock_guard<std::mutex> lock(mutex_);
if (!do_run_) {
break;
}
}
func();
}
});
return *this;
}
auto sleep_for(chrono::duration dur)
{
auto lock = std::unique_lock(mutex_);
return condvar_.wait_for(lock, dur);
}
auto sleep_until(chrono::time_point time)
{
auto lock = std::unique_lock(mutex_);
return condvar_.wait_until(lock, time);
}
auto wake_up()
{
condvar_.notify_all();
}
~SleeperThread()
{
do_run_ = false;
condvar_.notify_all();
thread_.detach();
}
private:
std::thread thread_;
std::condition_variable condvar_;
std::mutex mutex_;
bool do_run_ = true;
};
}

1264
include/util/clara.hpp Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,23 +1,19 @@
#pragma once
#include <giomm.h>
#include <sys/wait.h>
#include <unistd.h>
namespace waybar::util::command {
struct cmd_res {
int exit_code;
struct res {
int exit_code;
std::string out;
};
inline struct cmd_res exec(const std::string cmd)
{
FILE* fp(popen(cmd.c_str(), "r"));
if (!fp) {
return { -1, "" };
}
inline std::string read(FILE* fp) {
std::array<char, 128> buffer = {0};
std::string output;
std::string output;
while (feof(fp) == 0) {
if (fgets(buffer.data(), 128, fp) != nullptr) {
output += buffer.data();
@ -25,11 +21,78 @@ inline struct cmd_res exec(const std::string cmd)
}
// Remove last newline
if (!output.empty() && output[output.length()-1] == '\n') {
output.erase(output.length()-1);
if (!output.empty() && output[output.length() - 1] == '\n') {
output.erase(output.length() - 1);
}
int exit_code = WEXITSTATUS(pclose(fp));
return { exit_code, output };
return output;
}
inline int close(FILE* fp, pid_t pid) {
int stat;
fclose(fp);
while (waitpid(pid, &stat, 0) == -1) {
if (errno != EINTR) {
stat = -1;
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();
if (pid < 0) {
printf("Unable to exec cmd %s, error %s", cmd.c_str(), strerror(errno));
return pid;
}
// Child executes the command
if (!pid) {
setpgid(pid, pid);
execl("/bin/sh", "sh", "-c", cmd.c_str(), (char*)0);
exit(0);
}
return pid;
}
} // namespace waybar::util::command

View File

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

View File

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

View File

@ -1,8 +1,12 @@
project(
'waybar', 'cpp', 'c',
version: '0.0.5',
version: '0.6.0',
license: 'MIT',
default_options : ['cpp_std=c++17'],
default_options : [
'cpp_std=c++17',
'buildtype=release',
'default_library=static'
],
)
cpp_args = []
@ -14,26 +18,45 @@ if false # libc++
cpp_link_args += ['-lc++fs']
else
# TODO: For std::filesystem in libstdc++. Still unstable? Or why is it not in libstdc++ proper yet?
cpp_link_args += ['-lstdc++fs']
endif
compiler = meson.get_compiler('cpp')
git = find_program('git', required: false)
if not git.found()
add_project_arguments('-DVERSION="@0@"'.format(meson.project_version()), language: 'cpp')
else
git_commit_hash = run_command([git.path(), 'describe', '--always', '--tags']).stdout().strip()
git_branch = run_command([git.path(), 'rev-parse', '--abbrev-ref', 'HEAD']).stdout().strip()
version = '"@0@ (" __DATE__ ", branch \'@1@\')"'.format(git_commit_hash, git_branch)
add_project_arguments('-DVERSION=@0@'.format(version), language: 'cpp')
endif
if not compiler.has_header('filesystem')
add_project_arguments('-DFILESYSTEM_EXPERIMENTAL', language: 'cpp')
endif
add_global_arguments(cpp_args, language : 'cpp')
add_global_link_arguments(cpp_link_args, language : 'cpp')
thread_dep = dependency('threads')
libinput = dependency('libinput')
fmt = dependency('fmt', fallback: ['fmtlib', 'fmt_dep'])
fmt = dependency('fmt', version : ['>=5.3.0'], fallback : ['fmt', 'fmt_dep'])
wayland_client = dependency('wayland-client')
wayland_cursor = dependency('wayland-cursor')
wayland_protos = dependency('wayland-protocols')
wlroots = dependency('wlroots', fallback: ['wlroots', 'wlroots'])
gtkmm = dependency('gtkmm-3.0')
dbusmenu_gtk = dependency('dbusmenu-gtk3-0.4', required: get_option('dbusmenu-gtk'))
giounix = dependency('gio-unix-2.0', required: get_option('dbusmenu-gtk'))
jsoncpp = dependency('jsoncpp')
sigcpp = dependency('sigc++-2.0')
libnl = dependency('libnl-3.0', required: false)
libnlgen = dependency('libnl-genl-3.0', required: false)
libpulse = dependency('libpulse', required: false)
libnl = dependency('libnl-3.0', required: get_option('libnl'))
libnlgen = dependency('libnl-genl-3.0', required: get_option('libnl'))
libpulse = dependency('libpulse', required: get_option('pulseaudio'))
libudev = dependency('libudev', required: get_option('libudev'))
libmpdclient = dependency('libmpdclient', required: get_option('mpd'))
src_files = files(
'src/factory.cpp',
@ -43,15 +66,18 @@ src_files = files(
'src/modules/clock.cpp',
'src/modules/custom.cpp',
'src/modules/cpu.cpp',
'src/modules/idle_inhibitor.cpp',
'src/modules/temperature.cpp',
'src/main.cpp',
'src/bar.cpp',
'src/client.cpp'
)
if find_program('sway').found()
if true # find_program('sway', required : false).found()
add_project_arguments('-DHAVE_SWAY', language: 'cpp')
src_files += [
'src/modules/sway/ipc/client.cpp',
'src/modules/sway/mode.cpp',
'src/modules/sway/window.cpp',
'src/modules/sway/workspaces.cpp'
]
@ -67,6 +93,26 @@ if libpulse.found()
src_files += 'src/modules/pulseaudio.cpp'
endif
if dbusmenu_gtk.found()
add_project_arguments('-DHAVE_DBUSMENU', language: 'cpp')
src_files += files(
'src/modules/sni/tray.cpp',
'src/modules/sni/watcher.cpp',
'src/modules/sni/host.cpp',
'src/modules/sni/item.cpp'
)
endif
if libudev.found()
add_project_arguments('-DHAVE_LIBUDEV', language: 'cpp')
src_files += 'src/modules/backlight.cpp'
endif
if libmpdclient.found()
add_project_arguments('-DHAVE_LIBMPDCLIENT', language: 'cpp')
src_files += 'src/modules/mpd.cpp'
endif
subdir('protocol')
executable(
@ -83,9 +129,13 @@ executable(
libinput,
wayland_cursor,
gtkmm,
dbusmenu_gtk,
giounix,
libnl,
libnlgen,
libpulse,
libudev,
libmpdclient
],
include_directories: [include_directories('include')],
install: true,
@ -94,7 +144,7 @@ executable(
install_data(
'./resources/config',
'./resources/style.css',
install_dir: '/etc/xdg/waybar',
install_dir: join_paths(get_option('out'), 'etc/xdg/waybar')
)
clangtidy = find_program('clang-tidy', required: false)

6
meson_options.txt Normal file
View File

@ -0,0 +1,6 @@
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('pulseaudio', type: 'feature', value: 'auto', description: 'Enable support for pulseaudio')
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')

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 22 KiB

69
protocol/dbus-menu.xml Normal file
View File

@ -0,0 +1,69 @@
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<interface name="com.canonical.dbusmenu">
<!-- Properties -->
<property name="Version" type="u" access="read" />
<property name="TextDirection" type="s" access="read" />
<property name="Status" type="s" access="read" />
<property name="IconThemePath" type="as" access="read" />
<!-- Functions -->
<method name="GetLayout">
<arg type="i" name="parentId" direction="in" />
<arg type="i" name="recursionDepth" direction="in" />
<arg type="as" name="propertyNames" direction="in" />
<arg type="u" name="revision" direction="out" />
<arg type="(ia{sv}av)" name="layout" direction="out" />
</method>
<method name="GetGroupProperties">
<arg type="ai" name="ids" direction="in" />
<arg type="as" name="propertyNames" direction="in" />
<arg type="a(ia{sv})" name="properties" direction="out" />
</method>
<method name="GetProperty">
<arg type="i" name="id" direction="in" />
<arg type="s" name="name" direction="in" />
<arg type="v" name="value" direction="out" />
</method>
<method name="Event">
<arg type="i" name="id" direction="in" />
<arg type="s" name="eventId" direction="in" />
<arg type="v" name="data" direction="in" />
<arg type="u" name="timestamp" direction="in" />
</method>
<method name="EventGroup">
<arg type="a(isvu)" name="events" direction="in" />
<arg type="ai" name="idErrors" direction="out" />
</method>
<method name="AboutToShow">
<arg type="i" name="id" direction="in" />
<arg type="b" name="needUpdate" direction="out" />
</method>
<method name="AboutToShowGroup">
<arg type="ai" name="ids" direction="in" />
<arg type="ai" name="updatesNeeded" direction="out" />
<arg type="ai" name="idErrors" direction="out" />
</method>
<!-- Signals -->
<signal name="ItemsPropertiesUpdated">
<arg type="a(ia{sv})" name="updatedProps" direction="out" />
<arg type="a(ias)" name="removedProps" direction="out" />
</signal>
<signal name="LayoutUpdated">
<arg type="u" name="revision" direction="out" />
<arg type="i" name="parent" direction="out" />
</signal>
<signal name="ItemActivationRequested">
<arg type="i" name="id" direction="out" />
<arg type="u" name="timestamp" direction="out" />
</signal>
</interface>
</node>

View File

@ -0,0 +1,47 @@
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<interface name='org.kde.StatusNotifierItem'>
<annotation name="org.gtk.GDBus.C.Name" value="Item" />
<method name='ContextMenu'>
<arg type='i' direction='in' name='x'/>
<arg type='i' direction='in' name='y'/>
</method>
<method name='Activate'>
<arg type='i' direction='in' name='x'/>
<arg type='i' direction='in' name='y'/>
</method>
<method name='SecondaryActivate'>
<arg type='i' direction='in' name='x'/>
<arg type='i' direction='in' name='y'/>
</method>
<method name='Scroll'>
<arg type='i' direction='in' name='delta'/>
<arg type='s' direction='in' name='orientation'/>
</method>
<signal name='NewTitle'/>
<signal name='NewIcon'/>
<signal name='NewAttentionIcon'/>
<signal name='NewOverlayIcon'/>
<signal name='NewToolTip'/>
<signal name='NewStatus'>
<arg type='s' name='status'/>
</signal>
<property name='Category' type='s' access='read'/>
<property name='Id' type='s' access='read'/>
<property name='Title' type='s' access='read'/>
<property name='Status' type='s' access='read'/>
<property name='WindowId' type='u' access='read'/>
<property name='IconThemePath' type='s' access='read'/>
<property name='IconName' type='s' access='read'/>
<property name='IconPixmap' type='a(iiay)' access='read'/>
<property name='OverlayIconName' type='s' access='read'/>
<property name='OverlayIconPixmap' type='a(iiay)' access='read'/>
<property name='AttentionIconName' type='s' access='read'/>
<property name='AttentionIconPixmap' type='a(iiay)' access='read'/>
<property name='AttentionMovieName' type='s' access='read'/>
<property name='ToolTip' type='(sa(iiay)ss)' access='read'/>
<property name='Menu' type='o' access='read'/>
<property name='ItemIsMenu' type='b' access='read'/>
</interface>
</node>

View File

@ -0,0 +1,52 @@
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<interface name="org.kde.StatusNotifierWatcher">
<annotation name="org.gtk.GDBus.C.Name" value="Watcher" />
<!-- methods -->
<method name="RegisterStatusNotifierItem">
<annotation name="org.gtk.GDBus.C.Name" value="RegisterItem" />
<arg name="service" type="s" direction="in"/>
</method>
<method name="RegisterStatusNotifierHost">
<annotation name="org.gtk.GDBus.C.Name" value="RegisterHost" />
<arg name="service" type="s" direction="in"/>
</method>
<!-- properties -->
<property name="RegisteredStatusNotifierItems" type="as" access="read">
<annotation name="org.gtk.GDBus.C.Name" value="RegisteredItems" />
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QStringList"/>
</property>
<property name="IsStatusNotifierHostRegistered" type="b" access="read">
<annotation name="org.gtk.GDBus.C.Name" value="IsHostRegistered" />
</property>
<property name="ProtocolVersion" type="i" access="read"/>
<!-- signals -->
<signal name="StatusNotifierItemRegistered">
<annotation name="org.gtk.GDBus.C.Name" value="ItemRegistered" />
<arg type="s" direction="out" name="service" />
</signal>
<signal name="StatusNotifierItemUnregistered">
<annotation name="org.gtk.GDBus.C.Name" value="ItemUnregistered" />
<arg type="s" direction="out" name="service" />
</signal>
<signal name="StatusNotifierHostRegistered">
<annotation name="org.gtk.GDBus.C.Name" value="HostRegistered" />
</signal>
<signal name="StatusNotifierHostUnregistered">
<annotation name="org.gtk.GDBus.C.Name" value="HostUnregistered" />
</signal>
</interface>
</node>

View File

@ -24,6 +24,7 @@ wayland_scanner_client = generator(
client_protocols = [
[wl_protocol_dir, 'stable/xdg-shell/xdg-shell.xml'],
[wl_protocol_dir, 'unstable/xdg-output/xdg-output-unstable-v1.xml'],
[wl_protocol_dir, 'unstable/idle-inhibit/idle-inhibit-unstable-v1.xml'],
['wlr-layer-shell-unstable-v1.xml'],
]
@ -36,10 +37,66 @@ foreach p : client_protocols
client_protos_headers += wayland_scanner_client.process(xml)
endforeach
gdbus_codegen = find_program('gdbus-codegen')
r = run_command(gdbus_codegen, '--body', '--output', '/dev/null')
if r.returncode() != 0
gdbus_code_dsnw = custom_target(
'dbus-status-notifier-watcher.[ch]',
output: ['@BASENAME@.c','@BASENAME@.h'],
input: './dbus-status-notifier-watcher.xml',
command: [gdbus_codegen,'--c-namespace', 'Sn', '--generate-c-code', 'protocol/@BASENAME@', '@INPUT@'],
)
gdbus_code_dsni = custom_target(
'dbus-status-notifier-item.[ch]',
output: ['@BASENAME@.c','@BASENAME@.h'],
input: './dbus-status-notifier-item.xml',
command: [gdbus_codegen,'--c-namespace', 'Sn', '--generate-c-code', 'protocol/@BASENAME@', '@INPUT@'],
)
gdbus_code_dm = custom_target(
'dbus-menu.[ch]',
output: ['@BASENAME@.c','@BASENAME@.h'],
input: './dbus-menu.xml',
command: [gdbus_codegen,'--c-namespace', 'Sn', '--generate-c-code', 'protocol/@BASENAME@', '@INPUT@'],
)
client_protos_src += gdbus_code_dsnw[0]
client_protos_headers += gdbus_code_dsnw[1]
client_protos_src += gdbus_code_dsni[0]
client_protos_headers += gdbus_code_dsni[1]
client_protos_src += gdbus_code_dm[0]
client_protos_headers += gdbus_code_dm[1]
else
gdbus_code = generator(
gdbus_codegen,
output: '@BASENAME@.c',
arguments: ['--c-namespace', 'Sn', '--body', '--output', '@OUTPUT@', '@INPUT@']
)
gdbus_header = generator(
gdbus_codegen,
output: '@BASENAME@.h',
arguments: ['--c-namespace', 'Sn', '--header', '--output', '@OUTPUT@', '@INPUT@']
)
client_protos_src += gdbus_code.process('./dbus-status-notifier-watcher.xml')
client_protos_headers += gdbus_header.process('./dbus-status-notifier-watcher.xml')
client_protos_src += gdbus_code.process('./dbus-status-notifier-item.xml')
client_protos_headers += gdbus_header.process('./dbus-status-notifier-item.xml')
client_protos_src += gdbus_code.process('./dbus-menu.xml')
client_protos_headers += gdbus_header.process('./dbus-menu.xml')
endif
lib_client_protos = static_library(
'client_protos',
client_protos_src + client_protos_headers,
dependencies: [wayland_client]
dependencies: [wayland_client, gtkmm, giounix],
include_directories: include_directories('..'),
) # for the include directory
client_protos = declare_dependency(

View File

@ -1,55 +1,136 @@
{
"layer": "top", // Waybar at top layer
// "position": "bottom", // Waybar at the bottom of your screen
// "height": 30, // Waybar height
// "position": "bottom", // Waybar position (top|bottom|left|right)
"height": 30, // Waybar height (to be removed for auto height)
// "width": 1280, // Waybar width
// Choose the order of the modules
"modules-left": ["sway/workspaces", "custom/spotify"],
"modules-left": ["sway/workspaces", "sway/mode", "custom/media"],
"modules-center": ["sway/window"],
"modules-right": ["pulseaudio", "network", "cpu", "memory", "battery", "clock"],
"modules-right": ["mpd", "idle_inhibitor", "pulseaudio", "network", "cpu", "memory", "temperature", "backlight", "battery", "battery#bat2", "clock", "tray"],
// Modules configuration
"sway/workspaces": {
// "disable-scroll": true,
// "all-outputs": true,
// "format-icons": {
// "1": "",
// "2": "",
// "3": "",
// "4": "",
// "5": ""
// }
// "sway/workspaces": {
// "disable-scroll": true,
// "all-outputs": true,
// "format": "{name}: {icon}",
// "format-icons": {
// "1": "",
// "2": "",
// "3": "",
// "4": "",
// "5": "",
// "urgent": "",
// "focused": "",
// "default": ""
// }
// },
"sway/mode": {
"format": "<span style=\"italic\">{}</span>"
},
"sway/window": {
"max-length": 50
"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": {
"format": "{icon}",
"format-icons": {
"activated": "",
"deactivated": ""
}
},
"tray": {
// "icon-size": 21,
"spacing": 10
},
"clock": {
"tooltip-format": "{:%Y-%m-%d | %H:%M}",
"format-alt": "{:%Y-%m-%d}"
},
"cpu": {
"format": "{}% "
"format": "{usage}% ",
"tooltip": false
},
"memory": {
"format": "{}% "
},
"temperature": {
// "thermal-zone": 2,
// "hwmon-path": "/sys/class/hwmon/hwmon2/temp1_input",
"critical-threshold": 80,
// "format-critical": "{temperatureC}°C ",
"format": "{temperatureC}°C "
},
"backlight": {
// "device": "acpi_video1",
"format": "{percent}% {icon}",
"format-icons": ["", ""]
},
"battery": {
"states": {
// "good": 95,
"warning": 30,
"critical": 15
},
"format": "{capacity}% {icon}",
// "format-good": "", // An empty format will hide the module
// "format-full": "",
"format-icons": ["", "", "", "", ""]
},
"battery#bat2": {
"bat": "BAT2"
},
"network": {
// "interface": "wlp2s0", // (Optional) To force the use of this interface
"format-wifi": "{essid} ({signalStrength}%) ",
"format-ethernet": "{ifname} ",
"format-ethernet": "{ifname}: {ipaddr}/{cidr} ",
"format-disconnected": "Disconnected ⚠"
},
"pulseaudio": {
//"scroll-step": 1,
"format": "{volume}% {icon}",
"format-bluetooth": "{volume}% {icon}",
"format-muted": "",
"format-icons": ["", ""]
"format-icons": {
"headphones": "",
"handsfree": "",
"headset": "",
"phone": "",
"portable": "",
"car": "",
"default": ["", ""]
},
"on-click": "pavucontrol"
},
"custom/spotify": {
"format": " {}",
"custom/media": {
"format": "{icon} {}",
"return-type": "json",
"max-length": 40,
"exec": "$HOME/.config/waybar/mediaplayer.sh", // Script in resources folder
"exec-if": "pgrep spotify"
"format-icons": {
"spotify": "",
"default": "🎜"
},
"escape": true,
"exec": "$HOME/.config/waybar/mediaplayer.py 2> /dev/null" // Script in resources folder
}
}

View File

@ -0,0 +1,126 @@
#!/usr/bin/env python3
import argparse
import logging
import sys
import signal
import gi
import json
gi.require_version('Playerctl', '2.0')
from gi.repository import Playerctl, GLib
logger = logging.getLogger(__name__)
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):
logger.info('Received new playback status')
on_metadata(player, player.props.metadata, manager)
def on_metadata(player, metadata, manager):
logger.info('Received new metadata')
track_info = ''
if player.props.player_name == 'spotify' and \
'mpris:trackid' in metadata.keys() and \
':ad:' in player.props.metadata['mpris:trackid']:
track_info = 'AD PLAYING'
elif player.get_artist() != '' and player.get_title() != '':
track_info = '{artist} - {title}'.format(artist=player.get_artist(),
title=player.get_title())
if player.props.status != 'Playing':
track_info = '' + track_info
write_output(track_info, player)
def on_player_appeared(manager, player, selected_player=None):
if player is not None and player.name == selected_player:
init_player(manager, player)
else:
logger.debug("New player appeared, but it's not the selected player, skipping")
def on_player_vanished(manager, player):
logger.info('Player has vanished')
sys.stdout.write('\n')
sys.stdout.flush()
def init_player(manager, name):
logger.debug('Initialize player: {player}'.format(player=name.name))
player = Playerctl.Player.new_from_name(name)
player.connect('playback-status', on_play, manager)
player.connect('metadata', on_metadata, manager)
manager.manage_player(player)
on_metadata(player, player.props.metadata, manager)
def signal_handler(sig, frame):
logger.debug('Received signal to stop, exiting')
sys.stdout.write('\n')
sys.stdout.flush()
# loop.quit()
sys.exit(0)
def parse_arguments():
parser = argparse.ArgumentParser()
# Increase verbosity with every occurance of -v
parser.add_argument('-v', '--verbose', action='count', default=0)
# Define for which player we're listening
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()

View File

@ -1,7 +0,0 @@
#!/bin/sh
player_status=$(playerctl status 2> /dev/null)
if [ "$player_status" = "Playing" ]; then
echo "$(playerctl metadata artist) - $(playerctl metadata title)"
elif [ "$player_status" = "Paused" ]; then
echo "$(playerctl metadata artist) - $(playerctl metadata title)"
fi

View File

@ -3,33 +3,63 @@
border-radius: 0;
font-family: Roboto, Helvetica, Arial, sans-serif;
font-size: 13px;
min-height: 0;
}
window {
window#waybar {
background: rgba(43, 48, 59, 0.5);
border-bottom: 3px solid rgba(100, 114, 125, 0.5);
color: white;
color: #ffffff;
}
window#waybar.hidded {
opacity: 0.2;
}
/*
window#waybar.empty {
background: transparent;
}
window#waybar.solo {
background: #FFFFFF;
}
*/
window#waybar.termite {
background-color: #3F3F3F;
}
window#waybar.chromium {
background-color: #DEE1E6;
color: #000000;
}
/* https://github.com/Alexays/Waybar/wiki/FAQ#the-workspace-buttons-have-a-strange-hover-effect */
#workspaces button {
padding: 0 5px;
background: transparent;
color: white;
color: #ffffff;
border-bottom: 3px solid transparent;
}
#workspaces button.icon label {
font-size: 10px;
}
#workspaces button.focused {
background: #64727D;
border-bottom: 3px solid white;
border-bottom: 3px solid #ffffff;
}
#clock, #battery, #cpu, #memory, #network, #pulseaudio, #custom-spotify {
#workspaces button.urgent {
background-color: #eb4d4b;
}
#mode {
background: #64727D;
border-bottom: 3px solid #ffffff;
}
#clock, #battery, #cpu, #memory, #temperature, #backlight, #network, #pulseaudio, #custom-media, #tray, #mode, #idle_inhibitor {
padding: 0 10px;
margin: 0 5px;
color: #ffffff;
}
#clock {
@ -38,24 +68,24 @@ window {
#battery {
background-color: #ffffff;
color: black;
color: #000000;
}
#battery.charging {
color: white;
color: #ffffff;
background-color: #26A65B;
}
@keyframes blink {
to {
background-color: #ffffff;
color: black;
color: #000000;
}
}
#battery.warning {
#battery.critical:not(.charging) {
background: #f53c3c;
color: white;
color: #ffffff;
animation-name: blink;
animation-duration: 0.5s;
animation-timing-function: linear;
@ -63,6 +93,10 @@ window {
animation-direction: alternate;
}
label:focus {
background-color: #000000;
}
#cpu {
background: #2ecc71;
color: #000000;
@ -72,6 +106,10 @@ window {
background: #9b59b6;
}
#backlight {
background: #90b1b1;
}
#network {
background: #2980b9;
}
@ -82,7 +120,7 @@ window {
#pulseaudio {
background: #f1c40f;
color: black;
color: #000000;
}
#pulseaudio.muted {
@ -90,7 +128,53 @@ window {
color: #2a5c45;
}
#custom-spotify {
#custom-media {
background: #66cc99;
color: #2a5c45;
}
.custom-spotify {
background: #66cc99;
}
.custom-vlc {
background: #ffa000;
}
#temperature {
background: #f0932b;
}
#temperature.critical {
background: #eb4d4b;
}
#tray {
background-color: #2980b9;
}
#idle_inhibitor {
background-color: #2d3436;
}
#idle_inhibitor.activated {
background-color: #ecf0f1;
color: #2d3436;
}
#mpd {
background: #66cc99;
color: #2a5c45;
}
#mpd.disconnected {
background: #f53c3c;
}
#mpd.stopped {
background: #90b1b1;
}
#mpd.paused {
background: #51a37a;
}

View File

@ -1,51 +1,125 @@
#include "ALabel.hpp"
#include <util/command.hpp>
#include <iostream>
waybar::ALabel::ALabel(const Json::Value& config, const std::string format)
: config_(config),
format_(config_["format"] ? config_["format"].asString() : format),
default_format_(format_)
{
waybar::ALabel::ALabel(const Json::Value& config, const std::string& format, uint16_t interval)
: config_(config),
format_(config_["format"].isString() ? config_["format"].asString() : format),
interval_(config_["interval"] == "once"
? std::chrono::seconds(std::numeric_limits<int>::infinity())
: std::chrono::seconds(
config_["interval"].isUInt() ? config_["interval"].asUInt() : interval)),
default_format_(format_) {
event_box_.add(label_);
if (config_["max-length"]) {
if (config_["max-length"].isUInt()) {
label_.set_max_width_chars(config_["max-length"].asUInt());
label_.set_ellipsize(Pango::EllipsizeMode::ELLIPSIZE_END);
}
if (config_["format-alt"]) {
if (config_["format-alt"].isString()) {
event_box_.add_events(Gdk::BUTTON_PRESS_MASK);
event_box_.signal_button_press_event()
.connect(sigc::mem_fun(*this, &ALabel::handleToggle));
event_box_.signal_button_press_event().connect(sigc::mem_fun(*this, &ALabel::handleToggle));
}
// configure events' user commands
if (config_["on-click"].isString() || config_["on-click-right"].isString()) {
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
{
waybar::ALabel::~ALabel() {
for (const auto& pid : pid_) {
if (pid != -1) {
kill(-pid, 9);
}
}
}
auto waybar::ALabel::update() -> void {
// Nothing here
}
bool waybar::ALabel::handleToggle(GdkEventButton* const& ev)
{
alt = !alt;
if (alt) {
format_ = config_["format-alt"].asString();
} else {
format_ = default_format_;
bool waybar::ALabel::handleToggle(GdkEventButton* const& e) {
if (config_["on-click"].isString() && e->button == 1) {
pid_.push_back(waybar::util::command::forkExec(config_["on-click"].asString()));
} else if (config_["on-click-middle"].isString() && e->button == 2) {
pid_.push_back(waybar::util::command::forkExec(config_["on-click-middle"].asString()));
} else if (config_["on-click-right"].isString() && e->button == 3) {
pid_.push_back(waybar::util::command::forkExec(config_["on-click-right"].asString()));
} else if (config_["on-click-forward"].isString() && e->button == 8) {
pid_.push_back(waybar::util::command::forkExec(config_["on-click-backward"].asString()));
} else if (config_["on-click-backward"].isString() && e->button == 9) {
pid_.push_back(waybar::util::command::forkExec(config_["on-click-forward"].asString()));
}
if (config_["format-alt-click"].isUInt() && e->button == config_["format-alt-click"].asUInt()) {
alt_ = !alt_;
if (alt_ && config_["format-alt"].isString()) {
format_ = config_["format-alt"].asString();
} else {
format_ = default_format_;
}
}
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()) {
pid_.push_back(waybar::util::command::forkExec(config_["on-scroll-up"].asString()));
} else if (config_["on-scroll-down"].isString()) {
pid_.push_back(waybar::util::command::forkExec(config_["on-scroll-down"].asString()));
}
dp.emit();
return true;
}
std::string waybar::ALabel::getIcon(uint16_t percentage)
{
if (!config_["format-icons"] || !config_["format-icons"].isArray()) {
return "";
std::string waybar::ALabel::getIcon(uint16_t percentage, const std::string& alt) {
auto format_icons = config_["format-icons"];
if (format_icons.isObject()) {
if (!alt.empty() && (format_icons[alt].isString() || format_icons[alt].isArray())) {
format_icons = format_icons[alt];
} else {
format_icons = format_icons["default"];
}
}
auto size = config_["format-icons"].size();
auto idx = std::clamp(percentage / (100 / size), 0U, size - 1);
return config_["format-icons"][idx].asString();
if (format_icons.isArray()) {
auto size = format_icons.size();
auto idx = std::clamp(percentage / (100 / size), 0U, size - 1);
format_icons = format_icons[idx];
}
if (format_icons.isString()) {
return format_icons.asString();
}
return "";
}
waybar::ALabel::operator Gtk::Widget &() {
return event_box_;
bool waybar::ALabel::tooltipEnabled() {
return config_["tooltip"].isBool() ? config_["tooltip"].asBool() : true;
}
waybar::ALabel::operator Gtk::Widget&() { return event_box_; }

View File

@ -1,168 +1,214 @@
#include "bar.hpp"
#include "client.hpp"
#include "factory.hpp"
#include "util/json.hpp"
waybar::Bar::Bar(const Client& client,
std::unique_ptr<struct wl_output *> &&p_output, uint32_t p_wl_name)
: client(client), window{Gtk::WindowType::WINDOW_TOPLEVEL},
surface(nullptr), layer_surface(nullptr),
output(std::move(p_output)), wl_name(p_wl_name)
{
static const struct zxdg_output_v1_listener xdgOutputListener = {
.logical_position = handleLogicalPosition,
.logical_size = handleLogicalSize,
.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);
waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config)
: output(w_output),
config(w_config),
window{Gtk::WindowType::WINDOW_TOPLEVEL},
surface(nullptr),
layer_surface(nullptr),
left_(Gtk::ORIENTATION_HORIZONTAL, 0),
center_(Gtk::ORIENTATION_HORIZONTAL, 0),
right_(Gtk::ORIENTATION_HORIZONTAL, 0),
box_(Gtk::ORIENTATION_HORIZONTAL, 0) {
window.set_title("waybar");
window.set_name("waybar");
window.set_decorated(false);
setupConfig();
setupCss();
setupWidgets();
Gtk::Widget& wrap(window);
gtk_widget_realize(wrap.gobj());
GdkWindow *gdk_window = gtk_widget_get_window(wrap.gobj());
if (config["position"] == "right" || config["position"] == "left") {
height_ = 0;
width_ = 1;
}
window.set_size_request(width_, height_);
auto gtk_window = window.gobj();
auto gtk_widget = GTK_WIDGET(gtk_window);
gtk_widget_realize(gtk_widget);
auto gdk_window = window.get_window()->gobj();
gdk_wayland_window_set_use_custom_surface(gdk_window);
surface = gdk_wayland_window_get_wl_surface(gdk_window);
std::size_t layer_top = config_["layer"] == "top"
? ZWLR_LAYER_SHELL_V1_LAYER_TOP : ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM;
std::size_t layer = config["layer"] == "top" ? ZWLR_LAYER_SHELL_V1_LAYER_TOP
: ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM;
auto client = waybar::Client::inst();
layer_surface = zwlr_layer_shell_v1_get_layer_surface(
client.layer_shell, surface, *output, layer_top, "waybar");
client->layer_shell, surface, output->output, layer, "waybar");
static const struct zwlr_layer_surface_v1_listener layer_surface_listener = {
.configure = layerSurfaceHandleConfigure,
.closed = layerSurfaceHandleClosed,
.configure = layerSurfaceHandleConfigure,
.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_;
window.signal_configure_event().connect_notify([&](GdkEventConfigure* ev) {
auto tmp_height = height_;
auto tmp_width = width_;
if (ev->height > static_cast<int>(height_)) {
// Default minimal value
if (height_ != 1) {
std::cout << fmt::format(MIN_HEIGHT_MSG, height_, ev->height) << std::endl;
}
if (config["height"].isUInt()) {
std::cout << fmt::format(SIZE_DEFINED, "Height") << std::endl;
} else {
tmp_height = ev->height;
}
}
if (ev->width > static_cast<int>(width_)) {
// Default minimal value
if (width_ != 1) {
std::cout << fmt::format(MIN_WIDTH_MSG, width_, ev->width) << std::endl;
}
if (config["width"].isUInt()) {
std::cout << fmt::format(SIZE_DEFINED, "Width") << std::endl;
} else {
tmp_width = ev->width;
}
}
if (tmp_width != width_ || tmp_height != height_) {
zwlr_layer_surface_v1_set_size(layer_surface, tmp_width, tmp_height);
}
});
std::size_t anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP;
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;
}
auto height = config_["height"] ? config_["height"].asUInt() : height_;
auto width = config_["width"] ? 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_exclusive_zone(layer_surface, vertical ? width : height);
zwlr_layer_surface_v1_set_size(layer_surface, width, height);
wl_surface_commit(surface);
wl_display_roundtrip(client->wl_display);
setupWidgets();
}
void waybar::Bar::handleLogicalPosition(void* /*data*/,
struct zxdg_output_v1* /*zxdg_output_v1*/, int32_t /*x*/, int32_t /*y*/)
{
// Nothing here
}
// Converting string to button code rn as to avoid doing it later
void waybar::Bar::setupAltFormatKeyForModule(const std::string& module_name) {
if (config.isMember(module_name)) {
Json::Value& module = config[module_name];
if (module.isMember("format-alt")) {
if (module.isMember("format-alt-click")) {
Json::Value& click = module["format-alt-click"];
if (click.isString()) {
std::string str_click = click.asString();
void waybar::Bar::handleLogicalSize(void* /*data*/,
struct zxdg_output_v1* /*zxdg_output_v1*/, int32_t /*width*/,
int32_t /*height*/)
{
// Nothing here
}
void waybar::Bar::handleDone(void* /*data*/,
struct zxdg_output_v1* /*zxdg_output_v1*/)
{
// Nothing here
}
void waybar::Bar::handleName(void* data, struct zxdg_output_v1* /*xdg_output*/,
const char* name)
{
auto o = static_cast<waybar::Bar *>(data);
o->output_name = name;
}
void waybar::Bar::handleDescription(void* /*data*/,
struct zxdg_output_v1* /*zxdg_output_v1*/, const char* /*description*/)
{
// Nothing here
}
void waybar::Bar::layerSurfaceHandleConfigure(void* data,
struct zwlr_layer_surface_v1* surface, uint32_t serial, uint32_t width,
uint32_t height)
{
auto o = static_cast<waybar::Bar *>(data);
o->window.show_all();
zwlr_layer_surface_v1_ack_configure(surface, serial);
if (width != o->width_ || height != o->height_) {
o->width_ = width;
o->height_ = height;
std::cout << fmt::format(
"Bar configured (width: {}, height: {}) for output: {}",
o->width_, o->height_, o->output_name) << std::endl;
o->window.set_size_request(o->width_, o->height_);
o->window.resize(o->width_, o->height_);
zwlr_layer_surface_v1_set_exclusive_zone(surface, o->height_);
zwlr_layer_surface_v1_set_size(surface, o->width_, o->height_);
wl_surface_commit(o->surface);
if (str_click == "click-right") {
module["format-alt-click"] = 3U;
} else if (str_click == "click-middle") {
module["format-alt-click"] = 2U;
} else if (str_click == "click-backward") {
module["format-alt-click"] = 8U;
} else if (str_click == "click-forward") {
module["format-alt-click"] = 9U;
} else {
module["format-alt-click"] = 1U; // default click-left
}
} else {
module["format-alt-click"] = 1U;
}
} else {
module["format-alt-click"] = 1U;
}
}
}
}
void waybar::Bar::layerSurfaceHandleClosed(void* data,
struct zwlr_layer_surface_v1* /*surface*/)
{
auto o = static_cast<waybar::Bar *>(data);
std::cout << "Bar removed from output: " + o->output_name << std::endl;
void waybar::Bar::setupAltFormatKeyForModuleList(const char* module_list_name) {
if (config.isMember(module_list_name)) {
Json::Value& modules = config[module_list_name];
for (const Json::Value& module_name : modules) {
if (module_name.isString()) {
setupAltFormatKeyForModule(module_name.asString());
}
}
}
}
void waybar::Bar::handleSignal(int signal) {
for (auto& module : modules_left_) {
auto* custom = dynamic_cast<waybar::modules::Custom*>(module.get());
if (custom != nullptr) {
custom->refresh(signal);
}
}
for (auto& module : modules_center_) {
auto* custom = dynamic_cast<waybar::modules::Custom*>(module.get());
if (custom != nullptr) {
custom->refresh(signal);
}
}
for (auto& module : modules_right_) {
auto* custom = dynamic_cast<waybar::modules::Custom*>(module.get());
if (custom != nullptr) {
custom->refresh(signal);
}
}
}
void waybar::Bar::layerSurfaceHandleConfigure(void* data, struct zwlr_layer_surface_v1* surface,
uint32_t serial, uint32_t width, uint32_t height) {
auto o = static_cast<waybar::Bar*>(data);
if (width != o->width_ || height != o->height_) {
o->width_ = width;
o->height_ = height;
o->window.set_size_request(o->width_, o->height_);
o->window.resize(o->width_, o->height_);
zwlr_layer_surface_v1_set_exclusive_zone(o->layer_surface,
o->vertical ? o->width_ : o->height_);
std::cout << fmt::format(BAR_SIZE_MSG,
o->width_ == 1 ? "auto" : std::to_string(o->width_),
o->height_ == 1 ? "auto" : std::to_string(o->height_),
o->output->name)
<< std::endl;
wl_surface_commit(o->surface);
}
zwlr_layer_surface_v1_ack_configure(surface, serial);
}
void waybar::Bar::layerSurfaceHandleClosed(void* data, struct zwlr_layer_surface_v1* /*surface*/) {
auto o = static_cast<waybar::Bar*>(data);
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_center_.clear();
o->modules_right_.clear();
}
auto waybar::Bar::toggle() -> void
{
auto waybar::Bar::toggle() -> void {
visible = !visible;
auto zone = visible ? height_ : 0;
if (!visible) {
window.get_style_context()->add_class("hidded");
} else {
window.get_style_context()->remove_class("hidded");
}
zwlr_layer_surface_v1_set_exclusive_zone(layer_surface, zone);
wl_surface_commit(surface);
}
auto waybar::Bar::setupConfig() -> void
{
std::ifstream file(client.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::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]) {
for (const auto &name : config_[pos]) {
void waybar::Bar::getModules(const Factory& factory, const std::string& pos) {
if (config[pos].isArray()) {
for (const auto& name : config[pos]) {
try {
auto module = factory.makeModule(name.asString());
if (pos == "modules-left") {
@ -174,7 +220,13 @@ void waybar::Bar::getModules(const Factory& factory, const std::string& pos)
if (pos == "modules-right") {
modules_right_.emplace_back(module);
}
module->dp.connect([module] { module->update(); });
module->dp.connect([module, &name] {
try {
module->update();
} catch (const std::exception& e) {
std::cerr << name.asString() + ": " + e.what() << std::endl;
}
});
} catch (const std::exception& e) {
std::cerr << e.what() << std::endl;
}
@ -182,30 +234,30 @@ void waybar::Bar::getModules(const Factory& factory, const std::string& pos)
}
}
auto waybar::Bar::setupWidgets() -> void
{
auto &left = *Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 0));
auto &center = *Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 0));
auto &right = *Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 0));
auto waybar::Bar::setupWidgets() -> void {
window.add(box_);
box_.pack_start(left_, true, true);
box_.set_center_widget(center_);
box_.pack_end(right_, true, true);
auto &box = *Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 0));
window.add(box);
box.pack_start(left, true, true);
box.set_center_widget(center);
box.pack_end(right, true, true);
// Convert to button code for every module that is used.
setupAltFormatKeyForModuleList("modules-left");
setupAltFormatKeyForModuleList("modules-right");
setupAltFormatKeyForModuleList("modules-center");
Factory factory(*this, config_);
Factory factory(*this, config);
getModules(factory, "modules-left");
getModules(factory, "modules-center");
getModules(factory, "modules-right");
for (auto& module : modules_left_) {
left.pack_start(*module, false, true, 0);
for (auto const& module : modules_left_) {
left_.pack_start(*module, false, true, 0);
}
for (auto& module : modules_center_) {
center.pack_start(*module, true, true, 0);
for (auto const& module : modules_center_) {
center_.pack_start(*module, true, true, 0);
}
std::reverse(modules_right_.begin(), modules_right_.end());
for (auto& module : modules_right_) {
right.pack_end(*module, false, false, 0);
for (auto const& module : modules_right_) {
right_.pack_end(*module, false, false, 0);
}
window.show_all();
}

View File

@ -1,100 +1,275 @@
#include "client.hpp"
#include <fstream>
#include <iostream>
#include "util/clara.hpp"
#include "util/json.hpp"
waybar::Client::Client(int argc, char* argv[])
: gtk_app(Gtk::Application::create(argc, argv, "org.alexays.waybar")),
gdk_display(Gdk::Display::get_default()),
wl_display(gdk_wayland_display_get_wl_display(gdk_display->gobj()))
{
auto getFirstValidPath = [] (std::vector<std::string> possiblePaths) {
wordexp_t p;
waybar::Client *waybar::Client::inst() {
static auto c = new Client();
return c;
}
for (const std::string &path: possiblePaths) {
if (wordexp(path.c_str(), &p, 0) == 0) {
if (access(*p.we_wordv, F_OK) == 0) {
std::string result = *p.we_wordv;
wordfree(&p);
return result;
}
const std::string waybar::Client::getValidPath(const std::vector<std::string> &paths) {
wordexp_t p;
for (const std::string &path : paths) {
if (wordexp(path.c_str(), &p, 0) == 0) {
if (access(*p.we_wordv, F_OK) == 0) {
std::string result = *p.we_wordv;
wordfree(&p);
return result;
}
wordfree(&p);
}
}
return std::string();
}
void waybar::Client::handleGlobal(void *data, struct wl_registry *registry, uint32_t name,
const char *interface, uint32_t version) {
auto client = static_cast<Client *>(data);
if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) {
client->layer_shell = static_cast<struct zwlr_layer_shell_v1 *>(
wl_registry_bind(registry, name, &zwlr_layer_shell_v1_interface, version));
} else if (strcmp(interface, wl_output_interface.name) == 0) {
auto wl_output = static_cast<struct wl_output *>(
wl_registry_bind(registry, name, &wl_output_interface, version));
client->outputs_.emplace_back(new struct waybar_output({wl_output, "", name, nullptr}));
client->handleOutput(client->outputs_.back());
} else if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0 &&
version >= ZXDG_OUTPUT_V1_NAME_SINCE_VERSION) {
client->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) {
client->idle_inhibit_manager = static_cast<struct zwp_idle_inhibit_manager_v1 *>(
wl_registry_bind(registry, name, &zwp_idle_inhibit_manager_v1_interface, 1));
}
}
void waybar::Client::handleGlobalRemove(void * data, struct wl_registry * /*registry*/,
uint32_t name) {
auto client = static_cast<Client *>(data);
for (auto it = client->bars.begin(); it != client->bars.end();) {
if ((*it)->output->wl_name == name) {
auto output_name = (*it)->output->name;
(*it)->window.close();
it = client->bars.erase(it);
std::cout << "Bar removed from output: " + output_name << std::endl;
} 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()) {
zxdg_output_v1_destroy((*it)->xdg_output);
wl_output_destroy((*it)->output);
client->outputs_.erase(it);
}
}
void waybar::Client::handleOutput(std::unique_ptr<struct waybar_output> &output) {
static const struct zxdg_output_v1_listener xdgOutputListener = {
.logical_position = handleLogicalPosition,
.logical_size = handleLogicalSize,
.done = handleDone,
.name = handleName,
.description = handleDescription,
};
output->xdg_output = zxdg_output_manager_v1_get_xdg_output(xdg_output_manager, output->output);
zxdg_output_v1_add_listener(output->xdg_output, &xdgOutputListener, &output->wl_name);
}
void waybar::Client::handleLogicalPosition(void * /*data*/,
struct zxdg_output_v1 * /*zxdg_output_v1*/,
int32_t /*x*/, int32_t /*y*/) {
// 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;
}
}
return std::string();
};
config_file = getFirstValidPath({
"$XDG_CONFIG_HOME/waybar/config",
"$HOME/waybar/config",
"/etc/xdg/waybar/config",
"./resources/config",
});
css_file = getFirstValidPath({
"$XDG_CONFIG_HOME/waybar/style.css",
"$HOME/waybar/style.css",
"/etc/xdg/waybar/style.css",
"./resources/style.css",
});
found = in_array;
}
if (config["output"].isString() && config["output"].asString() != output->name) {
found = false;
}
return found;
}
void waybar::Client::handleGlobal(void *data, struct wl_registry *registry,
uint32_t name, const char *interface, uint32_t version)
{
auto o = static_cast<waybar::Client *>(data);
if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) {
o->layer_shell = static_cast<struct zwlr_layer_shell_v1 *>(
wl_registry_bind(registry, name, &zwlr_layer_shell_v1_interface, version));
} else if (strcmp(interface, wl_output_interface.name) == 0) {
auto output = std::make_unique<struct wl_output *>();
*output = static_cast<struct wl_output *>(wl_registry_bind(registry, name,
&wl_output_interface, version));
if (o->xdg_output_manager != nullptr) {
o->bars.emplace_back(std::make_unique<Bar>(*o, std::move(output), name));
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 (strcmp(interface, wl_seat_interface.name) == 0) {
o->seat = static_cast<struct wl_seat *>(wl_registry_bind(registry, name,
&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 (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()) {
wl_output_destroy(output->output);
zxdg_output_v1_destroy(output->xdg_output);
} else {
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::handleGlobalRemove(void* data,
struct wl_registry* /*registry*/, uint32_t name)
{
auto o = static_cast<waybar::Client *>(data);
for (auto it = o->bars.begin(); it != o->bars.end(); ++it) {
if ((**it).wl_name == name) {
o->bars.erase(it);
break;
}
void waybar::Client::handleDescription(void * /*data*/, struct zxdg_output_v1 * /*zxdg_output_v1*/,
const char * /*description*/) {
// Nothing here
}
void waybar::Client::setupConfigs(const std::string &config, const std::string &style) {
config_file_ = config.empty() ? getValidPath({
"$XDG_CONFIG_HOME/waybar/config",
"$HOME/.config/waybar/config",
"$HOME/waybar/config",
"/etc/xdg/waybar/config",
"./resources/config",
})
: config;
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()) {
throw std::runtime_error("Missing required resources files");
}
std::cout << "Resources files: " + config_file_ + ", " + css_file_ << std::endl;
}
auto waybar::Client::setupConfig() -> 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() -> 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()
{
void waybar::Client::bindInterfaces() {
registry = wl_display_get_registry(wl_display);
static const struct wl_registry_listener registry_listener = {
.global = handleGlobal,
.global_remove = handleGlobalRemove,
.global = handleGlobal,
.global_remove = handleGlobalRemove,
};
wl_registry_add_listener(registry, &registry_listener, this);
wl_display_roundtrip(wl_display);
if (layer_shell == nullptr || xdg_output_manager == nullptr) {
throw std::runtime_error("Failed to acquire required resources.");
}
}
int waybar::Client::main(int /*argc*/, char* /*argv*/[])
{
int waybar::Client::main(int argc, char *argv[]) {
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());
bool show_help = false;
bool show_version = false;
std::string config;
std::string style;
std::string bar_id;
auto cli = clara::detail::Help(show_help) |
clara::detail::Opt(show_version)["-v"]["--version"]("Show version") |
clara::detail::Opt(config, "config")["-c"]["--config"]("Config path") |
clara::detail::Opt(style, "style")["-s"]["--style"]("Style path") |
clara::detail::Opt(bar_id, "id")["-b"]["--bar"]("Bar id");
auto res = cli.parse(clara::detail::Args(argc, argv));
if (!res) {
std::cerr << "Error in command line: " << res.errorMessage() << std::endl;
return 1;
}
if (show_help) {
std::cout << cli << std::endl;
return 0;
}
if (show_version) {
std::cout << "Waybar v" << VERSION << std::endl;
return 0;
}
setupConfigs(config, style);
setupConfig();
setupCss();
bindInterfaces();
gtk_app->hold();
gtk_app->run();
bars.clear();
zxdg_output_manager_v1_destroy(xdg_output_manager);
zwlr_layer_shell_v1_destroy(layer_shell);
zwp_idle_inhibit_manager_v1_destroy(idle_inhibit_manager);
wl_registry_destroy(registry);
wl_seat_destroy(seat);
wl_display_disconnect(wl_display);
return 0;
}

View File

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

View File

@ -1,25 +1,27 @@
#include "client.hpp"
#include <csignal>
#include <iostream>
#include "client.hpp"
namespace waybar {
static Client* client;
} // namespace waybar
int main(int argc, char* argv[])
{
int main(int argc, char* argv[]) {
try {
waybar::Client c(argc, argv);
waybar::client = &c;
std::signal(SIGUSR1, [] (int signal) {
for (auto& bar : waybar::client->bars) {
(*bar).toggle();
auto client = waybar::Client::inst();
std::signal(SIGUSR1, [](int /*signal*/) {
for (auto& bar : waybar::Client::inst()->bars) {
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) {
std::cerr << e.what() << std::endl;
return 1;

248
src/modules/backlight.cpp Normal file
View File

@ -0,0 +1,248 @@
#include "modules/backlight.hpp"
#include <algorithm>
#include <chrono>
#include <memory>
#include <libudev.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <fmt/format.h>
namespace {
class FileDescriptor {
public:
explicit FileDescriptor(int fd) : fd_(fd) {}
FileDescriptor(const FileDescriptor &other) = delete;
FileDescriptor(FileDescriptor &&other) noexcept = delete;
FileDescriptor &operator=(const FileDescriptor &other) = delete;
FileDescriptor &operator=(FileDescriptor &&other) noexcept = delete;
~FileDescriptor() {
if (fd_ != -1) {
if (close(fd_) != 0) {
fmt::print(stderr, "Failed to close fd: {}\n", errno);
}
}
}
int get() const { return fd_; }
private:
int fd_;
};
struct UdevDeleter {
void operator()(udev *ptr) { udev_unref(ptr); }
};
struct UdevDeviceDeleter {
void operator()(udev_device *ptr) { udev_device_unref(ptr); }
};
struct UdevEnumerateDeleter {
void operator()(udev_enumerate *ptr) { udev_enumerate_unref(ptr); }
};
struct UdevMonitorDeleter {
void operator()(udev_monitor *ptr) { udev_monitor_unref(ptr); }
};
void check_eq(int rc, int expected, const char *message = "eq, rc was: ") {
if (rc != expected) {
throw std::runtime_error(fmt::format(message, rc));
}
}
void check_neq(int rc, int bad_rc, const char *message = "neq, rc was: ") {
if (rc == bad_rc) {
throw std::runtime_error(fmt::format(message, rc));
}
}
void check0(int rc, const char *message = "rc wasn't 0") { check_eq(rc, 0, message); }
void check_gte(int rc, int gte, const char *message = "rc was: ") {
if (rc < gte) {
throw std::runtime_error(fmt::format(message, rc));
}
}
void check_nn(const void *ptr, const char *message = "ptr was null") {
if (ptr == nullptr) {
throw std::runtime_error(message);
}
}
} // namespace
waybar::modules::Backlight::BacklightDev::BacklightDev(std::string name, int actual, int max)
: name_(std::move(name)), actual_(actual), max_(max) {}
std::string_view waybar::modules::Backlight::BacklightDev::name() const { return name_; }
int waybar::modules::Backlight::BacklightDev::get_actual() const { return actual_; }
void waybar::modules::Backlight::BacklightDev::set_actual(int actual) { actual_ = actual; }
int waybar::modules::Backlight::BacklightDev::get_max() const { return max_; }
void waybar::modules::Backlight::BacklightDev::set_max(int max) { max_ = max; }
waybar::modules::Backlight::Backlight(const std::string &name, const Json::Value &config)
: ALabel(config, "{percent}%", 2),
name_(name),
preferred_device_(config["device"].isString() ? config["device"].asString() : "") {
label_.set_name("backlight");
// Get initial state
{
std::unique_ptr<udev, UdevDeleter> udev_check{udev_new()};
check_nn(udev_check.get(), "Udev check new failed");
enumerate_devices(
devices_.begin(), devices_.end(), std::back_inserter(devices_), udev_check.get());
if (devices_.empty()) {
throw std::runtime_error("No backlight found");
}
dp.emit();
}
udev_thread_ = [this] {
std::unique_ptr<udev, UdevDeleter> udev{udev_new()};
check_nn(udev.get(), "Udev new failed");
std::unique_ptr<udev_monitor, UdevMonitorDeleter> mon{
udev_monitor_new_from_netlink(udev.get(), "udev")};
check_nn(mon.get(), "udev monitor new failed");
check_gte(udev_monitor_filter_add_match_subsystem_devtype(mon.get(), "backlight", nullptr),
0,
"udev failed to add monitor filter: ");
udev_monitor_enable_receiving(mon.get());
auto udev_fd = udev_monitor_get_fd(mon.get());
auto epoll_fd = FileDescriptor{epoll_create1(EPOLL_CLOEXEC)};
check_neq(epoll_fd.get(), -1, "epoll init failed: ");
epoll_event ctl_event{};
ctl_event.events = EPOLLIN;
ctl_event.data.fd = udev_fd;
check0(epoll_ctl(epoll_fd.get(), EPOLL_CTL_ADD, ctl_event.data.fd, &ctl_event),
"epoll_ctl failed: {}");
epoll_event events[EPOLL_MAX_EVENTS];
while (udev_thread_.isRunning()) {
const int event_count = epoll_wait(
epoll_fd.get(), events, EPOLL_MAX_EVENTS, std::chrono::milliseconds{interval_}.count());
if (!udev_thread_.isRunning()) {
break;
}
decltype(devices_) devices;
{
std::scoped_lock<std::mutex> lock(udev_thread_mutex_);
devices = devices_;
}
for (int i = 0; i < event_count; ++i) {
const auto &event = events[i];
check_eq(event.data.fd, udev_fd, "unexpected udev fd");
std::unique_ptr<udev_device, UdevDeviceDeleter> dev{udev_monitor_receive_device(mon.get())};
check_nn(dev.get(), "epoll dev was null");
upsert_device(devices.begin(), devices.end(), std::back_inserter(devices), dev.get());
}
// Refresh state if timed out
if (event_count == 0) {
enumerate_devices(devices.begin(), devices.end(), std::back_inserter(devices), udev.get());
}
{
std::scoped_lock<std::mutex> lock(udev_thread_mutex_);
devices_ = devices;
}
dp.emit();
}
};
}
waybar::modules::Backlight::~Backlight() = default;
auto waybar::modules::Backlight::update() -> void {
decltype(devices_) devices;
{
std::scoped_lock<std::mutex> lock(udev_thread_mutex_);
devices = devices_;
}
const auto best = best_device(devices.cbegin(), devices.cend(), preferred_device_);
if (best != nullptr) {
if (previous_best_.has_value() && previous_best_.value() == *best &&
!previous_format_.empty() && previous_format_ == format_) {
return;
}
const auto percent = best->get_max() == 0 ? 100 : best->get_actual() * 100 / best->get_max();
label_.set_markup(fmt::format(
format_, fmt::arg("percent", std::to_string(percent)), fmt::arg("icon", getIcon(percent))));
} else {
if (!previous_best_.has_value()) {
return;
}
label_.set_markup("");
}
previous_best_ = best == nullptr ? std::nullopt : std::optional{*best};
previous_format_ = format_;
}
template <class ForwardIt>
const waybar::modules::Backlight::BacklightDev *waybar::modules::Backlight::best_device(
ForwardIt first, ForwardIt last, std::string_view preferred_device) {
const auto found = std::find_if(
first, last, [preferred_device](const auto &dev) { return dev.name() == preferred_device; });
if (found != last) {
return &(*found);
}
const auto max = std::max_element(
first, last, [](const auto &l, const auto &r) { return l.get_max() < r.get_max(); });
return max == last ? nullptr : &(*max);
}
template <class ForwardIt, class Inserter>
void waybar::modules::Backlight::upsert_device(ForwardIt first, ForwardIt last, Inserter inserter,
udev_device *dev) {
const char *name = udev_device_get_sysname(dev);
check_nn(name);
const char *actual = udev_device_get_sysattr_value(dev, "actual_brightness");
check_nn(actual);
const int actual_int = std::stoi(actual);
const char *max = udev_device_get_sysattr_value(dev, "max_brightness");
check_nn(max);
const int max_int = std::stoi(max);
auto found =
std::find_if(first, last, [name](const auto &device) { return device.name() == name; });
if (found != last) {
found->set_actual(actual_int);
found->set_max(max_int);
} else {
*inserter = BacklightDev{name, actual_int, max_int};
++inserter;
}
}
template <class ForwardIt, class Inserter>
void waybar::modules::Backlight::enumerate_devices(ForwardIt first, ForwardIt last,
Inserter inserter, udev *udev) {
std::unique_ptr<udev_enumerate, UdevEnumerateDeleter> enumerate{udev_enumerate_new(udev)};
udev_enumerate_add_match_subsystem(enumerate.get(), "backlight");
udev_enumerate_scan_devices(enumerate.get());
udev_list_entry *enum_devices = udev_enumerate_get_list_entry(enumerate.get());
udev_list_entry *dev_list_entry;
udev_list_entry_foreach(dev_list_entry, enum_devices) {
const char * path = udev_list_entry_get_name(dev_list_entry);
std::unique_ptr<udev_device, UdevDeviceDeleter> dev{udev_device_new_from_syspath(udev, path)};
check_nn(dev.get(), "dev new failed");
upsert_device(first, last, inserter, dev.get());
}
}

View File

@ -1,58 +1,86 @@
#include "modules/battery.hpp"
waybar::modules::Battery::Battery(const Json::Value& config)
: ALabel(config, "{capacity}%")
{
try {
for (auto &node : fs::directory_iterator(data_dir_)) {
if (fs::is_directory(node) && fs::exists(node / "capacity")
&& fs::exists(node / "status") && fs::exists(node / "uevent")) {
batteries_.push_back(node);
}
}
} catch (fs::filesystem_error &e) {
throw std::runtime_error(e.what());
}
if (batteries_.empty()) {
throw std::runtime_error("No batteries.");
waybar::modules::Battery::Battery(const std::string& id, const Json::Value& config)
: ALabel(config, "{capacity}%", 60) {
label_.set_name("battery");
if (!id.empty()) {
label_.get_style_context()->add_class(id);
}
getBatteries();
fd_ = inotify_init1(IN_CLOEXEC);
if (fd_ == -1) {
throw std::runtime_error("Unable to listen batteries.");
}
for (auto &bat : batteries_) {
inotify_add_watch(fd_, (bat / "uevent").c_str(), IN_ACCESS);
for (auto const& bat : batteries_) {
auto wd = inotify_add_watch(fd_, (bat / "uevent").c_str(), IN_ACCESS);
if (wd != -1) {
wds_.push_back(wd);
}
}
label_.set_name("battery");
worker();
}
waybar::modules::Battery::~Battery()
{
waybar::modules::Battery::~Battery() {
for (auto wd : wds_) {
inotify_rm_watch(fd_, wd);
}
close(fd_);
}
void waybar::modules::Battery::worker()
{
// Trigger first values
update();
void waybar::modules::Battery::worker() {
thread_timer_ = [this] {
dp.emit();
thread_timer_.sleep_for(interval_);
};
thread_ = [this] {
struct inotify_event event = {0};
int nbytes = read(fd_, &event, sizeof(event));
if (nbytes != sizeof(event)) {
int nbytes = read(fd_, &event, sizeof(event));
if (nbytes != sizeof(event) || event.mask & IN_IGNORED) {
thread_.stop();
return;
}
// TODO: don't stop timer for now since there is some bugs :?
// thread_timer_.stop();
dp.emit();
};
}
auto waybar::modules::Battery::update() -> void
{
void waybar::modules::Battery::getBatteries() {
try {
uint16_t total = 0;
std::string status;
for (auto &bat : batteries_) {
uint16_t capacity;
for (auto const& node : fs::directory_iterator(data_dir_)) {
if (!fs::is_directory(node)) {
continue;
}
auto dir_name = node.path().filename();
auto bat_defined = config_["bat"].isString();
if (((bat_defined && dir_name == config_["bat"].asString()) || !bat_defined) &&
fs::exists(node / "capacity") && fs::exists(node / "uevent") &&
fs::exists(node / "status")) {
batteries_.push_back(node);
}
auto adap_defined = config_["adapter"].isString();
if (((adap_defined && dir_name == config_["adapter"].asString()) || !adap_defined) &&
fs::exists(node / "online")) {
adapter_ = node;
}
}
} catch (fs::filesystem_error& e) {
throw std::runtime_error(e.what());
}
if (batteries_.empty()) {
if (config_["bat"].isString()) {
throw std::runtime_error("No battery named " + config_["bat"].asString());
}
throw std::runtime_error("No batteries.");
}
}
const std::tuple<uint8_t, std::string> waybar::modules::Battery::getInfos() const {
try {
uint16_t total = 0;
std::string status = "Unknown";
for (auto const& bat : batteries_) {
uint16_t capacity;
std::string _status;
std::ifstream(bat / "capacity") >> capacity;
std::ifstream(bat / "status") >> _status;
@ -62,22 +90,75 @@ auto waybar::modules::Battery::update() -> void
total += capacity;
}
uint16_t capacity = total / batteries_.size();
label_.set_text(fmt::format(format_, fmt::arg("capacity", capacity),
fmt::arg("icon", getIcon(capacity))));
label_.set_tooltip_text(status);
bool charging = status == "Charging";
if (charging) {
label_.get_style_context()->add_class("charging");
} else {
label_.get_style_context()->remove_class("charging");
}
auto critical = config_["critical"] ? config_["critical"].asUInt() : 15;
if (capacity <= critical && !charging) {
label_.get_style_context()->add_class("warning");
} else {
label_.get_style_context()->remove_class("warning");
}
return {capacity, status};
} catch (const std::exception& e) {
std::cerr << e.what() << std::endl;
return {0, "Unknown"};
}
}
const std::string waybar::modules::Battery::getAdapterStatus(uint8_t capacity) const {
if (!adapter_.empty()) {
bool online;
std::ifstream(adapter_ / "online") >> online;
if (capacity == 100) {
return "Full";
}
return online ? "Charging" : "Discharging";
}
return "Unknown";
}
const std::string waybar::modules::Battery::getState(uint8_t capacity) const {
// 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(), [](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 [capacity, status] = getInfos();
if (status == "Unknown") {
status = getAdapterStatus(capacity);
}
if (tooltipEnabled()) {
label_.set_tooltip_text(status);
}
std::transform(status.begin(), status.end(), status.begin(), ::tolower);
auto format = format_;
auto state = getState(capacity);
label_.get_style_context()->remove_class(old_status_);
label_.get_style_context()->add_class(status);
old_status_ = status;
if (!state.empty() && config_["format-" + status + "-" + state].isString()) {
format = config_["format-" + status + "-" + state].asString();
} else if (config_["format-" + status].isString()) {
format = config_["format-" + status].asString();
} else if (!state.empty() && config_["format-" + state].isString()) {
format = config_["format-" + state].asString();
}
if (format.empty()) {
event_box_.hide();
} else {
event_box_.show();
label_.set_markup(
fmt::format(format, fmt::arg("capacity", capacity), fmt::arg("icon", getIcon(capacity))));
}
}

View File

@ -1,21 +1,39 @@
#include "modules/clock.hpp"
waybar::modules::Clock::Clock(const Json::Value& config)
: ALabel(config, "{:%H:%M}")
{
waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
: ALabel(config, "{:%H:%M}", 60) {
label_.set_name("clock");
uint32_t interval = config_["interval"] ? config_["inveral"].asUInt() : 60;
thread_ = [this, interval] {
auto now = waybar::chrono::clock::now();
if (!id.empty()) {
label_.get_style_context()->add_class(id);
}
thread_ = [this] {
dp.emit();
auto timeout = std::chrono::floor<std::chrono::seconds>(now
+ std::chrono::seconds(interval));
thread_.sleep_until(timeout);
auto now = std::chrono::system_clock::now();
auto timeout = std::chrono::floor<std::chrono::seconds>(now + interval_);
auto time_s = std::chrono::time_point_cast<std::chrono::seconds>(timeout);
auto sub_m =
std::chrono::duration_cast<std::chrono::seconds>(time_s.time_since_epoch()).count() %
interval_.count();
if (sub_m > 0) {
thread_.sleep_until(timeout - std::chrono::seconds(sub_m - 1));
} else {
thread_.sleep_until(timeout - std::chrono::seconds(sub_m));
}
};
}
auto waybar::modules::Clock::update() -> void
{
auto waybar::modules::Clock::update() -> void {
auto localtime = fmt::localtime(std::time(nullptr));
label_.set_text(fmt::format(format_, localtime));
auto text = fmt::format(format_, localtime);
label_.set_markup(text);
if (tooltipEnabled()) {
if (config_["tooltip-format"].isString()) {
auto tooltip_format = config_["tooltip-format"].asString();
auto tooltip_text = fmt::format(tooltip_format, localtime);
label_.set_tooltip_text(tooltip_text);
} else {
label_.set_tooltip_text(text);
}
}
}

View File

@ -1,22 +1,85 @@
#include "modules/cpu.hpp"
waybar::modules::Cpu::Cpu(const Json::Value& config)
: ALabel(config, "{}%")
{
waybar::modules::Cpu::Cpu(const std::string& id, const Json::Value& config)
: ALabel(config, "{usage}%", 10) {
label_.set_name("cpu");
uint32_t interval = config_["interval"] ? config_["inveral"].asUInt() : 10;
thread_ = [this, interval] {
if (!id.empty()) {
label_.get_style_context()->add_class(id);
}
thread_ = [this] {
dp.emit();
thread_.sleep_for(chrono::seconds(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
auto cpu_load = getCpuLoad();
auto [cpu_usage, tooltip] = getCpuUsage();
if (tooltipEnabled()) {
label_.set_tooltip_text(tooltip);
}
label_.set_markup(fmt::format(format_, fmt::arg("load", cpu_load), fmt::arg("usage", cpu_usage)));
}
uint16_t waybar::modules::Cpu::getCpuLoad() {
struct 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();
label_.set_text(fmt::format(format_, load));
return load;
}
throw std::runtime_error("Can't get Cpu load");
}
std::tuple<uint16_t, std::string> waybar::modules::Cpu::getCpuUsage() {
if (prev_times_.empty()) {
prev_times_ = parseCpuinfo();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
std::vector<std::tuple<size_t, size_t>> curr_times = parseCpuinfo();
std::string tooltip;
uint16_t usage = 0;
for (size_t i = 0; i < curr_times.size(); ++i) {
auto [curr_idle, curr_total] = curr_times[i];
auto [prev_idle, prev_total] = prev_times_[i];
const float delta_idle = curr_idle - prev_idle;
const float delta_total = curr_total - prev_total;
uint16_t tmp = 100 * (1 - delta_idle / delta_total);
if (i == 0) {
usage = tmp;
tooltip = fmt::format("Total: {}%", tmp);
} else {
tooltip = tooltip + fmt::format("\nCore{}: {}%", i - 1, tmp);
}
}
prev_times_ = curr_times;
return {usage, tooltip};
}
std::vector<std::tuple<size_t, size_t>> waybar::modules::Cpu::parseCpuinfo() {
std::ifstream info(data_dir_);
if (!info.is_open()) {
throw std::runtime_error("Can't open " + data_dir_);
}
std::vector<std::tuple<size_t, size_t>> cpuinfo;
std::string line;
while (getline(info, line)) {
if (line.substr(0, 3).compare("cpu") != 0) {
break;
}
std::stringstream sline(line.substr(5));
std::vector<size_t> times;
for (size_t time = 0; sline >> time; times.push_back(time))
;
size_t idle_time = 0;
size_t total_time = 0;
if (times.size() >= 4) {
idle_time = times[3];
total_time = std::accumulate(times.begin(), times.end(), 0);
}
cpuinfo.emplace_back(idle_time, total_time);
}
return cpuinfo;
}

View File

@ -1,46 +1,173 @@
#include "modules/custom.hpp"
waybar::modules::Custom::Custom(const std::string name,
const Json::Value& config)
: ALabel(config, "{}"), name_(name)
{
if (!config_["exec"]) {
throw std::runtime_error(name_ + " has no exec path.");
}
waybar::modules::Custom::Custom(const std::string& name, const Json::Value& config)
: ALabel(config, "{}"), name_(name), fp_(nullptr), pid_(-1) {
label_.set_name("custom-" + name_);
worker();
if (config_["exec"].isString()) {
if (interval_.count() > 0) {
delayWorker();
} else {
continuousWorker();
}
}
dp.emit();
}
void waybar::modules::Custom::worker()
{
uint32_t interval = config_["interval"] ? config_["inveral"].asUInt() : 30;
thread_ = [this, interval] {
waybar::modules::Custom::~Custom() {
if (pid_ != -1) {
kill(-pid_, 9);
pid_ = -1;
}
}
void waybar::modules::Custom::delayWorker() {
thread_ = [this] {
bool can_update = true;
if (config_["exec-if"]) {
if (config_["exec-if"].isString()) {
auto res = waybar::util::command::exec(config_["exec-if"].asString());
if (res.exit_code != 0) {
can_update = false;
label_.hide();
event_box_.hide();
}
}
if (can_update) {
output_ = waybar::util::command::exec(config_["exec"].asString());
dp.emit();
}
thread_.sleep_for(chrono::seconds(interval));
thread_.sleep_for(interval_);
};
}
auto waybar::modules::Custom::update() -> void
{
auto res = waybar::util::command::exec(config_["exec"].asString());
void waybar::modules::Custom::continuousWorker() {
auto cmd = config_["exec"].asString();
pid_ = -1;
fp_ = util::command::open(cmd, pid_);
if (!fp_) {
throw std::runtime_error("Unable to open " + cmd);
}
thread_ = [&] {
char* buff = nullptr;
size_t len = 0;
if (getline(&buff, &len, fp_) == -1) {
int exit_code = 1;
if (fp_) {
exit_code = WEXITSTATUS(util::command::close(fp_, pid_));
fp_ = nullptr;
}
thread_.stop();
if (exit_code != 0) {
output_ = {exit_code, ""};
dp.emit();
std::cerr << name_ + " just stopped unexpectedly, is it endless?" << std::endl;
}
return;
}
std::string output = buff;
// Hide label if output is empty
if (res.out.empty() || res.exit_code != 0) {
label_.hide();
} else {
auto str = fmt::format(format_, res.out);
label_.set_text(str);
label_.set_tooltip_text(str);
label_.show();
// Remove last newline
if (!output.empty() && output[output.length() - 1] == '\n') {
output.erase(output.length() - 1);
}
output_ = {0, output};
dp.emit();
};
}
void waybar::modules::Custom::refresh(int sig /*signal*/) {
if (sig == SIGRTMIN + config_["signal"].asInt()) {
thread_.wake_up();
}
}
auto waybar::modules::Custom::update() -> void {
// Hide label if output is empty
if (config_["exec"].isString() && (output_.out.empty() || output_.exit_code != 0)) {
event_box_.hide();
} else {
if (config_["return-type"].asString() == "json") {
parseOutputJson();
} else {
parseOutputRaw();
}
auto str = fmt::format(format_,
text_,
fmt::arg("alt", alt_),
fmt::arg("icon", getIcon(percentage_, alt_)),
fmt::arg("percentage", percentage_));
label_.set_markup(str);
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();
}
}
void waybar::modules::Custom::parseOutputRaw() {
std::istringstream output(output_.out);
std::string line;
int i = 0;
while (getline(output, line)) {
if (i == 0) {
if (config_["escape"].isBool() && config_["escape"].asBool()) {
text_ = Glib::Markup::escape_text(line);
} else {
text_ = line;
}
tooltip_ = line;
class_.clear();
} else if (i == 1) {
tooltip_ = line;
} else if (i == 2) {
class_.push_back(line);
} else {
break;
}
i++;
}
}
void waybar::modules::Custom::parseOutputJson() {
std::istringstream output(output_.out);
std::string line;
class_.clear();
while (getline(output, line)) {
auto parsed = parser_.parse(line);
if (config_["escape"].isBool() && config_["escape"].asBool()) {
text_ = Glib::Markup::escape_text(parsed["text"].asString());
} else {
text_ = parsed["text"].asString();
}
if (config_["escape"].isBool() && config_["escape"].asBool()) {
alt_ = Glib::Markup::escape_text(parsed["alt"].asString());
} else {
alt_ = parsed["alt"].asString();
}
tooltip_ = parsed["tooltip"].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()) {
percentage_ = parsed["percentage"].asUInt();
} else {
percentage_ = 0;
}
break;
}
}

View File

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

View File

@ -1,25 +1,61 @@
#include "modules/memory.hpp"
waybar::modules::Memory::Memory(const Json::Value& config)
: ALabel(config, "{}%")
{
waybar::modules::Memory::Memory(const std::string& id, const Json::Value& config)
: ALabel(config, "{}%", 30) {
label_.set_name("memory");
uint32_t interval = config_["interval"] ? config_["inveral"].asUInt() : 30;
thread_ = [this, interval] {
if (!id.empty()) {
label_.get_style_context()->add_class(id);
}
thread_ = [this] {
dp.emit();
thread_.sleep_for(chrono::seconds(interval));
thread_.sleep_for(interval_);
};
}
auto waybar::modules::Memory::update() -> void
{
struct sysinfo info = {0};
if (sysinfo(&info) == 0) {
auto total = info.totalram * info.mem_unit;
auto freeram = info.freeram * info.mem_unit;
int used_ram_percentage = 100 * (total - freeram) / total;
label_.set_text(fmt::format(format_, used_ram_percentage));
auto used_ram_gigabytes = (total - freeram) / std::pow(1024, 3);
label_.set_tooltip_text(fmt::format("{:.{}f}Gb used", used_ram_gigabytes, 1));
auto waybar::modules::Memory::update() -> void {
parseMeminfo();
if (memtotal_ > 0 && memfree_ >= 0) {
int used_ram_percentage = 100 * (memtotal_ - memfree_) / memtotal_;
label_.set_markup(fmt::format(format_, used_ram_percentage));
auto used_ram_gigabytes = (memtotal_ - memfree_) / std::pow(1024, 2);
if (tooltipEnabled()) {
label_.set_tooltip_text(fmt::format("{:.{}f}Gb used", used_ram_gigabytes, 1));
}
event_box_.show();
} else {
event_box_.hide();
}
}
void waybar::modules::Memory::parseMeminfo() {
int64_t memfree = -1, membuffer = -1, memcache = -1, memavail = -1;
std::ifstream info(data_dir_);
if (!info.is_open()) {
throw std::runtime_error("Can't open " + data_dir_);
}
std::string line;
while (getline(info, line)) {
auto posDelim = line.find(':');
if (posDelim == std::string::npos) {
continue;
}
std::string name = line.substr(0, posDelim);
int64_t value = std::stol(line.substr(posDelim + 1));
if (name.compare("MemTotal") == 0) {
memtotal_ = value;
} else if (name.compare("MemAvailable") == 0) {
memavail = value;
} else if (name.compare("MemFree") == 0) {
memfree = value;
} else if (name.compare("Buffers") == 0) {
membuffer = value;
} else if (name.compare("Cached") == 0) {
memcache = value;
}
if (memtotal_ > 0 && (memavail >= 0 || (memfree > -1 && membuffer > -1 && memcache > -1))) {
break;
}
}
memfree_ = memavail >= 0 ? memavail : memfree + membuffer + memcache;
}

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

@ -0,0 +1,344 @@
#include "modules/mpd.hpp"
#include <fmt/chrono.h>
#include <iostream>
waybar::modules::MPD::MPD(const std::string& id, const Json::Value& config)
: ALabel(config, "{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()) {
std::cerr << module_name_ << ": `port` configuration should be an unsigned int" << std::endl;
}
if (!config_["timeout"].isNull() && !config_["timeout"].isUInt()) {
std::cerr << module_name_ << ": `timeout` configuration should be an unsigned int" << std::endl;
}
label_.set_name("mpd");
if (!id.empty()) {
label_.get_style_context()->add_class(id);
}
if (!config["server"].isNull()) {
if (!config_["server"].isString()) {
std::cerr << module_name_ << "`server` configuration should be a string" << std::endl;
}
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) {
std::cerr << module_name_ + ": " + e.what() << std::endl;
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
update();
std::this_thread::sleep_for(interval_);
} else {
waitForEvent();
dp.emit();
}
} catch (const std::exception& e) {
std::cerr << module_name_ + ": " + e.what() << std::endl;
}
}
});
}
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) {
std::cerr << module_name_ << ": Trying to fetch state icon while disconnected" << std::endl;
return "";
}
if (stopped()) {
std::cerr << module_name_ << ": Trying to fetch state icon while stopped" << std::endl;
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) {
std::cerr << module_name_ << ": Trying to fetch option icon while disconnected" << std::endl;
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) {
std::cerr << module_name_ << ": Failed to connect to MPD" << std::endl;
connection_.reset();
alternate_connection_.reset();
return;
}
try {
checkErrors(connection_.get());
std::cerr << module_name_ << ": Connected to MPD" << std::endl;
} catch (std::runtime_error& e) {
std::cerr << module_name_ << ": Failed to connect to MPD: " << e.what() << std::endl;
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,20 +1,21 @@
#include "modules/network.hpp"
#include <sys/eventfd.h>
waybar::modules::Network::Network(const Json::Value& config)
: ALabel(config, "{ifname}"), family_(AF_INET),
signal_strength_dbm_(0), signal_strength_(0)
{
sock_fd_ = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
if (sock_fd_ < 0) {
throw std::runtime_error("Can't open network socket");
waybar::modules::Network::Network(const std::string &id, const Json::Value &config)
: ALabel(config, "{ifname}", 60),
family_(AF_INET),
efd_(-1),
ev_fd_(-1),
cidr_(-1),
signal_strength_dbm_(0),
signal_strength_(0) {
label_.set_name("network");
if (!id.empty()) {
label_.get_style_context()->add_class(id);
}
nladdr_.nl_family = AF_NETLINK;
nladdr_.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR;
if (bind(sock_fd_, reinterpret_cast<struct sockaddr *>(&nladdr_),
sizeof(nladdr_)) != 0) {
throw std::runtime_error("Can't bind network socket");
}
if (config_["interface"]) {
createInfoSocket();
createEventSocket();
if (config_["interface"].isString()) {
ifid_ = if_nametoindex(config_["interface"].asCString());
ifname_ = config_["interface"].asString();
if (ifid_ <= 0) {
@ -28,122 +29,201 @@ waybar::modules::Network::Network(const Json::Value& config)
ifname_ = ifname;
}
}
initNL80211();
label_.set_name("network");
// Trigger first values
getInfo();
update();
thread_ = [this] {
char buf[4096];
uint64_t len = netlinkResponse(sock_fd_, buf, sizeof(buf),
RTMGRP_LINK | RTMGRP_IPV4_IFADDR);
bool need_update = false;
for (auto nh = reinterpret_cast<struct nlmsghdr *>(buf); NLMSG_OK(nh, len);
nh = NLMSG_NEXT(nh, len)) {
if (nh->nlmsg_type == NLMSG_DONE) {
break;
}
if (nh->nlmsg_type == NLMSG_ERROR) {
continue;
}
if (nh->nlmsg_type < RTM_NEWADDR) {
auto rtif = static_cast<struct ifinfomsg *>(NLMSG_DATA(nh));
if (rtif->ifi_index == static_cast<int>(ifid_)) {
need_update = true;
if (!(rtif->ifi_flags & IFF_RUNNING)) {
disconnected();
}
}
}
}
if (ifid_ <= 0 && !config_["interface"]) {
// Need to wait before get external interface
thread_.sleep_for(std::chrono::seconds(1));
ifid_ = getExternalInterface();
if (ifid_ > 0) {
char ifname[IF_NAMESIZE];
if_indextoname(ifid_, ifname);
ifname_ = ifname;
need_update = true;
}
}
if (need_update) {
getInfo();
dp.emit();
}
};
dp.emit();
worker();
}
waybar::modules::Network::~Network()
{
close(sock_fd_);
nl_socket_free(sk_);
}
auto waybar::modules::Network::update() -> void
{
auto format = format_;
if (ifid_ <= 0) {
format = config_["format-disconnected"]
? config_["format-disconnected"].asString() : format;
label_.get_style_context()->add_class("disconnected");
} else {
if (essid_.empty()) {
format = config_["format-ethernet"]
? config_["format-ethernet"].asString() : format;
} else {
format = config_["format-wifi"]
? config_["format-wifi"].asString() : format;
}
label_.get_style_context()->remove_class("disconnected");
waybar::modules::Network::~Network() {
if (ev_fd_ > -1) {
eventfd_write(ev_fd_, 1);
std::this_thread::sleep_for(std::chrono::milliseconds(150));
close(ev_fd_);
}
if (efd_ > -1) {
close(efd_);
}
if (info_sock_ != nullptr) {
nl_socket_drop_membership(info_sock_, RTMGRP_LINK);
nl_socket_drop_membership(info_sock_, RTMGRP_IPV4_IFADDR);
nl_close(info_sock_);
nl_socket_free(info_sock_);
}
if (sk_ != nullptr) {
nl_close(sk_);
nl_socket_free(sk_);
}
label_.set_text(fmt::format(format,
fmt::arg("essid", essid_),
fmt::arg("signaldBm", signal_strength_dbm_),
fmt::arg("signalStrength", signal_strength_),
fmt::arg("ifname", ifname_)
));
}
void waybar::modules::Network::disconnected()
{
essid_.clear();
signal_strength_dbm_ = 0;
signal_strength_ = 0;
ifname_.clear();
ifid_ = -1;
void waybar::modules::Network::createInfoSocket() {
info_sock_ = nl_socket_alloc();
if (nl_connect(info_sock_, NETLINK_ROUTE) != 0) {
throw std::runtime_error("Can't connect network socket");
}
if (nl_socket_add_membership(info_sock_, RTMGRP_LINK) != 0) {
throw std::runtime_error("Can't add membership");
}
if (nl_socket_add_membership(info_sock_, RTMGRP_IPV4_IFADDR) != 0) {
throw std::runtime_error("Can't add membership");
}
nl_socket_disable_seq_check(info_sock_);
nl_socket_set_nonblocking(info_sock_);
nl_socket_modify_cb(info_sock_, NL_CB_VALID, NL_CB_CUSTOM, handleEvents, this);
efd_ = epoll_create1(EPOLL_CLOEXEC);
if (efd_ < 0) {
throw std::runtime_error("Can't create epoll");
}
{
ev_fd_ = eventfd(0, EFD_NONBLOCK);
struct epoll_event event = {0};
event.events = EPOLLIN | EPOLLET;
event.data.fd = ev_fd_;
if (epoll_ctl(efd_, EPOLL_CTL_ADD, ev_fd_, &event) == -1) {
throw std::runtime_error("Can't add epoll event");
}
}
{
auto fd = nl_socket_get_fd(info_sock_);
struct epoll_event event = {0};
event.events = EPOLLIN | EPOLLET | EPOLLRDHUP;
event.data.fd = fd;
if (epoll_ctl(efd_, EPOLL_CTL_ADD, fd, &event) == -1) {
throw std::runtime_error("Can't add epoll event");
}
}
}
void waybar::modules::Network::initNL80211()
{
void waybar::modules::Network::createEventSocket() {
sk_ = nl_socket_alloc();
if (genl_connect(sk_) != 0) {
nl_socket_free(sk_);
if (genl_connect(sk_) != 0) {
throw std::runtime_error("Can't connect to netlink socket");
}
if (nl_socket_modify_cb(sk_, NL_CB_VALID, NL_CB_CUSTOM, scanCb, this) < 0) {
nl_socket_free(sk_);
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) {
throw std::runtime_error("Can't set callback");
}
nl80211_id_ = genl_ctrl_resolve(sk_, "nl80211");
if (nl80211_id_ < 0) {
nl_socket_free(sk_);
throw std::runtime_error("Can't resolve nl80211 interface");
}
}
void waybar::modules::Network::worker() {
thread_timer_ = [this] {
if (ifid_ > 0) {
getInfo();
dp.emit();
}
thread_timer_.sleep_for(interval_);
};
std::array<struct epoll_event, EPOLL_MAX> events{};
thread_ = [this, &events] {
int ec = epoll_wait(efd_, events.data(), EPOLL_MAX, -1);
if (ec > 0) {
for (auto i = 0; i < ec; i++) {
if (events[i].data.fd == nl_socket_get_fd(info_sock_)) {
nl_recvmsgs_default(info_sock_);
} else {
thread_.stop();
break;
}
}
} else if (ec == -1) {
thread_.stop();
}
};
}
auto waybar::modules::Network::update() -> void {
std::string connectiontype;
std::string tooltip_format = "";
if (config_["tooltip-format"].isString()) {
tooltip_format = config_["tooltip-format"].asString();
}
if (ifid_ <= 0 || ipaddr_.empty()) {
if (config_["format-disconnected"].isString()) {
default_format_ = config_["format-disconnected"].asString();
}
if (config_["tooltip-format-disconnected"].isString()) {
tooltip_format = config_["tooltip-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();
}
if (config_["tooltip-format-ethernet"].isString()) {
tooltip_format = config_["tooltip-format-ethernet"].asString();
}
connectiontype = "ethernet";
} else {
if (config_["format-wifi"].isString()) {
default_format_ = config_["format-wifi"].asString();
}
if (config_["tooltip-format-wifi"].isString()) {
tooltip_format = config_["tooltip-format-wifi"].asString();
}
connectiontype = "wifi";
}
label_.get_style_context()->remove_class("disconnected");
}
if (!alt_) {
format_ = default_format_;
}
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("icon", getIcon(signal_strength_, connectiontype)));
label_.set_markup(text);
if (tooltipEnabled()) {
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("icon", getIcon(signal_strength_, connectiontype)));
label_.set_tooltip_text(tooltip_text);
} else {
label_.set_tooltip_text(text);
}
}
}
void waybar::modules::Network::disconnected() {
essid_.clear();
signal_strength_dbm_ = 0;
signal_strength_ = 0;
ipaddr_.clear();
netmask_.clear();
cidr_ = 0;
if (!config_["interface"].isString()) {
ifname_.clear();
ifid_ = -1;
}
// 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
int waybar::modules::Network::getExternalInterface()
{
int waybar::modules::Network::getExternalInterface() {
static const uint32_t route_buffer_size = 8192;
struct nlmsghdr *hdr = nullptr;
struct rtmsg *rt = nullptr;
char resp[route_buffer_size] = {0};
int ifidx = -1;
struct nlmsghdr * hdr = nullptr;
struct rtmsg * rt = nullptr;
char resp[route_buffer_size] = {0};
int ifidx = -1;
/* Prepare request. */
uint32_t reqlen = NLMSG_SPACE(sizeof(*rt));
char req[reqlen] = {0};
constexpr uint32_t reqlen = NLMSG_SPACE(sizeof(*rt));
char req[reqlen] = {0};
/* Build the RTM_GETROUTE request. */
hdr = reinterpret_cast<struct nlmsghdr *>(req);
@ -155,7 +235,7 @@ int waybar::modules::Network::getExternalInterface()
rt->rtm_table = RT_TABLE_MAIN;
/* Issue the query. */
if (netlinkRequest(sock_fd_, req, reqlen) < 0) {
if (netlinkRequest(req, reqlen) < 0) {
goto out;
}
@ -165,14 +245,14 @@ int waybar::modules::Network::getExternalInterface()
* consume responses till NLMSG_DONE/NLMSG_ERROR is encountered).
*/
do {
uint64_t len = netlinkResponse(sock_fd_, resp, route_buffer_size);
auto len = netlinkResponse(resp, route_buffer_size);
if (len < 0) {
goto out;
}
/* Parse the response payload into netlink messages. */
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) {
goto out;
}
@ -199,10 +279,10 @@ int waybar::modules::Network::getExternalInterface()
/* Parse all the attributes for a single routing table entry. */
struct rtattr *attr = RTM_RTA(rt);
uint64_t attrlen = RTM_PAYLOAD(hdr);
bool has_gateway = false;
bool has_destination = false;
int temp_idx = -1;
uint64_t attrlen = RTM_PAYLOAD(hdr);
bool has_gateway = false;
bool has_destination = false;
int temp_idx = -1;
for (; RTA_OK(attr, attrlen); attr = RTA_NEXT(attr, attrlen)) {
/* Determine if this routing table entry corresponds to the default
* route by seeing if it has a gateway, and if a destination addr is
@ -222,20 +302,20 @@ int waybar::modules::Network::getExternalInterface()
* Should be either missing, or maybe all 0s. Accept both.
*/
const uint32_t nr_zeroes = (family_ == AF_INET) ? 4 : 16;
unsigned char c = 0;
size_t dstlen = RTA_PAYLOAD(attr);
unsigned char c = 0;
size_t dstlen = RTA_PAYLOAD(attr);
if (dstlen != nr_zeroes) {
break;
}
for (uint32_t i = 0; i < dstlen; i += 1) {
c |= *(unsigned char *)(RTA_DATA(attr) + i);
c |= *((unsigned char *)RTA_DATA(attr) + i);
}
has_destination = (c == 0);
break;
}
case RTA_OIF:
/* The output interface index. */
temp_idx = *static_cast<int*>(RTA_DATA(attr));
temp_idx = *static_cast<int *>(RTA_DATA(attr));
break;
default:
break;
@ -255,38 +335,108 @@ out:
return ifidx;
}
uint64_t waybar::modules::Network::netlinkRequest(int fd, void *req,
uint32_t reqlen, uint32_t groups)
{
struct sockaddr_nl sa = {0};
sa.nl_family = AF_NETLINK;
sa.nl_groups = groups;
struct iovec iov = { req, reqlen };
struct msghdr msg = { &sa, sizeof(sa), &iov, 1, nullptr, 0, 0 };
return sendmsg(fd, &msg, 0);
void waybar::modules::Network::getInterfaceAddress() {
unsigned int cidrRaw;
struct ifaddrs *ifaddr, *ifa;
ipaddr_.clear();
netmask_.clear();
cidr_ = 0;
int success = getifaddrs(&ifaddr);
if (success == 0) {
ifa = ifaddr;
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);
}
}
uint64_t waybar::modules::Network::netlinkResponse(int fd, void *resp,
uint32_t resplen, uint32_t groups)
{
uint64_t ret;
struct sockaddr_nl sa = {0};
int waybar::modules::Network::netlinkRequest(void *req, uint32_t reqlen, uint32_t groups) {
struct sockaddr_nl sa = {};
sa.nl_family = AF_NETLINK;
sa.nl_groups = groups;
struct iovec iov = { resp, resplen };
struct msghdr msg = { &sa, sizeof(sa), &iov, 1, nullptr, 0, 0 };
ret = recvmsg(fd, &msg, 0);
struct iovec iov = {req, reqlen};
struct msghdr msg = {&sa, sizeof(sa), &iov, 1, nullptr, 0, 0};
return sendmsg(nl_socket_get_fd(info_sock_), &msg, 0);
}
int waybar::modules::Network::netlinkResponse(void *resp, uint32_t resplen, uint32_t groups) {
struct sockaddr_nl sa = {};
sa.nl_family = AF_NETLINK;
sa.nl_groups = groups;
struct iovec iov = {resp, resplen};
struct msghdr msg = {&sa, sizeof(sa), &iov, 1, nullptr, 0, 0};
auto ret = recvmsg(nl_socket_get_fd(info_sock_), &msg, 0);
if (msg.msg_flags & MSG_TRUNC) {
return -1;
}
return ret;
}
int waybar::modules::Network::scanCb(struct nl_msg *msg, void *data) {
int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
int ret = 0;
auto net = static_cast<waybar::modules::Network *>(data);
auto gnlh = static_cast<genlmsghdr *>(nlmsg_data(nlmsg_hdr(msg)));
struct nlattr* tb[NL80211_ATTR_MAX + 1];
struct nlattr* bss[NL80211_BSS_MAX + 1];
bool need_update = false;
for (nlmsghdr *nh = nlmsg_hdr(msg); NLMSG_OK(nh, ret); nh = NLMSG_NEXT(nh, ret)) {
if (nh->nlmsg_type == RTM_NEWADDR) {
need_update = true;
}
if (nh->nlmsg_type < RTM_NEWADDR) {
auto rtif = static_cast<struct ifinfomsg *>(NLMSG_DATA(nh));
if (rtif->ifi_index == static_cast<int>(net->ifid_)) {
need_update = true;
if (!(rtif->ifi_flags & IFF_RUNNING)) {
net->disconnected();
net->dp.emit();
return NL_SKIP;
}
}
}
if (need_update) break;
}
if (net->ifid_ <= 0 && !net->config_["interface"].isString()) {
for (uint8_t i = 0; i < MAX_RETRY; i += 1) {
net->ifid_ = net->getExternalInterface();
if (net->ifid_ > 0) {
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;
need_update = true;
}
}
if (need_update) {
if (net->ifid_ > 0) {
net->getInfo();
}
net->dp.emit();
}
return NL_SKIP;
}
int waybar::modules::Network::handleScan(struct nl_msg *msg, void *data) {
auto net = static_cast<waybar::modules::Network *>(data);
auto gnlh = static_cast<genlmsghdr *>(nlmsg_data(nlmsg_hdr(msg)));
struct nlattr * tb[NL80211_ATTR_MAX + 1];
struct nlattr * bss[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_FREQUENCY].type = NLA_U32;
@ -298,7 +448,8 @@ int waybar::modules::Network::scanCb(struct nl_msg *msg, void *data) {
bss_policy[NL80211_BSS_SIGNAL_UNSPEC].type = NLA_U8;
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;
}
if (tb[NL80211_ATTR_BSS] == nullptr) {
@ -316,42 +467,43 @@ int waybar::modules::Network::scanCb(struct nl_msg *msg, void *data) {
return NL_SKIP;
}
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) {
auto ies =
static_cast<char*>(nla_data(bss[NL80211_BSS_INFORMATION_ELEMENTS]));
auto ies_len = nla_len(bss[NL80211_BSS_INFORMATION_ELEMENTS]);
auto ies = static_cast<char *>(nla_data(bss[NL80211_BSS_INFORMATION_ELEMENTS]));
auto ies_len = nla_len(bss[NL80211_BSS_INFORMATION_ELEMENTS]);
const auto hdr_len = 2;
while (ies_len > hdr_len && ies[0] != 0) {
ies_len -= ies[1] + hdr_len;
ies += ies[1] + hdr_len;
}
if (ies_len > hdr_len && ies_len > ies[1] + hdr_len) {
auto essid_begin = ies + hdr_len;
auto essid_end = essid_begin + ies[1];
std::copy(essid_begin, essid_end, std::back_inserter(essid_));
auto essid_begin = ies + hdr_len;
auto essid_end = essid_begin + ies[1];
std::string essid_raw;
std::copy(essid_begin, essid_end, std::back_inserter(essid_raw));
essid_ = Glib::Markup::escape_text(essid_raw);
}
}
}
void waybar::modules::Network::parseSignal(struct nlattr **bss) {
if (bss[NL80211_BSS_SIGNAL_MBM] != nullptr) {
// signalstrength in dBm
signal_strength_dbm_ =
static_cast<int>(nla_get_u32(bss[NL80211_BSS_SIGNAL_MBM])) / 100;
// signalstrength in dBm from mBm
signal_strength_dbm_ = nla_get_s32(bss[NL80211_BSS_SIGNAL_MBM]) / 100;
// WiFi-hardware usually operates in the range -90 to -20dBm.
const int hardwareMax = -20;
const int hardwareMin = -90;
signal_strength_ = ((signal_strength_dbm_ - hardwareMin)
/ double{hardwareMax - hardwareMin}) * 100;
signal_strength_ =
((signal_strength_dbm_ - hardwareMin) / double{hardwareMax - hardwareMin}) * 100;
}
if (bss[NL80211_BSS_SIGNAL_UNSPEC] != nullptr) {
signal_strength_ = nla_get_u8(bss[NL80211_BSS_SIGNAL_UNSPEC]);
}
}
bool waybar::modules::Network::associatedOrJoined(struct nlattr** bss)
{
bool waybar::modules::Network::associatedOrJoined(struct nlattr **bss) {
if (bss[NL80211_BSS_STATUS] == nullptr) {
return false;
}
@ -366,16 +518,16 @@ bool waybar::modules::Network::associatedOrJoined(struct nlattr** bss)
}
}
auto waybar::modules::Network::getInfo() -> void
{
struct nl_msg* nl_msg = nlmsg_alloc();
auto waybar::modules::Network::getInfo() -> void {
getInterfaceAddress();
struct nl_msg *nl_msg = nlmsg_alloc();
if (nl_msg == nullptr) {
nl_socket_free(sk_);
return;
}
if (genlmsg_put(nl_msg, NL_AUTO_PORT, NL_AUTO_SEQ, nl80211_id_, 0, NLM_F_DUMP,
NL80211_CMD_GET_SCAN, 0) == nullptr
|| nla_put_u32(nl_msg, NL80211_ATTR_IFINDEX, ifid_) < 0) {
if (genlmsg_put(
nl_msg, NL_AUTO_PORT, NL_AUTO_SEQ, nl80211_id_, 0, NLM_F_DUMP, NL80211_CMD_GET_SCAN, 0) ==
nullptr ||
nla_put_u32(nl_msg, NL80211_ATTR_IFINDEX, ifid_) < 0) {
nlmsg_free(nl_msg);
return;
}

View File

@ -1,10 +1,19 @@
#include "modules/pulseaudio.hpp"
#include <array>
waybar::modules::Pulseaudio::Pulseaudio(const Json::Value& config)
: ALabel(config, "{volume}%"), mainloop_(nullptr), mainloop_api_(nullptr),
context_(nullptr), sink_idx_(0), volume_(0), muted_(false)
{
waybar::modules::Pulseaudio::Pulseaudio(const std::string &id, const Json::Value &config)
: ALabel(config, "{volume}%"),
mainloop_(nullptr),
mainloop_api_(nullptr),
context_(nullptr),
sink_idx_(0),
volume_(0),
muted_(false),
scrolling_(false) {
label_.set_name("pulseaudio");
if (!id.empty()) {
label_.get_style_context()->add_class(id);
}
mainloop_ = pa_threaded_mainloop_new();
if (mainloop_ == nullptr) {
throw std::runtime_error("pa_mainloop_new() failed.");
@ -15,10 +24,9 @@ waybar::modules::Pulseaudio::Pulseaudio(const Json::Value& config)
if (context_ == nullptr) {
throw std::runtime_error("pa_context_new() failed.");
}
if (pa_context_connect(context_, nullptr, PA_CONTEXT_NOAUTOSPAWN,
nullptr) < 0) {
auto err = fmt::format("pa_context_connect() failed: {}",
pa_strerror(pa_context_errno(context_)));
if (pa_context_connect(context_, nullptr, PA_CONTEXT_NOAUTOSPAWN, nullptr) < 0) {
auto err =
fmt::format("pa_context_connect() failed: {}", pa_strerror(pa_context_errno(context_)));
throw std::runtime_error(err);
}
pa_context_set_state_callback(context_, contextStateCb, this);
@ -26,17 +34,22 @@ waybar::modules::Pulseaudio::Pulseaudio(const Json::Value& config)
throw std::runtime_error("pa_mainloop_run() failed.");
}
pa_threaded_mainloop_unlock(mainloop_);
// define the pulse scroll events only when no user provided
// 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() {
mainloop_api_->quit(mainloop_api_, 0);
pa_threaded_mainloop_stop(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);
switch (pa_context_get_state(c)) {
case PA_CONTEXT_TERMINATED:
@ -47,48 +60,95 @@ void waybar::modules::Pulseaudio::contextStateCb(pa_context *c, void *data)
pa_context_set_subscribe_callback(c, subscribeCb, data);
pa_context_subscribe(c, PA_SUBSCRIPTION_MASK_SINK, nullptr, nullptr);
break;
case PA_CONTEXT_FAILED:
pa->mainloop_api_->quit(pa->mainloop_api_, 1);
break;
case PA_CONTEXT_CONNECTING:
case PA_CONTEXT_AUTHORIZING:
case PA_CONTEXT_SETTING_NAME:
break;
case PA_CONTEXT_FAILED:
default:
pa->mainloop_api_->quit(pa->mainloop_api_, 1);
break;
}
}
bool waybar::modules::Pulseaudio::handleScroll(GdkEventScroll *e) {
// Avoid concurrent scroll event
bool direction_up = false;
uint16_t change = config_["scroll-step"].isUInt() ? config_["scroll-step"].asUInt() * 100 : 100;
pa_cvolume pa_volume = pa_volume_;
if (scrolling_) {
return false;
}
scrolling_ = true;
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) {
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;
}
/*
* Called when an event we subscribed to occurs.
*/
void waybar::modules::Pulseaudio::subscribeCb(pa_context* context,
pa_subscription_event_type_t type, uint32_t idx, void* data)
{
void waybar::modules::Pulseaudio::subscribeCb(pa_context * context,
pa_subscription_event_type_t type, uint32_t idx,
void *data) {
unsigned facility = type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK;
if (facility == PA_SUBSCRIPTION_EVENT_SINK) {
pa_context_get_sink_info_by_index(context, idx, sinkInfoCb, data);
}
}
switch (facility) {
case PA_SUBSCRIPTION_EVENT_SINK:
pa_context_get_sink_info_by_index(context, idx, sinkInfoCb, data);
break;
default:
break;
/*
* Called in response to a volume change request
*/
void waybar::modules::Pulseaudio::volumeModifyCb(pa_context *c, int success, void *data) {
auto pa = static_cast<waybar::modules::Pulseaudio *>(data);
if (success != 0) {
pa_context_get_sink_info_by_index(pa->context_, pa->sink_idx_, sinkInfoCb, data);
}
}
/*
* Called when the requested sink information is ready.
*/
void waybar::modules::Pulseaudio::sinkInfoCb(pa_context* /*context*/,
const pa_sink_info* i, int /*eol*/, void* data)
{
void waybar::modules::Pulseaudio::sinkInfoCb(pa_context * /*context*/, const pa_sink_info *i,
int /*eol*/, void * data) {
if (i != nullptr) {
auto pa = static_cast<waybar::modules::Pulseaudio *>(data);
float volume = static_cast<float>(pa_cvolume_avg(&(i->volume)))
/ float{PA_VOLUME_NORM};
pa->pa_volume_ = i->volume;
float volume = static_cast<float>(pa_cvolume_avg(&(pa->pa_volume_))) / float{PA_VOLUME_NORM};
pa->sink_idx_ = i->index;
pa->volume_ = volume * 100.0f;
pa->volume_ = std::round(volume * 100.0F);
pa->muted_ = i->mute != 0;
pa->desc_ = i->description;
pa->port_name_ = i->active_port != nullptr ? i->active_port->name : "Unknown";
pa->dp.emit();
}
}
@ -97,25 +157,55 @@ void waybar::modules::Pulseaudio::sinkInfoCb(pa_context* /*context*/,
* Called when the requested information on the server is ready. This is
* used to find the default PulseAudio sink.
*/
void waybar::modules::Pulseaudio::serverInfoCb(pa_context *context,
const pa_server_info *i, void *data)
{
pa_context_get_sink_info_by_name(context, i->default_sink_name,
sinkInfoCb, data);
void waybar::modules::Pulseaudio::serverInfoCb(pa_context *context, const pa_server_info *i,
void *data) {
pa_context_get_sink_info_by_name(context, i->default_sink_name, sinkInfoCb, data);
}
auto waybar::modules::Pulseaudio::update() -> void
{
static const std::array<std::string, 9> ports = {
"headphones",
"speaker",
"hdmi",
"headset",
"handsfree",
"portable",
"car",
"hifi",
"phone",
};
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_name_;
}
auto waybar::modules::Pulseaudio::update() -> void {
auto format = format_;
if (muted_) {
format =
config_["format-muted"] ? config_["format-muted"].asString() : format;
format = config_["format-muted"].isString() ? config_["format-muted"].asString() : format;
label_.get_style_context()->add_class("muted");
} else {
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(fmt::format(
format, fmt::arg("volume", volume_), fmt::arg("icon", getIcon(volume_, getPortIcon()))));
if (tooltipEnabled()) {
label_.set_tooltip_text(desc_);
}
if (scrolling_) {
scrolling_ = false;
}
label_.set_label(fmt::format(format,
fmt::arg("volume", volume_),
fmt::arg("icon", getIcon(volume_))));
label_.set_tooltip_text(desc_);
}

137
src/modules/sni/host.cpp Normal file
View File

@ -0,0 +1,137 @@
#include "modules/sni/host.hpp"
#include <iostream>
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() {
if (bus_name_id_ > 0) {
Gio::DBus::unwatch_name(bus_name_id_);
bus_name_id_ = 0;
}
if (watcher_id_ > 0) {
Gio::DBus::unwatch_name(watcher_id_);
watcher_id_ = 0;
}
g_cancellable_cancel(cancellable_);
g_clear_object(&cancellable_);
g_clear_object(&watcher_);
}
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,
const Glib::ustring& name_owner) {
if (cancellable_ != nullptr) {
// TODO
return;
}
cancellable_ = g_cancellable_new();
sn_watcher_proxy_new(conn->gobj(),
G_DBUS_PROXY_FLAGS_NONE,
"org.kde.StatusNotifierWatcher",
"/StatusNotifierWatcher",
cancellable_,
&Host::proxyReady,
this);
}
void Host::nameVanished(const Glib::RefPtr<Gio::DBus::Connection>& conn, const Glib::ustring name) {
g_cancellable_cancel(cancellable_);
g_clear_object(&cancellable_);
g_clear_object(&watcher_);
items_.clear();
}
void Host::proxyReady(GObject* src, GAsyncResult* res, gpointer data) {
GError* error = nullptr;
SnWatcher* watcher = sn_watcher_proxy_new_finish(res, &error);
if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
std::cerr << error->message << std::endl;
g_error_free(error);
return;
}
auto host = static_cast<SNI::Host*>(data);
host->watcher_ = watcher;
if (error != nullptr) {
std::cerr << error->message << std::endl;
g_error_free(error);
return;
}
sn_watcher_call_register_host(
host->watcher_, host->object_path_.c_str(), host->cancellable_, &Host::registerHost, data);
}
void Host::registerHost(GObject* src, GAsyncResult* res, gpointer data) {
GError* error = nullptr;
sn_watcher_call_register_host_finish(SN_WATCHER(src), res, &error);
if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
std::cerr << error->message << std::endl;
g_error_free(error);
return;
}
auto host = static_cast<SNI::Host*>(data);
if (error != nullptr) {
std::cerr << error->message << std::endl;
g_error_free(error);
return;
}
g_signal_connect(host->watcher_, "item-registered", G_CALLBACK(&Host::itemRegistered), data);
g_signal_connect(host->watcher_, "item-unregistered", G_CALLBACK(&Host::itemUnregistered), data);
auto items = sn_watcher_dup_registered_items(host->watcher_);
if (items != nullptr) {
for (uint32_t i = 0; items[i] != nullptr; i += 1) {
host->addRegisteredItem(items[i]);
}
}
g_strfreev(items);
}
void Host::itemRegistered(SnWatcher* watcher, const gchar* service, gpointer data) {
auto host = static_cast<SNI::Host*>(data);
host->addRegisteredItem(service);
}
void Host::itemUnregistered(SnWatcher* watcher, const gchar* service, gpointer data) {
auto host = static_cast<SNI::Host*>(data);
auto [bus_name, object_path] = host->getBusNameAndObjectPath(service);
for (auto it = host->items_.begin(); it != host->items_.end(); ++it) {
if ((*it)->bus_name == bus_name && (*it)->object_path == object_path) {
host->on_remove_(*it);
host->items_.erase(it);
break;
}
}
}
std::tuple<std::string, std::string> Host::getBusNameAndObjectPath(const std::string service) {
auto it = service.find('/');
if (it != std::string::npos) {
return {service.substr(0, it), service.substr(it)};
}
return {service, "/StatusNotifierItem"};
}
void Host::addRegisteredItem(std::string service) {
auto [bus_name, object_path] = getBusNameAndObjectPath(service);
items_.emplace_back(new Item(bus_name, object_path, config_));
on_add_(items_.back());
}
}

325
src/modules/sni/item.cpp Normal file
View File

@ -0,0 +1,325 @@
#include "modules/sni/item.hpp"
#include <glibmm/main.h>
#include <iostream>
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()) {
icon_size = config["icon-size"].asUInt();
}
event_box.add(image);
event_box.add_events(Gdk::BUTTON_PRESS_MASK);
event_box.signal_button_press_event().connect(sigc::mem_fun(*this, &Item::handleClick));
cancellable_ = Gio::Cancellable::create();
auto interface = Glib::wrap(sn_item_interface_info(), true);
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 Item::proxyReady(Glib::RefPtr<Gio::AsyncResult>& result) {
try {
this->proxy_ = Gio::DBus::Proxy::create_for_bus_finish(result);
/* Properties are already cached during object creation */
auto cached_properties = this->proxy_->get_cached_property_names();
for (const auto& name : cached_properties) {
Glib::VariantBase value;
this->proxy_->get_cached_property(value, name);
setProperty(name, value);
}
this->proxy_->signal_signal().connect(sigc::mem_fun(*this, &Item::onSignal));
if (this->id.empty() || this->category.empty() || this->status.empty()) {
std::cerr << "Invalid Status Notifier Item: " + this->bus_name + "," + this->object_path
<< std::endl;
return;
}
this->updateImage();
// this->event_box.set_tooltip_text(this->title);
} catch (const Glib::Error& err) {
g_error("Failed to create DBus Proxy for %s %s: %s",
bus_name.c_str(),
object_path.c_str(),
err.what().c_str());
} catch (const std::exception& err) {
g_error("Failed to create DBus Proxy for %s %s: %s",
bus_name.c_str(),
object_path.c_str(),
err.what());
}
}
template <typename T>
T get_variant(Glib::VariantBase& value) {
return Glib::VariantBase::cast_dynamic<Glib::Variant<T>>(value).get();
}
void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& 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);
} else if (name == "ItemIsMenu") {
item_is_menu = get_variant<bool>(value);
}
}
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 (!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) {
g_warning("Failed to update properties: %s", err.what().c_str());
} catch (const std::exception& err) {
g_warning("Failed to update properties: %s", err.what());
}
}
void Item::onSignal(const Glib::ustring& sender_name, const Glib::ustring& signal_name,
const Glib::VariantContainerBase& arguments) {
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);
if (it == nullptr) {
return Glib::RefPtr<Gdk::Pixbuf>{};
}
GVariant* val;
gint lwidth = 0;
gint lheight = 0;
gint width;
gint height;
guchar* array = nullptr;
while (g_variant_iter_loop(it, "(ii@ay)", &width, &height, &val)) {
if (width > 0 && height > 0 && val != nullptr && width * height > lwidth * lheight) {
auto size = g_variant_get_size(val);
/* Sanity check */
if (size == 4U * width * height) {
/* Find the largest image */
gconstpointer data = g_variant_get_data(val);
if (data != nullptr) {
if (array != nullptr) {
g_free(array);
}
array = static_cast<guchar*>(g_memdup(data, size));
lwidth = width;
lheight = height;
}
}
}
}
g_variant_iter_free(it);
if (array != nullptr) {
/* argb to rgba */
for (uint32_t i = 0; i < 4U * lwidth * lheight; i += 4) {
guchar alpha = array[i];
array[i] = array[i + 1];
array[i + 1] = array[i + 2];
array[i + 2] = array[i + 3];
array[i + 3] = alpha;
}
return Gdk::Pixbuf::create_from_data(array,
Gdk::Colorspace::COLORSPACE_RGB,
true,
8,
lwidth,
lheight,
4 * lwidth,
&pixbuf_data_deleter);
}
return Glib::RefPtr<Gdk::Pixbuf>{};
}
void Item::updateImage() {
image.set_from_icon_name("image-missing", Gtk::ICON_SIZE_MENU);
image.set_pixel_size(icon_size);
if (!icon_name.empty()) {
try {
// Try to find icons specified by path and filename
#ifdef FILESYSTEM_EXPERIMENTAL
if (std::experimental::filesystem::exists(icon_name)) {
#else
if (std::filesystem::exists(icon_name)) {
#endif
auto pixbuf = Gdk::Pixbuf::create_from_file(icon_name);
if (pixbuf->gobj() != nullptr) {
// An icon specified by path and filename may be the wrong size for
// the tray
pixbuf = pixbuf->scale_simple(icon_size, icon_size, Gdk::InterpType::INTERP_BILINEAR);
image.set(pixbuf);
}
} else {
image.set(getIconByName(icon_name, icon_size));
}
} catch (Glib::Error& e) {
std::cerr << "Exception: " << e.what() << std::endl;
}
} else if (icon_pixmap) {
// An icon extracted may be the wrong size for the tray
icon_pixmap = icon_pixmap->scale_simple(icon_size, icon_size, Gdk::InterpType::INTERP_BILINEAR);
image.set(icon_pixmap);
}
}
Glib::RefPtr<Gdk::Pixbuf> Item::getIconByName(const std::string& name, int request_size) {
int tmp_size = 0;
icon_theme->rescan_if_needed();
auto sizes = icon_theme->get_icon_sizes(name.c_str());
for (auto const& size : sizes) {
// -1 == scalable
if (size == request_size || size == -1) {
tmp_size = request_size;
break;
} else if (size < request_size) {
tmp_size = size;
} else if (size > tmp_size && tmp_size > 0) {
tmp_size = request_size;
break;
}
}
if (tmp_size == 0) {
tmp_size = request_size;
}
if (!icon_theme_path.empty() &&
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 Item::onMenuDestroyed(Item* self) {
self->gtk_menu = nullptr;
self->dbus_menu = nullptr;
}
bool Item::makeMenu(GdkEventButton* const& ev) {
if (gtk_menu == nullptr) {
if (!menu.empty()) {
dbus_menu = dbusmenu_gtkmenu_new(bus_name.data(), menu.data());
if (dbus_menu != nullptr) {
g_object_ref_sink(G_OBJECT(dbus_menu));
g_object_weak_ref(G_OBJECT(dbus_menu), (GWeakNotify)onMenuDestroyed, this);
gtk_menu = Glib::wrap(GTK_MENU(dbus_menu));
gtk_menu->attach_to_widget(event_box);
}
}
}
if (gtk_menu != nullptr) {
#if GTK_CHECK_VERSION(3, 22, 0)
gtk_menu->popup_at_pointer(reinterpret_cast<GdkEvent*>(ev));
#else
gtk_menu->popup(ev->button, ev->time);
#endif
return true;
}
return false;
}
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) {
if (!makeMenu(ev)) {
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 false;
}
} // namespace waybar::modules::SNI

42
src/modules/sni/tray.cpp Normal file
View File

@ -0,0 +1,42 @@
#include "modules/sni/tray.hpp"
#include <iostream>
namespace waybar::modules::SNI {
Tray::Tray(const std::string& id, const Bar& bar, const Json::Value& config)
: config_(config),
box_(bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0),
host_(nb_hosts_, config, std::bind(&Tray::onAdd, this, std::placeholders::_1),
std::bind(&Tray::onRemove, this, std::placeholders::_1)) {
box_.set_name("tray");
if (!id.empty()) {
box_.get_style_context()->add_class(id);
}
if (config_["spacing"].isUInt()) {
box_.set_spacing(config_["spacing"].asUInt());
}
nb_hosts_ += 1;
dp.emit();
}
void Tray::onAdd(std::unique_ptr<Item>& item) {
box_.pack_start(item->event_box);
dp.emit();
}
void Tray::onRemove(std::unique_ptr<Item>& item) {
box_.remove(item->event_box);
dp.emit();
}
auto Tray::update() -> void {
if (box_.get_children().empty()) {
box_.hide();
} else {
box_.show_all();
}
}
Tray::operator Gtk::Widget&() { return box_; }
}

197
src/modules/sni/watcher.cpp Normal file
View File

@ -0,0 +1,197 @@
#include "modules/sni/watcher.hpp"
#include <iostream>
using namespace waybar::modules::SNI;
Watcher::Watcher()
: bus_name_id_(Gio::DBus::own_name(Gio::DBus::BusType::BUS_TYPE_SESSION,
"org.kde.StatusNotifierWatcher",
sigc::mem_fun(*this, &Watcher::busAcquired),
Gio::DBus::SlotNameAcquired(), Gio::DBus::SlotNameLost(),
Gio::DBus::BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT |
Gio::DBus::BUS_NAME_OWNER_FLAGS_REPLACE)),
watcher_(sn_watcher_skeleton_new()) {}
Watcher::~Watcher() {
if (bus_name_id_ > 0) {
g_bus_unown_name(bus_name_id_);
bus_name_id_ = 0;
}
if (hosts_ != nullptr) {
g_slist_free_full(hosts_, gfWatchFree);
hosts_ = nullptr;
}
if (items_ != nullptr) {
g_slist_free_full(items_, gfWatchFree);
items_ = nullptr;
}
g_dbus_interface_skeleton_unexport(G_DBUS_INTERFACE_SKELETON(watcher_));
}
void Watcher::busAcquired(const Glib::RefPtr<Gio::DBus::Connection>& conn, Glib::ustring name) {
GError* error = nullptr;
g_dbus_interface_skeleton_export(
G_DBUS_INTERFACE_SKELETON(watcher_), conn->gobj(), "/StatusNotifierWatcher", &error);
if (error != nullptr) {
std::cerr << error->message << std::endl;
g_error_free(error);
return;
}
g_signal_connect_swapped(
watcher_, "handle-register-item", G_CALLBACK(&Watcher::handleRegisterItem), this);
g_signal_connect_swapped(
watcher_, "handle-register-host", G_CALLBACK(&Watcher::handleRegisterHost), this);
}
gboolean Watcher::handleRegisterHost(Watcher* obj, GDBusMethodInvocation* invocation,
const gchar* service) {
const gchar* bus_name = service;
const gchar* object_path = "/StatusNotifierHost";
if (*service == '/') {
bus_name = g_dbus_method_invocation_get_sender(invocation);
object_path = service;
}
if (g_dbus_is_name(bus_name) == FALSE) {
g_dbus_method_invocation_return_error(invocation,
G_DBUS_ERROR,
G_DBUS_ERROR_INVALID_ARGS,
"D-Bus bus name '%s' is not valid",
bus_name);
return TRUE;
}
auto watch = gfWatchFind(obj->hosts_, bus_name, object_path);
if (watch != nullptr) {
g_dbus_method_invocation_return_error(
invocation,
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;
}
watch = gfWatchNew(GF_WATCH_TYPE_HOST, service, bus_name, object_path, obj);
obj->hosts_ = g_slist_prepend(obj->hosts_, watch);
if (!sn_watcher_get_is_host_registered(obj->watcher_)) {
sn_watcher_set_is_host_registered(obj->watcher_, TRUE);
sn_watcher_emit_host_registered(obj->watcher_);
}
sn_watcher_complete_register_host(obj->watcher_, invocation);
return TRUE;
}
gboolean Watcher::handleRegisterItem(Watcher* obj, GDBusMethodInvocation* invocation,
const gchar* service) {
const gchar* bus_name = service;
const gchar* object_path = "/StatusNotifierItem";
if (*service == '/') {
bus_name = g_dbus_method_invocation_get_sender(invocation);
object_path = service;
}
if (g_dbus_is_name(bus_name) == FALSE) {
g_dbus_method_invocation_return_error(invocation,
G_DBUS_ERROR,
G_DBUS_ERROR_INVALID_ARGS,
"D-Bus bus name '%s' is not valid",
bus_name);
return TRUE;
}
auto watch = gfWatchFind(obj->items_, bus_name, object_path);
if (watch != nullptr) {
g_warning("Status Notifier Item with bus name '%s' and object path '%s' is already registered",
bus_name,
object_path);
sn_watcher_complete_register_item(obj->watcher_, invocation);
return TRUE;
}
watch = gfWatchNew(GF_WATCH_TYPE_ITEM, service, bus_name, object_path, obj);
obj->items_ = g_slist_prepend(obj->items_, watch);
obj->updateRegisteredItems(obj->watcher_);
gchar* tmp = g_strdup_printf("%s%s", bus_name, object_path);
sn_watcher_emit_item_registered(obj->watcher_, tmp);
g_free(tmp);
sn_watcher_complete_register_item(obj->watcher_, invocation);
return TRUE;
}
Watcher::GfWatch* Watcher::gfWatchFind(GSList* list, const gchar* bus_name,
const gchar* object_path) {
for (GSList* l = list; l != nullptr; l = g_slist_next(l)) {
auto watch = static_cast<GfWatch*>(l->data);
if (g_strcmp0(watch->bus_name, bus_name) == 0 &&
g_strcmp0(watch->object_path, object_path) == 0) {
return watch;
}
}
return nullptr;
}
void Watcher::gfWatchFree(gpointer data) {
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);
watch->type = type;
watch->watcher = watcher;
watch->service = g_strdup(service);
watch->bus_name = g_strdup(bus_name);
watch->object_path = g_strdup(object_path);
watch->watch_id = g_bus_watch_name(G_BUS_TYPE_SESSION,
bus_name,
G_BUS_NAME_WATCHER_FLAGS_NONE,
nullptr,
&Watcher::nameVanished,
watch,
nullptr);
return watch;
}
void Watcher::nameVanished(GDBusConnection* connection, const char* name, gpointer data) {
auto watch = static_cast<GfWatch*>(data);
if (watch->type == GF_WATCH_TYPE_HOST) {
watch->watcher->hosts_ = g_slist_remove(watch->watcher->hosts_, watch);
if (watch->watcher->hosts_ == nullptr) {
sn_watcher_set_is_host_registered(watch->watcher->watcher_, FALSE);
sn_watcher_emit_host_registered(watch->watcher->watcher_);
}
} else if (watch->type == GF_WATCH_TYPE_ITEM) {
watch->watcher->items_ = g_slist_remove(watch->watcher->items_, watch);
watch->watcher->updateRegisteredItems(watch->watcher->watcher_);
gchar* tmp = g_strdup_printf("%s%s", watch->bus_name, watch->object_path);
sn_watcher_emit_item_unregistered(watch->watcher->watcher_, tmp);
g_free(tmp);
}
}
void Watcher::updateRegisteredItems(SnWatcher* obj) {
GVariantBuilder builder;
g_variant_builder_init(&builder, G_VARIANT_TYPE("as"));
for (GSList* l = items_; l != nullptr; l = g_slist_next(l)) {
auto watch = static_cast<GfWatch*>(l->data);
gchar* item = g_strdup_printf("%s%s", watch->bus_name, watch->object_path);
g_variant_builder_add(&builder, "s", item);
g_free(item);
}
GVariant* variant = g_variant_builder_end(&builder);
const gchar** items = g_variant_get_strv(variant, nullptr);
sn_watcher_set_registered_items(obj, items);
g_variant_unref(variant);
g_free(items);
}

View File

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

49
src/modules/sway/mode.cpp Normal file
View File

@ -0,0 +1,49 @@
#include "modules/sway/mode.hpp"
namespace waybar::modules::sway {
Mode::Mode(const std::string& id, const Bar& bar, const Json::Value& config)
: ALabel(config, "{}"), bar_(bar) {
label_.set_name("mode");
if (!id.empty()) {
label_.get_style_context()->add_class(id);
}
ipc_.subscribe(R"(["mode"])");
ipc_.signal_event.connect(sigc::mem_fun(*this, &Mode::onEvent));
// Launch worker
worker();
dp.emit();
}
void Mode::onEvent(const struct Ipc::ipc_response &res) {
if (res.payload["change"] != "default") {
mode_ = res.payload["change"].asString();
} else {
mode_.clear();
}
dp.emit();
}
void Mode::worker() {
thread_ = [this] {
try {
ipc_.handleEvent();
} catch (const std::exception& e) {
std::cerr << "Mode: " << e.what() << std::endl;
}
};
}
auto Mode::update() -> void {
if (mode_.empty()) {
event_box_.hide();
} else {
label_.set_markup(fmt::format(format_, mode_));
if (tooltipEnabled()) {
label_.set_tooltip_text(mode_);
}
event_box_.show();
}
}
} // namespace waybar::modules::sway

View File

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

View File

@ -1,215 +1,233 @@
#include "modules/sway/workspaces.hpp"
waybar::modules::sway::Workspaces::Workspaces(Bar& bar,
const Json::Value& config)
: bar_(bar), config_(config), scrolling_(false)
{
namespace waybar::modules::sway {
Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value &config)
: bar_(bar),
config_(config),
box_(bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0),
scrolling_(false) {
box_.set_name("workspaces");
ipc_.connect();
ipc_.subscribe("[ \"workspace\" ]");
if (!id.empty()) {
box_.get_style_context()->add_class(id);
}
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);
// Launch worker
worker();
}
void waybar::modules::sway::Workspaces::worker()
{
void Workspaces::onEvent(const struct Ipc::ipc_response &res) { ipc_.sendCmd(IPC_GET_WORKSPACES); }
void Workspaces::onCmd(const struct Ipc::ipc_response &res) {
if (res.type == IPC_GET_WORKSPACES) {
if (res.payload.isArray()) {
std::lock_guard<std::mutex> lock(mutex_);
workspaces_.clear();
std::copy_if(res.payload.begin(),
res.payload.end(),
std::back_inserter(workspaces_),
[&](const auto &workspace) {
return !config_["all-outputs"].asBool()
? workspace["output"].asString() == bar_.output->name
: true;
});
dp.emit();
}
} else {
if (scrolling_) {
scrolling_ = false;
}
}
}
void Workspaces::worker() {
thread_ = [this] {
try {
// Wait for the name of the output
if (!config_["all-outputs"].asBool() && bar_.output_name.empty()) {
while (bar_.output_name.empty()) {
thread_.sleep_for(chrono::milliseconds(150));
}
} else if (!workspaces_.empty()) {
ipc_.handleEvent();
}
{
std::lock_guard<std::mutex> lock(mutex_);
auto res = ipc_.sendCmd(IPC_GET_WORKSPACES);
workspaces_ = parser_.parse(res.payload);
}
dp.emit();
} catch (const std::exception& e) {
std::cerr << e.what() << std::endl;
ipc_.handleEvent();
} catch (const std::exception &e) {
std::cerr << "Workspaces: " << e.what() << std::endl;
}
};
}
auto waybar::modules::sway::Workspaces::update() -> void
{
bool Workspaces::filterButtons() {
bool needReorder = false;
std::lock_guard<std::mutex> lock(mutex_);
for (auto it = buttons_.begin(); it != buttons_.end();) {
auto ws = std::find_if(workspaces_.begin(), workspaces_.end(),
[it](auto node) -> bool { return node["num"].asInt() == it->first; });
if (ws == workspaces_.end()) {
auto ws = std::find_if(workspaces_.begin(), workspaces_.end(), [it](const auto &node) {
return node["name"].asString() == it->first;
});
if (ws == workspaces_.end() ||
(!config_["all-outputs"].asBool() && (*ws)["output"].asString() != bar_.output->name)) {
it = buttons_.erase(it);
needReorder = true;
} else {
++it;
}
}
for (auto node : workspaces_) {
if (!config_["all-outputs"].asBool()
&& bar_.output_name != node["output"].asString()) {
continue;
}
auto it = buttons_.find(node["num"].asInt());
if (it == buttons_.end()) {
addWorkspace(node);
return needReorder;
}
auto Workspaces::update() -> void {
std::lock_guard<std::mutex> lock(mutex_);
bool needReorder = filterButtons();
for (auto it = workspaces_.begin(); it != workspaces_.end(); ++it) {
auto bit = buttons_.find((*it)["name"].asString());
if (bit == buttons_.end()) {
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, node["num"].asInt());
}
button.show();
}
}
if (scrolling_) {
scrolling_ = false;
auto &button = bit == buttons_.end() ? addButton(*it) : bit->second;
if ((*it)["focused"].asBool()) {
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 (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(Json::Value node)
{
auto icon = getIcon(node["name"].asString());
auto pair = buttons_.emplace(node["num"].asInt(), icon);
Gtk::Button &Workspaces::addButton(const Json::Value &node) {
auto pair = buttons_.emplace(node["name"].asString(), node["name"].asString());
auto &button = pair.first->second;
if (icon != node["name"].asString()) {
button.get_style_context()->add_class("icon");
}
box_.pack_start(button, false, false, 0);
button.set_relief(Gtk::RELIEF_NONE);
button.signal_clicked().connect([this, pair] {
try {
std::lock_guard<std::mutex> lock(mutex_);
auto cmd = fmt::format("workspace \"{}\"", pair.first->first);
ipc_.sendCmd(IPC_COMMAND, cmd);
} catch (const std::exception& e) {
ipc_.sendCmd(IPC_COMMAND, fmt::format("workspace \"{}\"", pair.first->first));
} catch (const std::exception &e) {
std::cerr << e.what() << std::endl;
}
});
button.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);
if (!config_["disable-scroll"].asBool()) {
button.signal_scroll_event()
.connect(sigc::mem_fun(*this, &Workspaces::handleScroll));
button.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);
button.signal_scroll_event().connect(sigc::mem_fun(*this, &Workspaces::handleScroll));
}
box_.reorder_child(button, node["num"].asInt());
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();
return button;
}
std::string waybar::modules::sway::Workspaces::getIcon(std::string name)
{
if (config_["format-icons"][name]) {
return config_["format-icons"][name].asString();
}
if (config_["format-icons"]["default"]) {
return config_["format-icons"]["default"].asString();
std::string Workspaces::getIcon(const std::string &name, const Json::Value &node) {
std::vector<std::string> keys = {name, "urgent", "focused", "visible", "default"};
for (auto const &key : keys) {
if (key == "focused" || key == "visible" || key == "urgent") {
if (config_["format-icons"][key].isString() && node[key].asBool()) {
return config_["format-icons"][key].asString();
}
} else if (config_["format-icons"][key].isString()) {
return config_["format-icons"][key].asString();
}
}
return name;
}
bool waybar::modules::sway::Workspaces::handleScroll(GdkEventScroll *e)
{
bool Workspaces::handleScroll(GdkEventScroll *e) {
// Avoid concurrent scroll event
if (scrolling_) {
return false;
}
std::string name;
scrolling_ = true;
int id = -1;
uint16_t idx = 0;
{
std::lock_guard<std::mutex> lock(mutex_);
for (; idx < workspaces_.size(); idx += 1) {
if (workspaces_[idx]["focused"].asBool()) {
id = workspaces_[idx]["num"].asInt();
break;
}
}
}
if (id == -1) {
scrolling_ = false;
return false;
}
if (e->direction == GDK_SCROLL_UP) {
id = getNextWorkspace();
}
if (e->direction == GDK_SCROLL_DOWN) {
id = 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) {
id = getNextWorkspace();
} else if (delta_y > 0) {
id = getPrevWorkspace();
}
}
{
std::lock_guard<std::mutex> lock(mutex_);
if (id == workspaces_[idx]["num"].asInt()) {
auto it = std::find_if(workspaces_.begin(), workspaces_.end(), [](const auto &workspace) {
return workspace["focused"].asBool();
});
if (it == workspaces_.end()) {
scrolling_ = false;
return false;
}
switch (e->direction) {
case GDK_SCROLL_DOWN:
case GDK_SCROLL_RIGHT:
name = getCycleWorkspace(it, false);
break;
case GDK_SCROLL_UP:
case GDK_SCROLL_LEFT:
name = getCycleWorkspace(it, true);
break;
case 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 = getCycleWorkspace(it, true);
} else if (delta_y > 0) {
name = getCycleWorkspace(it, false);
}
break;
default:
break;
}
if (name.empty() || name == (*it)["name"].asString()) {
scrolling_ = false;
return false;
}
ipc_.sendCmd(IPC_COMMAND, fmt::format("workspace \"{}\"", id));
std::this_thread::sleep_for(std::chrono::milliseconds(150));
}
ipc_.sendCmd(IPC_COMMAND, fmt::format("workspace \"{}\"", name));
return true;
}
int waybar::modules::sway::Workspaces::getPrevWorkspace()
{
for (uint16_t i = 0; i != workspaces_.size(); i += 1) {
if (workspaces_[i]["focused"].asBool()) {
if (i > 0) {
return workspaces_[i - 1]["num"].asInt();
}
return workspaces_[workspaces_.size() - 1]["num"].asInt();
}
const std::string Workspaces::getCycleWorkspace(std::vector<Json::Value>::iterator it,
bool prev) const {
if (prev && it == workspaces_.begin()) {
return (*(--workspaces_.end()))["name"].asString();
}
return -1;
if (prev && it != workspaces_.begin())
--it;
else if (!prev && it != workspaces_.end())
++it;
if (!prev && it == workspaces_.end()) {
return (*(++workspaces_.begin()))["name"].asString();
}
return (*it)["name"].asString();
}
int waybar::modules::sway::Workspaces::getNextWorkspace()
{
for (uint16_t i = 0; i != workspaces_.size(); i += 1) {
if (workspaces_[i]["focused"].asBool()) {
if (i + 1U < workspaces_.size()) {
return workspaces_[i + 1]["num"].asInt();
}
return workspaces_[0]["num"].asInt();
}
std::string Workspaces::trimWorkspaceName(std::string name) {
std::size_t found = name.find(':');
if (found != std::string::npos) {
return name.substr(found + 1);
}
return -1;
return name;
}
waybar::modules::sway::Workspaces::operator Gtk::Widget &() {
return box_;
void Workspaces::onButtonReady(const Json::Value &node, Gtk::Button &button) {
if (config_["current-only"].asBool()) {
if (node["focused"].asBool()) {
button.show();
} else {
button.hide();
}
} else {
button.show();
}
}
Workspaces::operator Gtk::Widget &() { return box_; }
} // namespace waybar::modules::sway

View File

@ -0,0 +1,61 @@
#include "modules/temperature.hpp"
waybar::modules::Temperature::Temperature(const std::string& id, const Json::Value& config)
: ALabel(config, "{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);
}
#ifdef FILESYSTEM_EXPERIMENTAL
if (!std::experimental::filesystem::exists(file_path_)) {
#else
if (!std::filesystem::exists(file_path_)) {
#endif
throw std::runtime_error("Can't open " + file_path_);
}
label_.set_name("temperature");
if (!id.empty()) {
label_.get_style_context()->add_class(id);
}
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");
}
label_.set_markup(fmt::format(
format, fmt::arg("temperatureC", temperature_c), fmt::arg("temperatureF", temperature_f)));
}
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();
}

10
subprojects/fmt.wrap Normal file
View File

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

View File

@ -1,10 +0,0 @@
[wrap-file]
directory = fmt-4.1.0
source_url = https://github.com/fmtlib/fmt/archive/4.1.0.tar.gz
source_filename = fmt-4.1.0.tar.gz
source_hash = 46628a2f068d0e33c716be0ed9dcae4370242df135aed663a180b9fd8e36733d
patch_url = https://wrapdb.mesonbuild.com/v1/projects/fmt/4.1.0/1/get_zip
patch_filename = fmt-4.1.0-1-wrap.zip
patch_hash = 741931f01e558491724fc1c67bff996d1df79c0277626fc463de138052c9ecc0