Compare commits

...

454 Commits
0.0.3 ... 0.6.1

Author SHA1 Message Date
Alex
82bed9dd5e chore: v0.6.1 2019-05-02 14:31:02 +02:00
Alex
d027243a19 fix: json thread safe 2019-05-02 14:24:54 +02:00
Alex
e6d59f05cc fix s/hidded/hidden/ typo (#295)
fix s/hidded/hidden/ typo
2019-05-02 13:59:57 +02:00
Eric Engestrom
4d4562aade fix s/hidded/hidden/ typo 2019-05-01 12:40:12 +01:00
Alex
e8f31a0c4f revert: infinite seconds for once custom modules 2019-04-26 21:57:15 +02:00
Alex
f8c06b27ae Revert "feat(Cpu): dynamic fmt args"
This reverts commit 2d9bcb1a2d.
2019-04-26 21:49:16 +02:00
Alex
717a07d584 refactor(Window): simpler conditions 2019-04-26 15:29:54 +02:00
Alex
2d9bcb1a2d feat(Cpu): dynamic fmt args 2019-04-26 14:07:31 +02:00
Alex
4dd36890c1 style: background color transition 2019-04-26 12:37:35 +02:00
Alex
66acaeca7f style: workspaces button color for chromium class 2019-04-26 12:01:42 +02:00
Alex
20cf7592aa Merge pull request #287 from jonvaldes/master
Allow rotating label contents by specifying a new "rotate" property
2019-04-26 09:39:52 +02:00
Jon Valdés
9fe29c37b4 Fix indentation 2019-04-25 22:56:14 +02:00
Jon Valdés
f8ae1534db Allow rotating label contents by specifying a new "rotate" property in the label config 2019-04-25 22:47:58 +02:00
Alex
46c91a26ac style: workspaces button color for chromium class 2019-04-25 17:14:16 +02:00
Alex
07c592cc86 chore: v0.6.0 2019-04-25 16:59:22 +02:00
Alex
bb8ff5a99f feat(Bar): add class depend of window in the workspace 2019-04-25 16:47:51 +02:00
Alex
79a5e9ecee feat: multiple bar with same process 2019-04-25 13:25:06 +02:00
Alex
9504b7ac03 fix(Bar): typo 2019-04-24 12:42:16 +02:00
Alex
311c34ecbc feat(Bar): handle widget size changes 2019-04-24 12:37:24 +02:00
Alex
90d89fe974 refactor: kill custom modules scripts en destroy 2019-04-23 15:56:38 +02:00
Alex
cccf60c30e fix(Workspaces): fix concurrence and move json parser to ipc client 2019-04-23 11:42:08 +02:00
Alex
07dba791cf Update fmt build dependency (#284)
Update fmt build dependency
2019-04-23 09:46:03 +02:00
Hendrik Schröter
3ee99946c7 chore: update fmt build dependency
Closes #279
2019-04-23 04:40:27 +02:00
Alex
0d0f5ed7db chore: update fmt wrap 2019-04-21 19:19:38 +02:00
Alex
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
Minijackson
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
Alex
768bc29bc1 Merge pull request #275 from cole-h/issue-273
Ensure no NULL tags are set
2019-04-20 23:32:13 +02:00
Cole Helbling
12e1233d38 Fix compile-time warning of catch by value 2019-04-20 09:16:11 -07:00
Cole Helbling
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
Alex
d03997bdaa Merge pull request #272 from Alexays/refactoring
Refactoring
2019-04-19 17:33:49 +02:00
Alex
471b5b1ea1 Merge branch 'master' into refactoring 2019-04-19 17:33:18 +02:00
Alex
29d8f365f8 refactor(Tray): proper lookup in the default theme 2019-04-19 17:30:40 +02:00
Alex
8cf19826aa fix(Tray): Unexport on exit 2019-04-19 17:03:46 +02:00
Alex
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
Alex
e77c155ede fix(workspaces): avoid mutex block 2019-04-19 12:11:55 +02:00
Alex
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
Alex
171ecd53aa refactor(Bar): roundtrip before setup widgets 2019-04-19 11:56:40 +02:00
Minijackson
66b0420391 fix(mpd): Escape MPD values in the label 2019-04-19 11:11:44 +02:00
Alex
42dc0c4c71 fix(ipc): typo 2019-04-19 11:10:48 +02:00
Alex
bb1cf7570e refactor(IPC): use sigc signal 2019-04-19 11:09:06 +02:00
Alex
a14b933d3e fix(config): add missing comma 2019-04-18 17:55:02 +02:00
Alex
4c8f4f82dc fix(config): add missing comma 2019-04-18 17:54:38 +02:00
Alex
6ed8f94dab refactor: format code 2019-04-18 17:52:00 +02:00
Alex
807ef32357 refactor: format && better output management 2019-04-18 17:47:40 +02:00
Alex
817c42841b Merge pull request #268 from minijackson/mpd
Add MPD support
2019-04-18 16:11:16 +02:00
Minijackson
3e54c3c669 fix(mpd): better sample theme 2019-04-18 15:57:58 +02:00
Minijackson
3656035c89 fix(mpd): slightly better and safer error handling 2019-04-18 15:57:57 +02:00
Minijackson
0ce8821aec feat(mpd): Add playing / paused classes 2019-04-18 15:57:57 +02:00
Minijackson
38e37f3680 chore(mpd): add sample MPD config 2019-04-18 15:57:57 +02:00
Minijackson
ab43d34a1e refactor(mpd): Add module name to log messages 2019-04-18 15:57:57 +02:00
Minijackson
22eccc2ac2 feat(mpd): reset player state when connection drops 2019-04-18 15:57:57 +02:00
Minijackson
cd92b475ad chore: Add clang-format configuration and format MPD module 2019-04-18 15:57:57 +02:00
Minijackson
235997fa73 feat(mpd): Add support for elapsed and total time 2019-04-18 15:55:46 +02:00
Minijackson
80a12d0238 feat(mpd): play/pause on click & stop on right-click 2019-04-18 15:55:46 +02:00
Minijackson
07dab2baec feat(mpd): Add support for options (random, repeat, etc.) 2019-04-18 15:55:45 +02:00
Minijackson
cbfcec4867 feat(mpd): Add support for play/pause icons 2019-04-18 15:55:45 +02:00
Minijackson
557b786ce0 feat(mpd): Allow for specifying the reconnect interval 2019-04-18 15:55:45 +02:00
Minijackson
8c9dd94670 feat(mpd): Add support for setting tooltip label when disconnected 2019-04-18 15:55:45 +02:00
Minijackson
06aff70e2e feat: Add basic support for MPD 2019-04-18 15:55:45 +02:00
Minijackson
85b6e6f709 chore: Add EditorConfig file 2019-04-18 15:55:45 +02:00
Alex
6d6df4be00 refactor(sni-item): better way to search in default theme 2019-04-17 22:15:18 +02:00
Alex
9564adb5b4 refactor(Bar): avoid reinterpret_cast 2019-04-17 19:33:49 +02:00
Alex
aeaa1927d9 fix: add default_paths on init 2019-04-17 19:23:52 +02:00
Alex
346ec68578 refactor: format tray && partial fix for #235 2019-04-17 14:19:04 +02:00
Alex
93a1bafb46 chore: add clang-format 2019-04-17 13:47:34 +02:00
Alex
5f37abbd3f Update README.md 2019-04-16 13:42:49 +02:00
Alex
3273ee8b42 fix(Tray): icon size lookup 2019-04-15 12:10:37 +02:00
Alex
d05b8398fa fix: prefer to hold running even when no window is open 2019-04-15 11:42:16 +02:00
Alex
ecc5f48dd7 feat: partially hide waybar on toggle 2019-04-15 11:11:16 +02:00
Alex
316b948d86 Merge pull request #265 from Alexays/custom-multiple-classes
Custom: Ability to add multiple classes
2019-04-15 10:58:34 +02:00
Alex
5828d34fa0 Merge branch 'master' into custom-multiple-classes 2019-04-15 10:58:27 +02:00
Alex
bc9a49787a feat: enable pango markup on sway workspaces 2019-04-15 10:55:44 +02:00
Alex
6aee51479d feat: ability to add multiple classes 2019-04-15 10:18:27 +02:00
Alex
57c99dc526 refactor(Tray): also search in default theme 2019-04-11 15:28:38 +02:00
Alex
78067462be fix(Tray): icons update 2019-04-11 15:20:39 +02:00
Alex
5870421f84 refactor(temperature): check if file exist on init 2019-04-11 15:08:23 +02:00
Alex
24684ca71b chore: v0.5.1 2019-04-04 12:01:00 +02:00
Alex
8351dea292 refactor(network): process all messages 2019-04-04 11:58:27 +02:00
Alex
a68011bce6 style(workspaces): urgent style 2019-04-02 09:31:40 +02:00
Alex
1f6f443c48 Revert "refactor(network): fix skipped messages"
This reverts commit 1ccf372f8e.
2019-04-01 11:41:43 +02:00
Alex
7fac483530 fix: don't bind RTMGRP_IPV(4|6)_ROUTE 2019-03-31 16:33:01 +02:00
Alex
0ad9a03d3d Merge pull request #256 from SibrenVasse/spotify-json
Convert spotify module to generic json version
2019-03-31 16:30:16 +02:00
Sibren Vasse
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
Sibren Vasse
618fe80670 chore(custom): fix typo and quoting style 2019-03-31 16:10:42 +02:00
Alex
3a8cd91cc0 Revert "refactor: partial revert of 1ccf372f8e9d74cb18e92220c18a0729832fe69e"
This reverts commit 949a4ecf2e.
2019-03-30 09:20:28 +01:00
Alex
30725824d0 Merge pull request #251 from DanielVoogsgerd/feature-select-player
Feature select player
2019-03-30 09:19:24 +01:00
Alex
6522a7acb4 chore: let compile sway modules even if we can't find the binary 2019-03-30 09:15:51 +01:00
Alex
fd3e4ab2d4 Merge branch 'master' into feature-select-player 2019-03-30 09:07:31 +01:00
Alex
949a4ecf2e refactor: partial revert of 1ccf372f8e 2019-03-30 09:03:31 +01:00
Alex
860a6a1b81 Merge pull request #252 from alebastr/tray-items-refresh
Process tray icon updates
2019-03-30 08:40:55 +01:00
Aleksei Bavshin
5a2b5624dc feat(tray): process tray icon update signals 2019-03-29 18:40:28 -07:00
Aleksei Bavshin
82fcee33b3 refactor(tray): use Gio::DBus bindings in SNI Item class 2019-03-29 18:28:29 -07:00
Aleksei Bavshin
842e8501f9 fix(tray): free icon data on pixbuf update 2019-03-28 10:52:25 -07:00
Alex
7bd5454e43 Merge pull request #250 from DanielVoogsgerd/feature-arguments-logging
Feature: Command line arguments/Logging
2019-03-28 09:38:33 +01:00
Alex
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
Daniel Voogsgerd
095294389b feat(Spotify): Add option to select player 2019-03-27 22:27:23 +01:00
Daniel Voogsgerd
3bcf3befec chore(Spotify): Add logging for events 2019-03-27 17:28:02 +01:00
Daniel Voogsgerd
db69b449ba feat(Spotify): Add logger 2019-03-27 16:37:59 +01:00
Daniel Voogsgerd
ed358ef024 feat(Spotify): Add argument parser 2019-03-27 16:33:15 +01:00
Daniel Voogsgerd
faa79ea216 refactor: Add main method / Clean global scope 2019-03-27 16:10:36 +01:00
Alex
b17984f75e Merge pull request #248 from ndrewtl/patch-1
Note availability in Arch community repository
2019-03-27 08:15:44 +01:00
Andrew Lee
4521db66ce Note availability in Arch community repository 2019-03-26 18:20:09 -07:00
Alex
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
Sibren Vasse
55a6e4907b feat(custom): allow icon selection based on json alt attribute 2019-03-26 00:35:49 +01:00
Alex
dda0cc793e fix: check vertical after parsing multiple outputs 2019-03-25 21:02:00 +01:00
Alex
5144426f0e fix(workspace): scroll direction 2019-03-25 11:55:01 +01:00
Alex
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
José Ribeiro
3eb901f800 Escape ESSID text before interpreting as pango
Fixes #240.
2019-03-24 03:11:54 +00:00
Alex
47142a61ae feat: allow waybar to be positioned on left/right 2019-03-22 12:25:05 +01:00
Alex
f700319d7f chore: v0.5.0 2019-03-20 10:51:40 +01:00
Alex
418767c405 Merge pull request #228 from hoellen/feat-rtsignal
Add pkill signals to custom modules
2019-03-18 19:53:02 +01:00
hoellen
1f924c9c06 Merge branch 'master' into feat-rtsignal 2019-03-18 19:04:11 +01:00
hoellen
38fa7ceab1 add signalhandler for module update 2019-03-18 18:46:44 +01:00
Alex
22cddc5e26 refactor(workspaces): scroll output aware 2019-03-18 14:44:07 +01:00
Alex
1ccf372f8e refactor(network): fix skipped messages 2019-03-18 11:07:36 +01:00
Alex
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
Alex
6fc06fe9db Merge branch 'master' into pulseaudio-ci-icon-lookup 2019-03-15 09:41:40 +01:00
Alex
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
Aleksei Bavshin
9ad80849b1 fix(pulseaudio): Avoid allocation of string vector on every call of getPortIcon() 2019-03-14 18:35:16 -07:00
Aleksei Bavshin
492d151079 fix(pulseaudio) use case-insensitive comparison for icon lookup 2019-03-14 18:08:12 -07:00
Aleksei Bavshin
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
Alex
6d2dcd8ac7 fix(temperature): default thermal zone 2019-03-14 14:01:10 +01:00
Alex
7d6079bb06 style: update default temperature style/config 2019-03-13 22:39:39 +01:00
Alex
c820ed3495 Merge pull request #216 from Groggy/temperature
Add temperature module
2019-03-13 22:21:11 +01:00
Groggy
0314bf70b3 feat(temperature): update README 2019-03-13 16:56:13 +01:00
Groggy
7ae549dc9e Add temperature module 2019-03-13 13:35:43 +01:00
Alex
cd13180199 Merge pull request #213 from jubalh/rdmos
Add available on openSUSE
2019-03-12 11:41:05 +01:00
Michael Vetter
8d6d8e8210 Add available on openSUSE 2019-03-12 11:01:18 +01:00
Alex
2995da845d fix: config per output 2019-03-10 10:34:56 +01:00
Alex
973cec1713 feat(idle): add status class 2019-03-10 10:29:06 +01:00
Alex
94d7b083c5 fix(Pulseaudio): switch case 2019-03-08 15:30:41 +01:00
Alex
ef88f0a223 fix: clock rounding 2019-03-07 12:34:21 +01:00
Alex
ab0dcbfb2e Merge pull request #204 from Organic-Code/master
Improving mouse buttons support
2019-03-05 10:16:19 +01:00
Alex
1974766125 Merge branch 'master' into master 2019-03-05 10:15:05 +01:00
Jonas
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
Alex
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
Alex
67c756b28e Merge branch 'master' into idle_inhibitor_bugfix 2019-03-04 11:19:14 +01:00
Alex
584750d0fe Merge pull request #207 from jonfin/tooltip-network
Add custom tooltip format for network module
2019-03-04 11:17:52 +01:00
Jonas
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
Jonas
a9f680b06e Add custom tooltip format for network module 2019-03-03 21:35:35 +01:00
Alex
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
John Doe
737da3615f resolves #205 2019-03-03 21:35:32 +11:00
Lucas Lazare
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
Alex
f47492c371 chore: v0.4.0 2019-03-01 17:12:02 +01:00
Alex
79b0367e6c feat(custom): escape option 2019-03-01 17:03:01 +01:00
Alex
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
Ian Hattendorf
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
Ian Hattendorf
39de8e544c Disable backlight module if no backlight found 2019-02-28 19:50:57 -07:00
Alex
0271e9bc1a Revert "fix: Escape user controlled input"
This reverts commit 7247360e29.
2019-02-28 19:30:27 +01:00
Alex
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
Alex
6633e34bbd Merge branch 'master' into custom_alt 2019-02-26 12:18:34 +01:00
Alex
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
Ian Hattendorf
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
Robinhuett
39bf403505 feat(custom): Add field for additional data to json 2019-02-25 22:04:09 +01:00
Alex
4499a23e51 Merge pull request #192 from DutchMofo/clock-tooltip
Fix tooltip format
2019-02-25 20:39:31 +01:00
Piet van der Loo
821a009c32 Fix tooltip format 2019-02-25 19:25:19 +01:00
Alex
961a05bedf Merge pull request #191 from Robinhuett/escape_userinput
fix: Escape user controlled input
2019-02-25 18:46:17 +01:00
Robinhuett
7247360e29 fix: Escape user controlled input 2019-02-25 18:28:56 +01:00
Alex
abfa428dab Merge pull request #190 from Robinhuett/fix_backlight_fmtalt
fix(backlight) Allow format-alt
2019-02-25 10:52:42 +01:00
Robinhuett
63e97df9ff fix(backlight) Allow format-alt 2019-02-24 22:15:41 +01:00
Alex
5a7f801922 Merge pull request #189 from DutchMofo/clock-tooltip
Added clock tooltip with optional alternate format
2019-02-24 12:10:01 +01:00
Piet van der Loo
8fa30f8ce3 Forgot comma in json 2019-02-24 11:54:59 +01:00
Piet van der Loo
da4661f97c Removed clock tooltip from example config 2019-02-24 11:49:58 +01:00
Piet van der Loo
7245fb5d0a Added clock tooltip with optional alternate format 2019-02-24 09:25:34 +01:00
Alex
b4f36436c3 style: update idle inhibitor color 2019-02-22 17:34:13 +01:00
Alex
eb7e265eb1 Merge pull request #187 from jonfin/idle_inhibitor
Add module Idle inhibitor
2019-02-22 17:31:51 +01:00
Jonas
c2ed0cb832 Add the possibility to disable the tooltip 2019-02-22 16:58:36 +01:00
Jonas
d708ce2be9 Add idle inhibitor module 2019-02-22 16:55:46 +01:00
Alex
83a6475510 feat: can disable tooltip 2019-02-22 11:35:47 +01:00
Alex
331b28393a Merge pull request #181 from ianhattendorf/feature/module-brightness
Add backlight module
2019-02-21 16:04:32 +01:00
Alex
afbf2de1b9 Merge branch 'master' into feature/module-brightness 2019-02-21 15:29:17 +01:00
Ian Hattendorf
e67347f6ad Gate backlight module behind libudev availability 2019-02-18 21:11:18 -07:00
Alex
bef8520937 Remove opensuse ci for now 2019-02-18 12:33:40 +01:00
Alex
8bfcb106c0 style: comment about workspace hover effect 2019-02-18 12:30:41 +01:00
Ian Hattendorf
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
Alex
6bf4f65228 fix: two finger scroll 2019-02-16 09:56:53 +01:00
Alex
0c9edb0c4b fix(custom): check exist_status in endless script before clean the output 2019-02-16 09:48:27 +01:00
Alex
3e18673451 fix: remove bluetooth class on else 2019-02-12 17:55:50 +01:00
Alex
d0370acb21 refactor(network): better network disconnection 2019-02-11 19:06:39 +01:00
Alex
aeec80f375 fix(Tray): big icon 2019-02-07 13:34:30 +01:00
Alex
dc9fe04d11 refactor: add retry to get external interface 2019-02-06 10:33:12 +01:00
Alex
01cec9fcb7 fix(bar): multi screens 2019-02-04 22:09:01 +01:00
Alex
6fb25ade7e chore: update opensuse Dockerfile 2019-02-04 11:20:18 +01:00
Alex
e10e9554ab Update README.md 2019-02-04 10:32:16 +01:00
Alex
93173851d3 fix(Workspaces): index 2019-02-02 12:07:59 +01:00
Alex
add9e925f4 fix(Workspaces): button order 2019-02-02 00:36:52 +01:00
Alex
60af51fc17 chore: update opensuse dockerfile 2019-02-01 21:56:34 +01:00
Alex
3021ef2d51 Update README.md 2019-02-01 21:52:38 +01:00
Alex
0ddcf26a45 feat: output configuration 2019-02-01 21:45:59 +01:00
Alex
4d3c2191cb chore: v0.3.0 2019-01-28 19:38:58 +01:00
Alex
38fc62ea27 feat: bar id 2019-01-28 19:26:16 +01:00
Alex
2f7b2677d7 Merge pull request #155 from Robinhuett/window_ignore_self_fix
fix(window): Fix #154
2019-01-27 12:06:58 +01:00
Robinhuett
6d03d9f6a9 fix(window): Fix #154 2019-01-27 11:59:07 +01:00
Alex
6f2d784d4e Merge pull request #154 from Robinhuett/window_ignore_self
fix(window): Ignore waybar
2019-01-27 02:09:44 +01:00
Robinhuett
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
Alex
a0fd99b112 Merge pull request #152 from jubalh/fmt
Add fmt dependency to README.md
2019-01-25 16:42:41 +01:00
Michael Vetter
12a48b70ae Add fmt dependency to README.md 2019-01-25 16:38:38 +01:00
Alex
aedf133b16 Merge pull request #146 from Alexays/network_alt
fix(Network): format-alt
2019-01-14 09:05:52 +01:00
Alex
02aed73295 Merge branch 'master' into network_alt 2019-01-13 22:39:04 +01:00
Alex
9348e88592 Merge pull request #130 from Alexays/destructor
refactor: proper modules destruction
2019-01-13 22:37:19 +01:00
Alexis
eace8c549f fix(Network): format-alt 2019-01-13 22:36:37 +01:00
Alexis
f8116132a7 fix(Workspaces): check thread is running before parse response 2019-01-13 22:23:09 +01:00
Alexis
171e0e5ae3 revert: don't disable seq check 2019-01-13 22:22:22 +01:00
Alexis
30781757e3 revert: prefer nl_send_sync 2019-01-13 22:22:22 +01:00
Alexis
800d2f388e refactor(network): proper signal strength type 2019-01-13 22:22:22 +01:00
Alexis
1647e31b48 refactor: free netlink message 2019-01-13 22:22:22 +01:00
Alexis
399f61df98 refactor: proper modules destruction 2019-01-13 22:22:22 +01:00
Alex
8af813c2f1 Merge pull request #140 from Robinhuett/headers
refactor: replace all gtkmm.h includes
2019-01-09 01:00:00 +01:00
Robinhuett
29a2ee1744 refactor: Replace all occurencec of gtkmm.h and only use the necessary headers 2019-01-08 21:05:44 +01:00
Alex
afa1cc8287 Update README.md 2018-12-29 00:06:59 +01:00
Alex
ab78698ffd Merge pull request #139 from cjbassi/master
Remove ws index from sway ws names
2018-12-28 18:45:58 +01:00
Caleb Bassi
8ea0659ee2 Remove ws index from sway ws names 2018-12-28 09:36:02 -08:00
Alex
aa6da11ba4 Merge pull request #138 from cjbassi/master
Add install and build-debug commands to makefile
2018-12-28 15:58:51 +01:00
Caleb Bassi
6a5a4881ad Add install and build-debug commands to makefile 2018-12-28 06:37:07 -08:00
Alex
3a9bf932b4 Merge pull request #133 from cjbassi/feature/makefile
Add simple makefile
2018-12-28 01:23:23 +01:00
Alex
a078be991a Merge branch 'master' into feature/makefile 2018-12-28 01:16:56 +01:00
Alex
83ae5ec5c9 Merge pull request #134 from cjbassi/fix/typo
Fix typo
2018-12-28 01:16:31 +01:00
Caleb Bassi
b4d38294a7 Fix typo 2018-12-27 16:03:29 -08:00
Caleb Bassi
7804514c5c Add simple makefile 2018-12-27 15:56:47 -08:00
Alex
513278597a Merge pull request #129 from Robinhuett/custom-module-icon
Add format-icons to custom module
2018-12-26 11:42:25 +01:00
Robinhuett
4698c9d2cf chore(custom): Change int conversion 2018-12-26 11:35:58 +01:00
Robinhuett
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
Alex
76bbdd0425 Merge pull request #128 from Robinhuett/wifiicon
Use SignalStrength for format icons
2018-12-25 21:07:25 +01:00
Robinhuett
34df2b0695 fix(ALabel): Better fix for getIcon 2018-12-25 21:03:13 +01:00
Robinhuett
ad638357b5 feat(network): Use Signal Strength for format-icons 2018-12-25 14:17:34 +01:00
Robinhuett
7404f80122 fix(ALabel): getIcon 2018-12-25 14:15:59 +01:00
Alexis
de0ee9fcb2 fix(battery): adapter status 2018-12-24 12:17:07 +01:00
Alexis
755fad6bc3 fix(battery): typo 2018-12-24 08:50:58 +01:00
Alexis
e3c0624c48 fix(battery): typo 2018-12-24 08:38:37 +01:00
Alexis
87e55ea993 feat(battery): check adapter online as fallback when battery status report unknown 2018-12-24 08:37:10 +01:00
Alexis
3b8bfb08a4 fix(network): typo 2018-12-22 14:00:56 +01:00
Alex
00728fe877 Merge pull request #125 from chep/master
fix(pulseaudio): return puleseaudio port string if unknown
2018-12-20 10:24:55 +01:00
Cédric Chépied
a4062455cd fix(pulseaudio): return puleseaudio port string if unknown 2018-12-20 09:54:10 +01:00
Alexis
3691d84543 chore: v0.2.3 2018-12-18 17:42:35 +01:00
Alexis
b554094c7e feat: args && class id 2018-12-18 17:30:54 +01:00
Alexis
66ad2864c2 fix(Pulseaudio): use markup 2018-12-17 09:00:40 +01:00
Alexis
c7b0639f32 fix(workspaces): check thread is running 2018-12-09 10:49:28 +01:00
Alexis
0acc50264e fix(custom): failed getline after closed pipe 2018-12-08 13:57:56 +01:00
Alexis
1b13f9e38c fix(custom): close endless scripts 2018-12-08 12:58:47 +01:00
Alexis
7befd27059 fix: clock interval 2018-12-04 09:38:08 +01:00
Alexis
2ec34e4adb feat: add $HOME to valid path 2018-12-03 20:12:34 +01:00
Alexis
812a7a9861 fix: typo 2018-12-03 11:13:56 +01:00
Alexis
1d96d57b75 feat: warn user about stopped endless custom module 2018-12-03 09:35:10 +01:00
Alexis
691fb88057 refactor(modules): const bar 2018-12-01 00:10:41 +01:00
Alexis
281bab4943 feat(ci): opensuse 2018-11-30 21:48:42 +01:00
Alexis
8e93fd3ae0 revert: Update to new Sway IPC protocol 2018-11-29 09:50:00 +01:00
Alex
37c7e586d6 Merge pull request #110 from tokyovigilante/ipc-fix
Update to new Sway IPC protocol
2018-11-29 00:40:57 +01:00
Ryan Walklin
3a5a470d90 Update to new Sway IPC protocol 2018-11-28 22:27:06 +00:00
Alex
c051b517ca Merge pull request #108 from SibrenVasse/spotify
fix(custom): fix conditional statement
2018-11-26 20:09:21 +01:00
Sibren Vasse
a0cdef569b fix(custom): fix conditional statement 2018-11-26 19:35:03 +01:00
Alexis
a123711a8d fix(network): disconnected forced interface 2018-11-25 21:32:50 +01:00
Alexis
391a7de94a fix(window): escape window name 2018-11-25 15:21:00 +01:00
Alex
952d877652 Merge pull request #103 from SibrenVasse/spotify
Event based mediaplayer script
2018-11-24 18:13:08 +01:00
Alex
5356d25a9b Merge branch 'master' into spotify 2018-11-24 18:11:48 +01:00
Alex
65b9911df4 Merge pull request #105 from David96/master
Allow custom plugins that don't execute anything
2018-11-24 18:01:31 +01:00
David96
2aed121903 Allow custom plugins that don't execute anything 2018-11-24 17:24:02 +01:00
Alexis
08bfdda4cb revert(network): wait interface 2018-11-24 16:01:22 +01:00
Alexis
8f8ec3b999 feat(Label): on-click-right 2018-11-24 15:56:16 +01:00
Alexis
a17220054d feat(client): pefix module name on update error 2018-11-24 11:20:03 +01:00
Alexis
4cc48b3cfd fix(client): catch error on update 2018-11-24 11:13:52 +01:00
Alexis
087de4e956 refactor(client): lambda to method 2018-11-24 11:04:56 +01:00
Alexis
2c2a0473f4 feat(client): throw when we don't have required resources files 2018-11-23 19:31:40 +01:00
Alexis
2e1f8b2fc5 fix(network): check len of netlinkResponse 2018-11-23 17:52:10 +01:00
Alexis
686bc4828e refactor(network): only get info when there is an interface 2018-11-23 17:46:14 +01:00
Alexis
f6c2a8d9b7 fix(network): free the message instead of the socket 2018-11-23 17:42:26 +01:00
Alexis
baa7f52e21 refactor(network): wait for new address 2018-11-23 16:04:29 +01:00
Sibren Vasse
c4c0c01a2f feat(custom): event based mediaplayer script 2018-11-23 12:08:15 +01:00
Alexis
2b05b8e69a chore: v0.2.2 2018-11-23 12:03:23 +01:00
Alexis
ad7400d5ce refactor(ALabel): add interval 2018-11-23 11:57:37 +01:00
Alexis
36652158ad refactor(tray): more cpp binding 2018-11-23 10:46:58 +01:00
Alexis
8db94f7efd revert(ALabel): ref on format 2018-11-22 16:50:42 +01:00
Alexis
aaaa17d94f fix(config): remove duplicate tray 2018-11-22 16:21:46 +01:00
Alexis
0b1b0eb1a7 feat(tray): multiple hosts 2018-11-22 16:20:49 +01:00
Alexis
f00be0b552 refactor(tray): remove useless variable 2018-11-22 15:51:55 +01:00
Alexis
ba79b4d397 refactor(tray): cleanup and fixes 2018-11-22 15:47:23 +01:00
Alexis
bd62c4cbc5 feat(Label): use set_markup 2018-11-21 20:49:09 +01:00
Alexis
b3559328f1 fix(window): ellipsize 2018-11-20 23:24:33 +01:00
Alexis
69fceb1c92 fix(tray): sigsev on click when multiple icons is available 2018-11-16 12:01:03 +01:00
Alexis
33f138c16e chore: v0.2.1 2018-11-16 10:15:27 +01:00
Alexis
01692b719a chore: update README 2018-11-16 10:07:20 +01:00
Alexis
8c26a6aab7 chore: update README 2018-11-16 10:06:24 +01:00
Alexis
e42fae32ab feat(network): network info interval 2018-11-16 10:02:12 +01:00
Alexis
c910767378 refactor: remove usless using 2018-11-15 14:48:49 +01:00
Alexis
94b9f0a399 feat(cpu): add both usage and load 2018-11-15 14:44:43 +01:00
Alexis
1665003d23 fix: sigsev 2018-11-14 19:14:51 +01:00
Alex
e5573c20e6 Merge pull request #96 from Robinhuett/module_network_ipaddr
Module network ipaddr
2018-11-14 10:44:27 +01:00
Robinhuett
75cc1bc318 refactor(network): codestyle and error handling 2018-11-14 10:31:17 +01:00
Robinhuett
50e782e028 chore: update example config 2018-11-13 21:40:47 +01:00
Robinhuett
5c66b1a770 feat(network): display ip address and subnetmask 2018-11-13 21:31:26 +01:00
Alex
3dc0f7ccf9 Merge pull request #94 from Robinhuett/cpu_fix
Use /proc/stat for cpu load
2018-11-11 13:41:11 +01:00
Robinhuett
e1d98f0ad9 fix(cpu): show correct load
feat(cpu): show cores in tooltip
2018-11-11 03:11:32 +01:00
Alexis
7222668326 refactor: disable battery timer stop on inotify event for now 2018-11-09 23:02:46 +01:00
Alex
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
Alexis
45bb8b1a1f refactor: simpler memory code 2018-11-09 22:55:25 +01:00
Robinhuett
e21df5ae36 Add config option for volume change scroll step size 2018-11-09 22:48:27 +01:00
Alex
a5bca24f9b Merge pull request #91 from Robinhuett/ram_free_used
Use /proc/meminfo for Memory module
2018-11-09 22:34:11 +01:00
Alex
c07037d6b8 Update README.md 2018-11-09 17:27:35 +01:00
Alexis
a9751545fa fix: update travis 2018-11-09 17:15:19 +01:00
Robinhuett
9ea0815dea Use ifstream to reaad /proc/meminfo 2018-11-09 16:24:13 +01:00
Alexis
b8b799a187 [ci skip] remove .SRCINFO at root 2018-11-09 15:36:21 +01:00
Alexis
39dfa66261 feat(ci): archlinux 2018-11-09 15:19:17 +01:00
Alex
13702012a4 CI (#90) 2018-11-09 12:07:16 +01:00
Robinhuett
6b62079d8a rewind stream instead of opening a new one 2018-11-09 00:17:30 +01:00
Robinhuett
ac0963c608 Use /proc/meminfo for Memory module 2018-11-08 21:09:56 +01:00
Alexis
2d2fb88040 fix: fmt 2018-11-08 09:57:24 +01:00
Alex
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
David96
adcd956c24 Fix workspaces not being removed from bar when moved to another output 2018-11-05 20:59:28 +01:00
Alex
6c9e37699b Merge pull request #86 from David96/master
Fix clicking and scrolling through workspaces
2018-11-05 20:35:48 +01:00
David96
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
Alexis
d6af63d84a chore: add travis 2018-11-05 11:59:05 +01:00
Alex
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
Alexis
43cd80fb31 chore: 0.2.0 2018-11-03 13:20:05 +01:00
Victor Berger
2f6abfda59 Don't call layer_surface.set_size on configure 2018-11-03 13:16:13 +01:00
Alex
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
Lucas L. Treffenstädt
0637888460 even simpler check 2018-11-03 12:44:15 +01:00
Lucas L. Treffenstädt
ebbdaa168c automatically detect where filesystem lives 2018-11-02 23:15:42 +01:00
Alexis
6ab01b1ad4 fix(style): not charging 2018-11-02 23:00:38 +01:00
Alex
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
Lucas L. Treffenstädt
25f31b19f6 formatting is hard. 2018-11-02 22:50:01 +01:00
Lucas L. Treffenstädt
d8b6201632 ...and fix the function signature in the header 2018-11-02 22:15:54 +01:00
Lucas L. Treffenstädt
123ce083b4 fix typo and initialize old_status_ 2018-11-02 22:08:55 +01:00
Lucas L. Treffenstädt
0522577fe5 make status and state fully configurable formats 2018-11-02 22:04:43 +01:00
Alex
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
Lucas L. Treffenstädt
b6cad05489 fix formatting 2018-11-02 21:13:57 +01:00
Lucas L. Treffenstädt
236be90c2f add option for when filesystem still lives in the experimental namespace 2018-11-02 20:59:41 +01:00
Lucas L. Treffenstädt
f137090d55 fix compilation on systems with old gdbus-codegen 2018-11-02 20:13:09 +01:00
Lucas L. Treffenstädt
9c57df505c Add class for full battery and give option to interpret unknown as full 2018-11-02 19:41:00 +01:00
Alexis
00e7e87f55 fix: style 2018-11-02 17:39:00 +01:00
Alexis
836c543c62 fix: style 2018-11-02 17:07:51 +01:00
Alexis
7bca5fd6bd feat(Bar): add a warning about minimum height 2018-11-02 12:35:26 +01:00
Alex
61e9f0803d Merge pull request #75 from ForTheReallys/proper_height
Fix #54
2018-11-02 12:26:14 +01:00
Alexis
9b201c77d7 feat: battery states && format-full/charging 2018-11-02 11:23:29 +01:00
Alex Maese
4b68840212 Fix #54 2018-11-01 16:00:38 -05:00
Alexis
9d4048983d refactor: remove useless tmp variable 2018-11-01 09:27:00 +01:00
Alex
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
Robinhuett
e23fbd0add Added return-type json to custom module 2018-11-01 00:40:44 +01:00
Robinhuett
341d3300fa Custom modules can control tooltip and CSS class 2018-10-30 21:28:31 +01:00
Alex
c3e185546d Merge pull request #68 from harishkrupo/master
Add configuration options for widgets on mouse events
2018-10-30 16:32:37 +01:00
Alex
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
Harish Krupo
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
Robinhuett
4c8621c7a5 Added second warning stage to battery module
Also naming is a bit more consistent
2018-10-30 16:23:36 +01:00
Harish Krupo
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
Alex
e93c5e7957 Merge pull request #70 from Robinhuett/mode_module
Add module to show sway binding mode
2018-10-30 14:26:43 +01:00
Robinhuett
668b7b736c Added default config for sway binding mode 2018-10-30 13:44:44 +01:00
Robinhuett
a042eea384 Add module to show sway binding mode 2018-10-30 13:39:30 +01:00
Alexis
c9a8a07976 fix(window): title on new workspace 2018-10-29 21:52:53 +01:00
Alexis
4307e4fd8e chore: upgrade fmt to 5.2.0 2018-10-28 14:40:25 +01:00
Alexis
daf613f8ca feat: add debug about tray beta 2018-10-28 08:43:48 +01:00
Alexis
3f2eb0b492 chore: 0.1.3 2018-10-28 08:39:33 +01:00
Alex
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
maxice8
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
Alexis
ed3e4b1395 fix(pulseaudio): check active_port is set 2018-10-27 11:23:43 +02:00
Alex
16b01e1059 Merge pull request #62 from colemickens/giounix20
meson: fix 'gio-unix-2.0' dependency
2018-10-27 09:35:47 +02:00
Alex
1ae490c8f7 Merge pull request #61 from colemickens/outdir
meson: make extra output directory configurable
2018-10-27 09:16:11 +02:00
Cole Mickens
0d0a3be483 meson: fix 'gio-unix-2.0' dependency 2018-10-26 23:21:03 -07:00
Cole Mickens
a1c4b9bb0c meson: make extra output directory configurable 2018-10-26 23:20:38 -07:00
Alexis
a55a1ae866 fix(tray): icons size 2018-10-26 14:53:39 +02:00
Alexis
07d8dfb3d6 feat(tray): spacing config 2018-10-26 12:08:50 +02:00
Alexis
5010227e6b fix(tray): icons 2018-10-26 11:59:03 +02:00
Alexis
e8f3c1c6b3 chore: v0.1.2 2018-10-26 11:21:04 +02:00
Alexis
7e6c701659 chore: update README 2018-10-26 11:16:17 +02:00
Alexis
adc38c3dfe feat(sni): set protocol version 2018-10-26 10:56:45 +02:00
Alexis
b10907ee44 refactor: remove useless code 2018-10-26 10:39:25 +02:00
Alexis
0c9699b076 fix: check before set is host registered 2018-10-26 10:27:15 +02:00
Alexis
63e86fbe9e fix: check type 2018-10-26 10:12:34 +02:00
Alexis
f20441fa92 refactor: simpler sni naming 2018-10-26 10:05:54 +02:00
Alexis
3f269ff463 fix: check json::value type 2018-10-26 09:27:16 +02:00
Alexis
fd76e98552 fix: ifdef include 2018-10-25 19:12:28 +02:00
Alexis
9fae5efc06 feat: use interval thread until got inotify event 2018-10-25 17:39:15 +02:00
Alexis
7f1f217d84 feat: multiple config per modules 2018-10-25 17:30:26 +02:00
Alexis
1ea0c1f9dd chore: find gdbus-codegen once 2018-10-25 16:42:01 +02:00
Alexis
4626cbef63 fix(pulseaudio): round volume 2018-10-25 13:57:35 +02:00
Alexis
85f845ca43 refactor: remove debug 2018-10-25 13:49:30 +02:00
Alexis
68d9d2c347 fix: ifdef 2018-10-25 12:24:39 +02:00
Alexis
f3fe57dd24 chore: v0.1.0 2018-10-25 12:15:52 +02:00
Alexis
d4b97d5d09 feat: optional tray 2018-10-25 11:47:03 +02:00
Alexis
f3975e6634 feat: gdbus dbus-menu 2018-10-25 11:44:04 +02:00
Alex
0dedcc0126 Merge pull request #39 from Alexays/tray-gdbus
Tray beta
2018-10-25 11:40:33 +02:00
Alex
0e6147b644 Merge branch 'master' into tray-gdbus 2018-10-25 11:36:35 +02:00
Alexis
45847847b9 style: disallow resize 2018-10-25 10:43:37 +02:00
Alexis
c912d8c86a fix(workspaces): index 2018-10-25 10:22:11 +02:00
Alex
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
dangerousdan
137c5fb712 fix custom intervals causing cpu to go mental 2018-10-23 00:06:46 +01:00
Alexis
5a6e05dcde feat: workspaces index 2018-10-22 10:41:52 +02:00
Alex
290f3a79e9 Merge pull request #46 from DanySpin97/build-options
Add options for pulseaudio and libnl
2018-10-21 12:31:14 +02:00
Danilo Spinella
73553802f9 Add options for pulseaudio and libnl 2018-10-21 09:58:35 +00:00
Tobias Pisani
a13ce6e227 Merge branch 'tray-gdbus' into tray-gdbus 2018-10-05 21:20:36 +02:00
Alexis
091b460d03 feat(Tray): handle click 2018-10-04 18:53:50 +02:00
Alexis
56e55fa4aa fix: remove TODO 2018-10-04 18:53:50 +02:00
Alexis
dc799adf45 feat(Tray): icon pixmap 2018-10-04 18:53:50 +02:00
Alexis
75c9477aa8 feat(Tray): handle item unregister 2018-10-04 18:53:50 +02:00
Alexis
75cf1d70fd feat(WIP): tray
feat(wip): tray

feat(wip): tray

feat(WIP): gdbus

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

feat(wip): tray

feat(WIP): gdbus

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

feat(wip): tray

feat(WIP): gdbus

feat(WIP): tray
2018-09-02 17:29:16 +02:00
Alexis
28c65c64e6 chore: add default build type 2018-08-30 11:30:20 +02:00
Alexis
4f75d5e33b fix: config 2018-08-30 00:04:43 +02:00
Alexis
aa05304139 feat(Pulseadio): config icons 2018-08-29 23:54:23 +02:00
Alexis
6dd9b5ccc4 feat(Pulseadio): port icons 2018-08-29 23:50:41 +02:00
Alexis
d0933ab50f fix(thread): check before detach 2018-08-29 21:07:58 +02:00
Alexis
9a1b8bb831 fix(Custom): only set id when getting an output 2018-08-28 11:10:36 +02:00
Alexis
53956d9d18 feat(ALabel): Toggleable labels 2018-08-27 01:36:25 +02:00
Alexis
e9478f548e chore: add mediaplayer script 2018-08-26 21:47:35 +02:00
Alexis
c8ca8b3725 fix(Custom): hide label when exec-if failed 2018-08-26 21:41:34 +02:00
Alexis
0ad2bc7516 refactor(Network): clean nl socket 2018-08-24 15:32:06 +02:00
Alexis
0dba3abc1d fix(custom): do not take the custom module ref 2018-08-21 10:50:09 +02:00
Alexis
8be67d5008 chore: optional deps 2018-08-20 17:20:02 +02:00
Alex
49232eed8d Clean (#31) 2018-08-20 14:50:45 +02:00
Alexis
b7e3d10fb7 revert(workspaces): ipc command out of update func 2018-08-20 00:19:27 +02:00
Alexis
8ce33e0c64 fix(window): pick only con title 2018-08-19 20:37:33 +02:00
Alexis
969c1ceedd chore: v0.0.5 2018-08-19 13:43:41 +02:00
Alexis
52a4e761a8 fix(workspaces): avoid useless mutex lock 2018-08-19 13:43:00 +02:00
Alexis
16b856c8bc fix: remove debug flag 2018-08-19 13:41:22 +02:00
Alex
6705134034 Handle screens disconnection (#29) 2018-08-19 13:39:57 +02:00
Alexis
ce50a627be refactor: move command execution into their own file 2018-08-18 17:54:20 +02:00
Alexis
b794ca63d1 feat(custom): exec-if 2018-08-18 17:27:40 +02:00
Alexis
38ede5b3d5 refactor(ipc): clean 2018-08-18 16:01:56 +02:00
Alexis
27dfffa4e3 refactor: style issue 2018-08-18 15:05:18 +02:00
Alexis
b1fd4d7b82 feat(modules): generic label module to allow max-length on all labels 2018-08-18 11:43:48 +02:00
Alexis
c128562284 feat(bar): clean exit 2018-08-17 20:28:26 +02:00
Alex
d280f5e8bd Network detect (#26) 2018-08-17 14:24:00 +02:00
Alexis
0603b99714 fix(bar): proper center modules 2018-08-16 18:11:16 +02:00
Alexis
0371271465 fix(custom): hide first 2018-08-16 17:59:45 +02:00
Alexis
93f87f322f chore: v0.0.4 2018-08-16 17:19:02 +02:00
Alexis
8768183f3d fea(workspaces): add disable-scroll config 2018-08-16 17:12:45 +02:00
Alexis
e4f35d7ca0 fea(custom): add max-length config 2018-08-16 17:09:51 +02:00
Alexis
57f3a01a5b refactor: remove assert 2018-08-16 15:41:09 +02:00
Alex
6635548d3e Style code (#25) 2018-08-16 14:29:41 +02:00
Alexis
3fdc50163d feat(window): update when window title change 2018-08-16 00:02:57 +02:00
Alexis
a9246a09eb feat(workspaces): add a option to show all workspaces from all outputs 2018-08-15 22:19:17 +02:00
Alexis
3ed3416d75 fix(config): update sway workspaces key 2018-08-15 21:03:49 +02:00
Alexis
008856cbb8 feat(clock): allow choose interval 2018-08-15 21:00:04 +02:00
Alexis
608b791ac1 refactor(clock): use fmt::localtime 2018-08-15 20:53:27 +02:00
Alexis
d427512d7d chore: update README 2018-08-15 20:18:00 +02:00
Alexis
f94598c138 feat(sway): add focused window name 2018-08-15 20:17:17 +02:00
Alexis
9b75302d22 refactor(client): cleanup 2018-08-15 17:31:45 +02:00
Alexis
be66cc2dd1 feat(workspaces): add urgent, visible class 2018-08-15 15:03:51 +02:00
Alexis
52e7b6148b feat(workspaces): add class to button when label is a icon 2018-08-15 14:58:55 +02:00
81 changed files with 7030 additions and 1404 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,20 +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 (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).
@@ -22,12 +27,34 @@
```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
```
Contributions welcome! - have fun :)
**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>
The style guidelines is [Google's](https://google.github.io/styleguide/cppguide.html)
## License

38
include/ALabel.hpp Normal file
View File

@@ -0,0 +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, 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,12 +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() {}
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,50 +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"
namespace waybar {
struct Client;
struct Bar {
Bar(Client& client, std::unique_ptr<struct wl_output *>&& output);
Bar(const Bar&) = delete;
Client& client;
Gtk::Window window;
struct wl_surface *surface;
struct zwlr_layer_surface_v1 *layerSurface;
std::unique_ptr<struct wl_output *> output;
bool visible = true;
std::string outputName;
auto setWidth(uint32_t) -> void;
auto toggle() -> void;
private:
static void _handleLogicalPosition(void *data,
struct zxdg_output_v1 *zxdg_output_v1, int32_t x, int32_t y);
static void _handleLogicalSize(void *data,
struct zxdg_output_v1 *zxdg_output_v1, int32_t width, int32_t height);
static void _handleDone(void *data, struct zxdg_output_v1 *zxdg_output_v1);
static void _handleName(void *data, struct zxdg_output_v1 *xdg_output,
const char *name);
static void _handleDescription(void *data,
struct zxdg_output_v1 *zxdg_output_v1, const char *description);
static void _layerSurfaceHandleConfigure(void *data,
struct zwlr_layer_surface_v1 *surface, uint32_t serial, uint32_t width,
uint32_t height);
static void _layerSurfaceHandleClosed(void *data,
struct zwlr_layer_surface_v1 *surface);
auto _setupConfig() -> void;
auto _setupWidgets() -> void;
auto _setupCss() -> void;
uint32_t _width = 0;
uint32_t _height = 30;
Json::Value _config;
Glib::RefPtr<Gtk::StyleContext> _styleContext;
Glib::RefPtr<Gtk::CssProvider> _cssProvider;
struct zxdg_output_v1 *_xdgOutput;
class Factory;
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,42 +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 {
struct Client {
std::string cssFile;
std::string configFile;
Gtk::Main gtk_main;
class Client {
public:
static Client *inst();
int main(int argc, char *argv[]);
Glib::RefPtr<Gtk::Application> gtk_app;
Glib::RefPtr<Gdk::Display> gdk_display;
struct wl_display *wlDisplay = nullptr;
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;
struct wl_output *wlOutput = nullptr;
struct zwp_idle_inhibit_manager_v1 *idle_inhibit_manager = nullptr;
std::vector<std::unique_ptr<Bar>> bars;
Client(int argc, char* argv[]);
void bind_interfaces();
auto setup_css();
int main(int argc, char* argv[]);
private:
static void _handle_global(void *data, struct wl_registry *registry,
uint32_t name, const char *interface, uint32_t version);
static void _handle_global_remove(void *data,
struct wl_registry *registry, uint32_t name);
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 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

@@ -2,23 +2,44 @@
#include <json/json.h>
#include "modules/clock.hpp"
#include "modules/workspaces.hpp"
#ifdef HAVE_SWAY
#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, Json::Value config);
IModule *makeModule(std::string name);
Factory(const Bar& bar, const Json::Value& config);
IModule* makeModule(const std::string& name) const;
private:
Bar &_bar;
Json::Value _config;
const Bar& bar_;
const Json::Value& config_;
};
}
} // namespace waybar

View File

@@ -1,32 +0,0 @@
#pragma once
#include <iostream>
#include "ipc.hpp"
/**
* IPC response including type of IPC response, size of payload and the json
* encoded payload string.
*/
struct ipc_response {
uint32_t size;
uint32_t type;
std::string payload;
};
/**
* Gets the path to the IPC socket from sway.
*/
std::string get_socketpath(void);
/**
* Opens the sway socket.
*/
int ipc_open_socket(std::string socket_path);
/**
* Issues a single IPC command and returns the buffer. len will be updated with
* the length of the buffer returned from sway.
*/
std::string ipc_single_command(int socketfd, uint32_t type, const char *payload, uint32_t *len);
/**
* Receives a single IPC response and returns an ipc_response.
*/
struct ipc_response ipc_recv_response(int socketfd);

View File

@@ -1,32 +0,0 @@
#pragma once
#define event_mask(ev) (1 << (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,
// 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),
};

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
#include <json/json.h>
#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 "IModule.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 IModule {
class Battery : public ALabel {
public:
Battery(Json::Value config);
Battery(const std::string&, const Json::Value&);
~Battery();
auto update() -> void;
operator Gtk::Widget&();
private:
std::string _getIcon(uint16_t percentage);
static inline const fs::path _data_dir = "/sys/class/power_supply/";
std::vector<fs::path> _batteries;
util::SleeperThread _thread;
Gtk::Label _label;
Json::Value _config;
static inline const fs::path data_dir_ = "/sys/class/power_supply/";
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,21 +1,20 @@
#pragma once
#include <json/json.h>
#include <fmt/format.h>
#include "util/chrono.hpp"
#include "IModule.hpp"
#include "ALabel.hpp"
#include "fmt/time.h"
#include "util/sleeper_thread.hpp"
namespace waybar::modules {
class Clock : public IModule {
class Clock : public ALabel {
public:
Clock(Json::Value config);
Clock(const std::string&, const Json::Value&);
~Clock() = default;
auto update() -> void;
operator Gtk::Widget &();
private:
Gtk::Label _label;
waybar::util::SleeperThread _thread;
Json::Value _config;
waybar::util::SleeperThread thread_;
};
}
} // namespace waybar::modules

View File

@@ -1,22 +1,30 @@
#pragma once
#include <json/json.h>
#include <fmt/format.h>
#include <sys/sysinfo.h>
#include "util/chrono.hpp"
#include "IModule.hpp"
#include <fstream>
#include <iostream>
#include <numeric>
#include <vector>
#include "ALabel.hpp"
#include "util/sleeper_thread.hpp"
namespace waybar::modules {
class Cpu : public IModule {
class Cpu : public ALabel {
public:
Cpu(Json::Value config);
Cpu(const std::string&, const Json::Value&);
~Cpu() = default;
auto update() -> void;
operator Gtk::Widget &();
private:
Gtk::Label _label;
waybar::util::SleeperThread _thread;
Json::Value _config;
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 <json/json.h>
#include <fmt/format.h>
#include "util/chrono.hpp"
#include "IModule.hpp"
#include <csignal>
#include <iostream>
#include "ALabel.hpp"
#include "util/command.hpp"
#include "util/json.hpp"
#include "util/sleeper_thread.hpp"
namespace waybar::modules {
class Custom : public IModule {
class Custom : public ALabel {
public:
Custom(std::string name, Json::Value config);
Custom(const std::string&, const Json::Value&);
~Custom();
auto update() -> void;
operator Gtk::Widget &();
void refresh(int /*signal*/);
private:
Gtk::Label _label;
waybar::util::SleeperThread _thread;
const std::string _name;
Json::Value _config;
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,22 +1,24 @@
#pragma once
#include <json/json.h>
#include <fmt/format.h>
#include <sys/sysinfo.h>
#include "util/chrono.hpp"
#include "IModule.hpp"
#include <fstream>
#include "ALabel.hpp"
#include "util/sleeper_thread.hpp"
namespace waybar::modules {
class Memory : public IModule {
class Memory : public ALabel {
public:
Memory(Json::Value config);
Memory(const std::string&, const Json::Value&);
~Memory() = default;
auto update() -> void;
operator Gtk::Widget &();
private:
Gtk::Label _label;
waybar::util::SleeperThread _thread;
Json::Value _config;
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,35 +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 <json/json.h>
#include <arpa/inet.h>
#include <fmt/format.h>
#include "util/chrono.hpp"
#include "IModule.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 IModule {
class Network : public ALabel {
public:
Network(Json::Value config);
Network(const std::string&, const Json::Value&);
~Network();
auto update() -> void;
operator Gtk::Widget &();
private:
void _parseEssid(struct nlattr **bss);
void _parseSignal(struct nlattr **bss);
bool _associatedOrJoined(struct nlattr **bss);
static int _scanCb(struct nl_msg *msg, void *data);
auto _getInfo() -> void;
Gtk::Label _label;
waybar::util::SleeperThread _thread;
Json::Value _config;
std::size_t _ifid;
std::string _essid;
int _signalStrengthdBm;
int _signalStrength;
static const uint8_t MAX_RETRY = 5;
static const uint8_t EPOLL_MAX = 200;
static int handleEvents(struct nl_msg*, void*);
static int handleScan(struct nl_msg*, void*);
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,36 +1,39 @@
#pragma once
#include <pulse/pulseaudio.h>
#include <json/json.h>
#include <fmt/format.h>
#include <pulse/pulseaudio.h>
#include <pulse/volume.h>
#include <algorithm>
#include "IModule.hpp"
#include "ALabel.hpp"
namespace waybar::modules {
class Pulseaudio : public IModule {
class Pulseaudio : public ALabel {
public:
Pulseaudio(Json::Value config);
Pulseaudio(const std::string&, const Json::Value&);
~Pulseaudio();
auto update() -> void;
operator Gtk::Widget &();
private:
std::string _getIcon(uint16_t percentage);
static void _subscribeCb(pa_context *context,
pa_subscription_event_type_t type, uint32_t idx, void *data);
static void _contextStateCb(pa_context *c, void *data);
static void _sinkInfoCb(pa_context *context, const pa_sink_info *i,
int eol, void *data);
static void _serverInfoCb(pa_context *context, const pa_server_info *i,
void *data);
Gtk::Label _label;
Json::Value _config;
pa_threaded_mainloop *_mainloop;
pa_mainloop_api *_mainloop_api;
pa_context *_context;
uint32_t _sinkIdx{0};
int _volume;
bool _muted;
std::string _desc;
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

@@ -0,0 +1,51 @@
#pragma once
#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();
struct ipc_response {
uint32_t size;
uint32_t type;
Json::Value payload;
};
sigc::signal<void, const struct ipc_response&> signal_event;
sigc::signal<void, const struct ipc_response&> signal_cmd;
void sendCmd(uint32_t type, const std::string &payload = "");
void subscribe(const std::string &payload);
void handleEvent();
protected:
static inline const std::string ipc_magic_ = "i3-ipc";
static inline const 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_;
std::mutex mutex_parser_;
util::JsonParser parser_;
};
} // namespace waybar::modules::sway

View File

@@ -0,0 +1,32 @@
#pragma once
#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,
// 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),
};

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

@@ -0,0 +1,34 @@
#pragma once
#include <fmt/format.h>
#include <tuple>
#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 Window : public ALabel {
public:
Window(const std::string&, const waybar::Bar&, const Json::Value&);
~Window() = default;
auto update() -> void;
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

@@ -0,0 +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 "modules/sway/ipc/client.hpp"
#include "util/sleeper_thread.hpp"
namespace waybar::modules::sway {
class Workspaces : public IModule {
public:
Workspaces(const std::string&, const waybar::Bar&, const Json::Value&);
~Workspaces() = default;
auto update() -> void;
operator Gtk::Widget&();
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,37 +0,0 @@
#pragma once
#include <fmt/format.h>
#include "bar.hpp"
#include "client.hpp"
#include "util/chrono.hpp"
#include "util/json.hpp"
#include "IModule.hpp"
namespace waybar::modules {
class Workspaces : public IModule {
public:
Workspaces(waybar::Bar &bar, Json::Value config);
auto update() -> void;
operator Gtk::Widget &();
private:
void _addWorkspace(Json::Value node);
std::string _getIcon(std::string name);
Json::Value _getWorkspaces(const std::string data);
bool _handleScroll(GdkEventScroll *e);
int _getPrevWorkspace();
int _getNextWorkspace();
Bar &_bar;
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;
int _ipcfd;
int _ipcEventfd;
};
}

View File

@@ -1,92 +0,0 @@
#pragma once
#include <chrono>
#include <ctime>
#include <functional>
#include <condition_variable>
#include <thread>
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>;
inline struct timespec to_timespec(time_point t) noexcept
{
long secs = duration_cast<seconds>(t.time_since_epoch()).count();
long nsc = duration_cast<nanoseconds>(t.time_since_epoch() % seconds(1)).count();
return {secs, nsc};
}
inline time_point to_time_point(struct timespec t) noexcept
{
return time_point(duration_cast<duration>(seconds(t.tv_sec) + nanoseconds(t.tv_nsec)));
}
}
namespace waybar::util {
struct SleeperThread {
SleeperThread() = default;
SleeperThread(std::function<void()> func)
: thread{[this, func] {
do {
func();
} while (do_run);
}}
{
defined = true;
}
SleeperThread& operator=(std::function<void()> func)
{
thread = std::thread([this, func] {
do {
func();
} while (do_run);
});
defined = true;
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;
if (defined) {
condvar.notify_all();
thread.join();
}
}
private:
std::thread thread;
std::condition_variable condvar;
std::mutex mutex;
bool defined = false;
bool do_run = true;
};
}

1264
include/util/clara.hpp Normal file

File diff suppressed because it is too large Load Diff

98
include/util/command.hpp Normal file
View File

@@ -0,0 +1,98 @@
#pragma once
#include <giomm.h>
#include <sys/wait.h>
#include <unistd.h>
namespace waybar::util::command {
struct res {
int exit_code;
std::string out;
};
inline std::string read(FILE* fp) {
std::array<char, 128> buffer = {0};
std::string output;
while (feof(fp) == 0) {
if (fgets(buffer.data(), 128, fp) != nullptr) {
output += buffer.data();
}
}
// Remove last newline
if (!output.empty() && output[output.length() - 1] == '\n') {
output.erase(output.length() - 1);
}
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,30 +5,24 @@
namespace waybar::util {
struct JsonParser {
JsonParser() : reader_(builder_.newCharReader()) {}
JsonParser()
: _reader(_builder.newCharReader())
{}
Json::Value parse(const std::string data)
{
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()
{
delete _reader;
}
~JsonParser() = default;
private:
Json::CharReaderBuilder _builder;
Json::CharReader *_reader;
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.3',
version: '0.6.1',
license: 'MIT',
default_options : ['cpp_std=c++17'],
default_options : [
'cpp_std=c++17',
'buildtype=release',
'default_library=static'
],
)
cpp_args = []
@@ -14,32 +18,106 @@ 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')
libnlgen = dependency('libnl-genl-3.0')
libpulse = dependency('libpulse')
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',
'src/ALabel.cpp',
'src/modules/memory.cpp',
'src/modules/battery.cpp',
'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 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'
]
endif
if libnl.found() and libnlgen.found()
add_project_arguments('-DHAVE_LIBNL', language: 'cpp')
src_files += 'src/modules/network.cpp'
endif
if libpulse.found()
add_project_arguments('-DHAVE_LIBPULSE', language: 'cpp')
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(
'waybar',
run_command('find', './src', '-name', '*.cpp').stdout().strip().split('\n'),
src_files,
dependencies: [
thread_dep,
wlroots,
@@ -51,9 +129,13 @@ executable(
libinput,
wayland_cursor,
gtkmm,
dbusmenu_gtk,
giounix,
libnl,
libnlgen,
libpulse,
libudev,
libmpdclient
],
include_directories: [include_directories('include')],
install: true,
@@ -62,5 +144,17 @@ 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)
if clangtidy.found()
run_target(
'tidy',
command: [
clangtidy,
'-checks=*,-fuchsia-default-arguments',
'-p', meson.build_root()
] + src_files)
endif

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,42 +1,136 @@
{
// "layer": "top", // Waybar at top layer
// "position": "bottom", // Waybar at the bottom of your screen
// "height": 30, // Waybar height
"layer": "top", // Waybar at top layer
// "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": ["workspaces", "custom/spotify"],
"modules-right": ["pulseaudio", "network", "cpu", "memory", "battery", "clock"],
"modules-left": ["sway/workspaces", "sway/mode", "custom/media"],
"modules-center": ["sway/window"],
"modules-right": ["mpd", "idle_inhibitor", "pulseaudio", "network", "cpu", "memory", "temperature", "backlight", "battery", "battery#bat2", "clock", "tray"],
// Modules configuration
"workspaces": {
// "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>"
},
"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": {
"1": "",
"2": "",
"3": "",
"4": "",
"5": ""
"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",
"format": "{essid} ({signalStrength}%) "
// "interface": "wlp2s0", // (Optional) To force the use of this interface
"format-wifi": "{essid} ({signalStrength}%) ",
"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": ["", ""]
},
"custom/spotify": {
"format": " {}",
"exec": "$HOME/.bin/mediaplayer.sh"
"on-click": "pavucontrol"
},
"custom/media": {
"format": "{icon} {}",
"return-type": "json",
"max-length": 40,
"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

@@ -3,33 +3,74 @@
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;
transition-property: background, background-color;
transition-duration: .5s;
}
#workspaces button {
padding: 0 8px;
window#waybar.hidden {
opacity: 0.2;
}
/*
window#waybar.empty {
background: transparent;
color: white;
}
window#waybar.solo {
background: #FFFFFF;
}
*/
window#waybar.termite {
background-color: #3F3F3F;
}
window#waybar.chromium {
background-color: #DEE1E6;
color: #000000;
border: none;
}
/* https://github.com/Alexays/Waybar/wiki/FAQ#the-workspace-buttons-have-a-strange-hover-effect */
#workspaces button {
padding: 0 5px;
background: transparent;
color: #ffffff;
border-bottom: 3px solid transparent;
}
#workspaces button label {
font-size: 12px;
window#waybar.chromium #workspaces button {
color: #3F3F3F;
}
#workspaces button.current {
#workspaces button.focused {
background: #64727D;
border-bottom: 3px solid white;
border-bottom: 3px solid #ffffff;
}
#clock, #battery, #cpu, #memory, #network, #pulseaudio, #custom-spotify {
window#waybar.chromium #workspaces button.focused {
color: #ffffff;
}
#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 +79,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 +104,10 @@ window {
animation-direction: alternate;
}
label:focus {
background-color: #000000;
}
#cpu {
background: #2ecc71;
color: #000000;
@@ -72,13 +117,21 @@ window {
background: #9b59b6;
}
#backlight {
background: #90b1b1;
}
#network {
background: #2980b9;
}
#network.disconnected {
background: #f53c3c;
}
#pulseaudio {
background: #f1c40f;
color: black;
color: #000000;
}
#pulseaudio.muted {
@@ -86,7 +139,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;
}

130
src/ALabel.cpp Normal file
View File

@@ -0,0 +1,130 @@
#include "ALabel.hpp"
#include <util/command.hpp>
#include <iostream>
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(100000000)
: std::chrono::seconds(
config_["interval"].isUInt() ? config_["interval"].asUInt() : interval)),
default_format_(format_) {
event_box_.add(label_);
if (config_["max-length"].isUInt()) {
label_.set_max_width_chars(config_["max-length"].asUInt());
label_.set_ellipsize(Pango::EllipsizeMode::ELLIPSIZE_END);
}
if (config_["rotate"].isUInt()) {
label_.set_angle(config["rotate"].asUInt());
}
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));
}
// 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));
}
}
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& 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, 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"];
}
}
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 "";
}
bool waybar::ALabel::tooltipEnabled() {
return config_["tooltip"].isBool() ? config_["tooltip"].asBool() : true;
}
waybar::ALabel::operator Gtk::Widget&() { return event_box_; }

View File

@@ -1,188 +1,263 @@
#include "bar.hpp"
#include "client.hpp"
#include "factory.hpp"
#include "util/json.hpp"
waybar::Bar::Bar(Client &client, std::unique_ptr<struct wl_output *> &&p_output)
: client(client), window{Gtk::WindowType::WINDOW_TOPLEVEL},
output(std::move(p_output))
{
static const struct zxdg_output_v1_listener xdgOutputListener = {
.logical_position = _handleLogicalPosition,
.logical_size = _handleLogicalSize,
.done = _handleDone,
.name = _handleName,
.description = _handleDescription,
};
_xdgOutput =
zxdg_output_manager_v1_get_xdg_output(client.xdg_output_manager, *output);
zxdg_output_v1_add_listener(_xdgOutput, &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();
if (_config["height"])
_height = _config["height"].asUInt();
bool positionBottom = _config["position"] == "bottom";
bool layerTop = _config["layer"] == "top";
gtk_widget_realize(GTK_WIDGET(window.gobj()));
GdkWindow *gdkWindow = gtk_widget_get_window(GTK_WIDGET(window.gobj()));
gdk_wayland_window_set_use_custom_surface(gdkWindow);
surface = gdk_wayland_window_get_wl_surface(gdkWindow);
layerSurface = zwlr_layer_shell_v1_get_layer_surface(
client.layer_shell, surface, *output,
(layerTop ? ZWLR_LAYER_SHELL_V1_LAYER_TOP : ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM),
"waybar");
zwlr_layer_surface_v1_set_anchor(layerSurface,
ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT |
(positionBottom ? ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM : ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP));
zwlr_layer_surface_v1_set_size(layerSurface, _width, _height);
static const struct zwlr_layer_surface_v1_listener layerSurfaceListener = {
.configure = _layerSurfaceHandleConfigure,
.closed = _layerSurfaceHandleClosed,
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 = 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->output, layer, "waybar");
static const struct zwlr_layer_surface_v1_listener layer_surface_listener = {
.configure = layerSurfaceHandleConfigure,
.closed = layerSurfaceHandleClosed,
};
zwlr_layer_surface_v1_add_listener(layerSurface, &layerSurfaceListener,
this);
zwlr_layer_surface_v1_add_listener(layer_surface, &layer_surface_listener, this);
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;
}
zwlr_layer_surface_v1_set_anchor(layer_surface, anchor);
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();
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::_handleLogicalSize(void *data,
struct zxdg_output_v1 *zxdg_output_v1, int32_t width, int32_t height)
{
// Nothing here
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::_handleDone(void *data, struct zxdg_output_v1 *zxdg_output_v1)
{
// Nothing here
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::_handleName(void *data, struct zxdg_output_v1 *xdg_output,
const char *name)
{
auto o = reinterpret_cast<waybar::Bar *>(data);
o->outputName = 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 = reinterpret_cast<waybar::Bar *>(data);
o->window.show_all();
o->setWidth(o->_config["width"] ? o->_config["width"].asUInt() : width);
zwlr_layer_surface_v1_ack_configure(surface, serial);
if (o->_height != height) {
height = o->_height;
std::cout << fmt::format("New Height: {}", height) << std::endl;
zwlr_layer_surface_v1_set_size(surface, o->_width, height);
zwlr_layer_surface_v1_set_exclusive_zone(surface, o->visible ? height : 0);
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 = reinterpret_cast<waybar::Bar *>(data);
zwlr_layer_surface_v1_destroy(o->layerSurface);
o->layerSurface = nullptr;
wl_surface_destroy(o->surface);
o->surface = nullptr;
o->window.close();
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);
o->modules_left_.clear();
o->modules_center_.clear();
o->modules_right_.clear();
}
auto waybar::Bar::setWidth(uint32_t width) -> void
{
if (width == this->_width) return;
std::cout << fmt::format("Bar width configured: {}", width) << std::endl;
this->_width = width;
window.set_size_request(width);
window.resize(width, _height);
zwlr_layer_surface_v1_set_size(layerSurface, width, _height + 1);
wl_surface_commit(surface);
}
auto waybar::Bar::toggle() -> void
{
auto waybar::Bar::toggle() -> void {
visible = !visible;
auto zone = visible ? _height : 0;
zwlr_layer_surface_v1_set_exclusive_zone(layerSurface, zone);
auto zone = visible ? height_ : 0;
if (!visible) {
window.get_style_context()->add_class("hidden");
} else {
window.get_style_context()->remove_class("hidden");
}
zwlr_layer_surface_v1_set_exclusive_zone(layer_surface, zone);
wl_surface_commit(surface);
}
auto waybar::Bar::_setupConfig() -> void
{
util::JsonParser parser;
std::ifstream file(client.configFile);
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>());
_config = parser.parse(str);
}
auto waybar::Bar::_setupCss() -> void
{
_cssProvider = Gtk::CssProvider::create();
_styleContext = Gtk::StyleContext::create();
// load our css file, wherever that may be hiding
if (_cssProvider->load_from_path(client.cssFile)) {
Glib::RefPtr<Gdk::Screen> screen = window.get_screen();
_styleContext->add_provider_for_screen(screen, _cssProvider,
GTK_STYLE_PROVIDER_PRIORITY_USER);
}
}
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 &box1 = *Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 0));
window.add(box1);
box1.set_homogeneous(true);
box1.pack_start(left, true, true);
box1.pack_start(center, false, false);
box1.pack_end(right, true, true);
Factory factory(*this, _config);
if (_config["modules-left"]) {
for (auto name : _config["modules-left"]) {
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 (module)
left.pack_start(*module, false, true, 0);
if (pos == "modules-left") {
modules_left_.emplace_back(module);
}
if (pos == "modules-center") {
modules_center_.emplace_back(module);
}
if (_config["modules-center"]) {
for (auto name : _config["modules-center"]) {
auto module = factory.makeModule(name.asString());
if (module)
center.pack_start(*module, true, false, 10);
if (pos == "modules-right") {
modules_right_.emplace_back(module);
}
module->dp.connect([module, &name] {
try {
module->update();
} catch (const std::exception& e) {
std::cerr << name.asString() + ": " + e.what() << std::endl;
}
if (_config["modules-right"]) {
std::reverse(_config["modules-right"].begin(), _config["modules-right"].end());
for (auto name : _config["modules-right"]) {
auto module = factory.makeModule(name.asString());
if (module)
right.pack_end(*module, false, false, 0);
});
} catch (const std::exception& e) {
std::cerr << e.what() << std::endl;
}
}
}
}
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);
// Convert to button code for every module that is used.
setupAltFormatKeyForModuleList("modules-left");
setupAltFormatKeyForModuleList("modules-right");
setupAltFormatKeyForModuleList("modules-center");
Factory factory(*this, config);
getModules(factory, "modules-left");
getModules(factory, "modules-center");
getModules(factory, "modules-right");
for (auto const& module : modules_left_) {
left_.pack_start(*module, false, 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 const& module : modules_right_) {
right_.pack_end(*module, false, false, 0);
}
window.show_all();
}

View File

@@ -1,93 +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_main(argc, argv),
gdk_display(Gdk::Display::get_default()),
wlDisplay(gdk_wayland_display_get_wl_display(gdk_display->gobj()))
{
auto getFirstValidPath = [] (std::vector<std::string> possiblePaths) {
waybar::Client *waybar::Client::inst() {
static auto c = new Client();
return c;
}
const std::string waybar::Client::getValidPath(const std::vector<std::string> &paths) {
wordexp_t p;
for (std::string path: possiblePaths) {
for (const std::string &path : paths) {
if (wordexp(path.c_str(), &p, 0) == 0) {
if (access(p.we_wordv[0], F_OK) == 0) {
std::string result = p.we_wordv[0];
if (access(*p.we_wordv, F_OK) == 0) {
std::string result = *p.we_wordv;
wordfree(&p);
return result;
} else {
wordfree(&p);
}
wordfree(&p);
}
}
return std::string();
};
}
configFile = getFirstValidPath({
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;
}
}
found = in_array;
}
if (config["output"].isString() && config["output"].asString() != output->name) {
found = false;
}
return found;
}
std::unique_ptr<struct waybar::waybar_output> &waybar::Client::getOutput(uint32_t wl_name) {
auto it = std::find_if(outputs_.begin(), outputs_.end(), [&wl_name](const auto &output) {
return output->wl_name == wl_name;
});
if (it == outputs_.end()) {
throw std::runtime_error("Unable to find valid output");
}
return *it;
}
std::vector<Json::Value> waybar::Client::getOutputConfigs(
std::unique_ptr<struct waybar_output> &output) {
std::vector<Json::Value> configs;
if (config_.isArray()) {
for (auto const &config : config_) {
if (config.isObject() && isValidOutput(config, output)) {
configs.push_back(config);
}
}
} else if (isValidOutput(config_, output)) {
configs.push_back(config_);
}
return configs;
}
void waybar::Client::handleName(void * data, struct zxdg_output_v1 * /*xdg_output*/,
const char *name) {
auto wl_name = *static_cast<uint32_t *>(data);
auto client = waybar::Client::inst();
try {
auto &output = client->getOutput(wl_name);
output->name = name;
auto configs = client->getOutputConfigs(output);
if (configs.empty()) {
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::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",
});
cssFile = getFirstValidPath({
})
: 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;
}
void waybar::Client::_handle_global(void *data, struct wl_registry *registry,
uint32_t name, const char *interface, uint32_t version)
{
auto o = reinterpret_cast<waybar::Client *>(data);
if (!strcmp(interface, zwlr_layer_shell_v1_interface.name)) {
o->layer_shell = (zwlr_layer_shell_v1 *)wl_registry_bind(registry, name,
&zwlr_layer_shell_v1_interface, version);
} else if (!strcmp(interface, wl_output_interface.name)) {
o->wlOutput = (struct wl_output *)wl_registry_bind(registry, name,
&wl_output_interface, version);
auto output = std::make_unique<struct wl_output *>();
*output = o->wlOutput;
if (o->xdg_output_manager)
o->bars.emplace_back(std::make_unique<Bar>(*o, std::move(output)));
} else if (!strcmp(interface, wl_seat_interface.name)) {
o->seat = (struct wl_seat *)wl_registry_bind(registry, name,
&wl_seat_interface, version);
} else if (!strcmp(interface, zxdg_output_manager_v1_interface.name)
&& version >= ZXDG_OUTPUT_V1_NAME_SINCE_VERSION) {
o->xdg_output_manager =
(struct zxdg_output_manager_v1 *)wl_registry_bind(registry, name,
&zxdg_output_manager_v1_interface, ZXDG_OUTPUT_V1_NAME_SINCE_VERSION);
if (o->wlOutput) {
auto output = std::make_unique<struct wl_output *>();
*output = o->wlOutput;
o->bars.emplace_back(std::make_unique<Bar>(*o, std::move(output)));
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::_handle_global_remove(void *data,
struct wl_registry *registry, uint32_t name)
{
// TODO
}
void waybar::Client::bind_interfaces()
{
registry = wl_display_get_registry(wlDisplay);
void waybar::Client::bindInterfaces() {
registry = wl_display_get_registry(wl_display);
static const struct wl_registry_listener registry_listener = {
.global = _handle_global,
.global_remove = _handle_global_remove,
.global = handleGlobal,
.global_remove = handleGlobalRemove,
};
wl_registry_add_listener(registry, &registry_listener, this);
wl_display_roundtrip(wlDisplay);
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[])
{
bind_interfaces();
gtk_main.run();
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_display_disconnect(wl_display);
return 0;
}

View File

@@ -1,35 +1,75 @@
#include "factory.hpp"
waybar::Factory::Factory(Bar &bar, 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(std::string name)
{
waybar::IModule* waybar::Factory::makeModule(const std::string& name) const {
try {
if (name == "battery")
return new waybar::modules::Battery(_config[name]);
if (name == "workspaces")
return new waybar::modules::Workspaces(_bar, _config[name]);
if (name == "memory")
return new waybar::modules::Memory(_config[name]);
if (name == "cpu")
return new waybar::modules::Cpu(_config[name]);
if (name == "clock")
return new waybar::modules::Clock(_config[name]);
if (name == "network")
return new waybar::modules::Network(_config[name]);
if (name == "pulseaudio")
return new waybar::modules::Pulseaudio(_config[name]);
if (!name.compare(0, 7, "custom/") && name.size() > 7)
return new waybar::modules::Custom(name.substr(7), _config[name]);
std::cerr << "Unknown module: " + name << std::endl;
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 (ref == "sway/mode") {
return new waybar::modules::sway::Mode(id, bar_, config_[name]);
}
if (ref == "sway/workspaces") {
return new waybar::modules::sway::Workspaces(id, bar_, config_[name]);
}
if (ref == "sway/window") {
return new waybar::modules::sway::Window(id, bar_, config_[name]);
}
#endif
if (ref == "idle_inhibitor") {
return new waybar::modules::IdleInhibitor(id, bar_, config_[name]);
}
if (ref == "memory") {
return new waybar::modules::Memory(id, config_[name]);
}
if (ref == "cpu") {
return new waybar::modules::Cpu(id, config_[name]);
}
if (ref == "clock") {
return new waybar::modules::Clock(id, 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());
std::cerr << err << std::endl;
throw std::runtime_error(err);
} catch (...) {
auto err = fmt::format("Disabling module \"{}\", Unknown reason", name);
std::cerr << err << std::endl;
throw std::runtime_error(err);
}
return nullptr;
throw std::runtime_error("Unknown module: " + name);
}

View File

@@ -1,95 +0,0 @@
#define _POSIX_C_SOURCE 200809L
#include <string>
#include <stdio.h>
#include <sys/socket.h>
#include <sys/un.h>
#include "ipc/client.hpp"
static const char ipc_magic[] = {'i', '3', '-', 'i', 'p', 'c'};
static const size_t ipc_header_size = sizeof(ipc_magic)+8;
std::string get_socketpath(void) {
const char *env = getenv("SWAYSOCK");
if (env) return std::string(env);
std::string str;
{
std::string str_buf;
FILE* in;
char buf[512] = { 0 };
if (!(in = popen("sway --get-socketpath 2>/dev/null", "r"))) {
throw std::runtime_error("Failed to get socket path");
}
while (fgets(buf, sizeof(buf), in) != nullptr) {
str_buf.append(buf, sizeof(buf));
}
pclose(in);
str = str_buf;
}
if (str.back() == '\n') {
str.pop_back();
}
return str;
}
int ipc_open_socket(std::string socket_path) {
struct sockaddr_un addr;
int socketfd;
if ((socketfd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
throw std::runtime_error("Unable to open Unix socket");
}
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, socket_path.c_str(), sizeof(addr.sun_path) - 1);
addr.sun_path[sizeof(addr.sun_path) - 1] = 0;
int l = sizeof(struct sockaddr_un);
if (connect(socketfd, (struct sockaddr *)&addr, l) == -1) {
throw std::runtime_error("Unable to connect to " + socket_path);
}
return socketfd;
}
struct ipc_response ipc_recv_response(int socketfd) {
struct ipc_response response;
char data[ipc_header_size];
uint32_t *data32 = (uint32_t *)(data + sizeof(ipc_magic));
size_t total = 0;
while (total < ipc_header_size) {
ssize_t received = recv(socketfd, data + total, ipc_header_size - total, 0);
if (received <= 0) {
throw std::runtime_error("Unable to receive IPC response");
}
total += received;
}
total = 0;
response.size = data32[0];
response.type = data32[1];
char payload[response.size + 1];
while (total < response.size) {
ssize_t received = recv(socketfd, payload + total, response.size - total, 0);
if (received < 0) {
throw std::runtime_error("Unable to receive IPC response");
}
total += received;
}
payload[response.size] = '\0';
response.payload = std::string(payload);
return response;
}
std::string ipc_single_command(int socketfd, uint32_t type, const char *payload, uint32_t *len) {
char data[ipc_header_size];
uint32_t *data32 = (uint32_t *)(data + sizeof(ipc_magic));
memcpy(data, ipc_magic, sizeof(ipc_magic));
data32[0] = *len;
data32[1] = type;
if (send(socketfd, data, ipc_header_size, 0) == -1)
throw std::runtime_error("Unable to send IPC header");
if (send(socketfd, payload, *len, 0) == -1)
throw std::runtime_error("Unable to send IPC payload");
struct ipc_response resp = ipc_recv_response(socketfd);
*len = resp.size;
return resp.payload;
}

View File

@@ -2,22 +2,26 @@
#include <iostream>
#include "client.hpp"
namespace waybar {
static Client* client;
}
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.get()->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,81 +1,164 @@
#include "modules/battery.hpp"
waybar::modules::Battery::Battery(Json::Value config)
: _config(config)
{
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 const& bat : batteries_) {
auto wd = inotify_add_watch(fd_, (bat / "uevent").c_str(), IN_ACCESS);
if (wd != -1) {
wds_.push_back(wd);
}
}
worker();
}
waybar::modules::Battery::~Battery() {
for (auto wd : wds_) {
inotify_rm_watch(fd_, wd);
}
close(fd_);
}
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) || event.mask & IN_IGNORED) {
thread_.stop();
return;
}
// TODO: don't stop timer for now since there is some bugs :?
// thread_timer_.stop();
dp.emit();
};
}
void waybar::modules::Battery::getBatteries() {
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);
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.size())
if (batteries_.empty()) {
if (config_["bat"].isString()) {
throw std::runtime_error("No battery named " + config_["bat"].asString());
}
throw std::runtime_error("No batteries.");
auto fd = inotify_init();
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);
// Trigger first value
update();
_label.set_name("battery");
_thread = [this, fd] {
struct inotify_event event;
int nbytes = read(fd, &event, sizeof(event));
if (nbytes != sizeof(event))
return;
Glib::signal_idle().connect_once(sigc::mem_fun(*this, &Battery::update));
};
}
}
auto waybar::modules::Battery::update() -> void
{
const std::tuple<uint8_t, std::string> waybar::modules::Battery::getInfos() const {
try {
uint16_t total = 0;
bool charging = false;
std::string status;
for (auto &bat : _batteries) {
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;
if (status == "Charging")
charging = true;
std::ifstream(bat / "status") >> _status;
if (_status != "Unknown") {
status = _status;
}
total += capacity;
}
uint16_t capacity = total / _batteries.size();
auto format = _config["format"]
? _config["format"].asString() : "{capacity}%";
_label.set_text(fmt::format(format, fmt::arg("capacity", capacity),
fmt::arg("icon", _getIcon(capacity))));
_label.set_tooltip_text(status);
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");
uint16_t capacity = total / batteries_.size();
return {capacity, status};
} catch (const std::exception& e) {
std::cerr << e.what() << std::endl;
return {0, "Unknown"};
}
}
std::string waybar::modules::Battery::_getIcon(uint16_t percentage)
{
if (!_config["format-icons"] || !_config["format-icons"].isArray()) return "";
auto size = _config["format-icons"].size();
auto idx = std::clamp(percentage / (100 / size), 0U, size - 1);
return _config["format-icons"][idx].asString();
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";
}
waybar::modules::Battery::operator Gtk::Widget &()
{
return _label;
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,27 +1,39 @@
#include "modules/clock.hpp"
waybar::modules::Clock::Clock(Json::Value config)
: _config(config)
{
_label.set_name("clock");
_thread = [this] {
Glib::signal_idle().connect_once(sigc::mem_fun(*this, &Clock::update));
auto now = waybar::chrono::clock::now();
auto timeout =
std::chrono::floor<std::chrono::minutes>(now + std::chrono::minutes(1));
_thread.sleep_until(timeout);
waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
: ALabel(config, "{:%H:%M}", 60) {
label_.set_name("clock");
if (!id.empty()) {
label_.get_style_context()->add_class(id);
}
thread_ = [this] {
dp.emit();
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 t = std::time(nullptr);
auto localtime = std::localtime(&t);
auto format =
_config["format"] ? _config["format"].asString() : "{:02}:{:02}";
_label.set_text(fmt::format(format, localtime->tm_hour, localtime->tm_min));
}
waybar::modules::Clock::operator Gtk::Widget &() {
return _label;
auto waybar::modules::Clock::update() -> void {
auto localtime = fmt::localtime(std::time(nullptr));
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,27 +1,85 @@
#include "modules/cpu.hpp"
waybar::modules::Cpu::Cpu(Json::Value config)
: _config(config)
{
_label.set_name("cpu");
int interval = _config["interval"] ? _config["inveral"].asInt() : 10;
_thread = [this, interval] {
Glib::signal_idle().connect_once(sigc::mem_fun(*this, &Cpu::update));
_thread.sleep_for(chrono::seconds(interval));
};
};
auto waybar::modules::Cpu::update() -> void
{
struct sysinfo info;
if (!sysinfo(&info)) {
float f_load = 1.f / (1 << SI_LOAD_SHIFT);
int load = info.loads[0] * f_load * 100 / get_nprocs();
auto format = _config["format"] ? _config["format"].asString() : "{}%";
_label.set_text(fmt::format(format, load));
waybar::modules::Cpu::Cpu(const std::string& id, const Json::Value& config)
: ALabel(config, "{usage}%", 10) {
label_.set_name("cpu");
if (!id.empty()) {
label_.get_style_context()->add_class(id);
}
thread_ = [this] {
dp.emit();
thread_.sleep_for(interval_);
};
}
waybar::modules::Cpu::operator Gtk::Widget &() {
return _label;
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);
uint16_t load = info.loads[0] * f_load * 100 / get_nprocs();
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,49 +1,173 @@
#include "modules/custom.hpp"
#include <iostream>
waybar::modules::Custom::Custom(std::string name, Json::Value config)
: _name(name), _config(config)
{
if (!_config["exec"])
throw std::runtime_error(name + " has no exec path.");
int interval = _config["interval"] ? _config["inveral"].asInt() : 30;
_thread = [this, interval] {
Glib::signal_idle().connect_once(sigc::mem_fun(*this, &Custom::update));
_thread.sleep_for(chrono::seconds(interval));
};
};
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_);
if (config_["exec"].isString()) {
if (interval_.count() > 0) {
delayWorker();
} else {
continuousWorker();
}
}
dp.emit();
}
auto waybar::modules::Custom::update() -> void
{
std::array<char, 128> buffer;
std::string output;
std::shared_ptr<FILE> fp(popen(_config["exec"].asCString(), "r"), pclose);
if (!fp) {
std::cerr << _name + " can't exec " + _config["exec"].asString() << std::endl;
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"].isString()) {
auto res = waybar::util::command::exec(config_["exec-if"].asString());
if (res.exit_code != 0) {
can_update = false;
event_box_.hide();
}
}
if (can_update) {
output_ = waybar::util::command::exec(config_["exec"].asString());
dp.emit();
}
thread_.sleep_for(interval_);
};
}
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;
}
while (!feof(fp.get())) {
if (fgets(buffer.data(), 128, fp.get()) != nullptr)
output += buffer.data();
}
std::string output = buff;
// 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 (output.empty()) {
_label.set_name("");
_label.hide();
if (config_["exec"].isString() && (output_.out.empty() || output_.exit_code != 0)) {
event_box_.hide();
} else {
_label.set_name("custom-" + _name);
auto format = _config["format"] ? _config["format"].asString() : "{}";
_label.set_text(fmt::format(format, output));
_label.show();
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();
}
}
waybar::modules::Custom::operator Gtk::Widget &() {
return _label;
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,30 +1,61 @@
#include "modules/memory.hpp"
waybar::modules::Memory::Memory(Json::Value config)
: _config(config)
{
_label.set_name("memory");
int interval = _config["interval"] ? _config["inveral"].asInt() : 30;
_thread = [this, interval] {
Glib::signal_idle().connect_once(sigc::mem_fun(*this, &Memory::update));
_thread.sleep_for(chrono::seconds(interval));
};
waybar::modules::Memory::Memory(const std::string& id, const Json::Value& config)
: ALabel(config, "{}%", 30) {
label_.set_name("memory");
if (!id.empty()) {
label_.get_style_context()->add_class(id);
}
thread_ = [this] {
dp.emit();
thread_.sleep_for(interval_);
};
}
auto waybar::modules::Memory::update() -> void
{
struct sysinfo info;
if (!sysinfo(&info)) {
auto total = info.totalram * info.mem_unit;
auto freeram = info.freeram * info.mem_unit;
int used_ram_percentage = 100 * (total - freeram) / total;
auto format = _config["format"] ? _config["format"].asString() : "{}%";
_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();
}
}
waybar::modules::Memory::operator Gtk::Widget &() {
return _label;
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,30 +1,438 @@
#include "modules/network.hpp"
#include <sys/eventfd.h>
waybar::modules::Network::Network(Json::Value config)
: _config(config), _ifid(if_nametoindex(config["interface"].asCString()))
{
if (_ifid == 0)
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);
}
createInfoSocket();
createEventSocket();
if (config_["interface"].isString()) {
ifid_ = if_nametoindex(config_["interface"].asCString());
ifname_ = config_["interface"].asString();
if (ifid_ <= 0) {
throw std::runtime_error("Can't found network interface");
_label.set_name("network");
int interval = _config["interval"] ? _config["inveral"].asInt() : 30;
_thread = [this, interval] {
Glib::signal_idle().connect_once(sigc::mem_fun(*this, &Network::update));
_thread.sleep_for(chrono::minutes(1));
};
};
auto waybar::modules::Network::update() -> void
{
_getInfo();
auto format = _config["format"] ? _config["format"].asString() : "{essid}";
_label.set_text(fmt::format(format,
fmt::arg("essid", _essid),
fmt::arg("signaldBm", _signalStrengthdBm),
fmt::arg("signalStrength", _signalStrength)
));
}
} else {
ifid_ = getExternalInterface();
if (ifid_ > 0) {
char ifname[IF_NAMESIZE];
if_indextoname(ifid_, ifname);
ifname_ = ifname;
}
}
dp.emit();
worker();
}
int waybar::modules::Network::_scanCb(struct nl_msg *msg, void *data) {
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_);
}
}
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::createEventSocket() {
sk_ = nl_socket_alloc();
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, handleScan, this) < 0) {
throw std::runtime_error("Can't set callback");
}
nl80211_id_ = genl_ctrl_resolve(sk_, "nl80211");
if (nl80211_id_ < 0) {
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() {
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;
/* Prepare request. */
constexpr uint32_t reqlen = NLMSG_SPACE(sizeof(*rt));
char req[reqlen] = {0};
/* Build the RTM_GETROUTE request. */
hdr = reinterpret_cast<struct nlmsghdr *>(req);
hdr->nlmsg_len = NLMSG_LENGTH(sizeof(*rt));
hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
hdr->nlmsg_type = RTM_GETROUTE;
rt = static_cast<struct rtmsg *>(NLMSG_DATA(hdr));
rt->rtm_family = family_;
rt->rtm_table = RT_TABLE_MAIN;
/* Issue the query. */
if (netlinkRequest(req, reqlen) < 0) {
goto out;
}
/* Read the response(s).
*
* WARNING: All the packets generated by the request must be consumed (as in,
* consume responses till NLMSG_DONE/NLMSG_ERROR is encountered).
*/
do {
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)) {
if (hdr->nlmsg_type == NLMSG_DONE) {
goto out;
}
if (hdr->nlmsg_type == NLMSG_ERROR) {
/* Even if we found the interface index, something is broken with the
* netlink socket, so return an error.
*/
ifidx = -1;
goto out;
}
/* If we found the correct answer, skip parsing the attributes. */
if (ifidx != -1) {
continue;
}
/* Find the message(s) concerting the main routing table, each message
* corresponds to a single routing table entry.
*/
rt = static_cast<struct rtmsg *>(NLMSG_DATA(hdr));
if (rt->rtm_table != RT_TABLE_MAIN) {
continue;
}
/* 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;
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
* set, that it is all 0s.
*/
switch (attr->rta_type) {
case RTA_GATEWAY:
/* The gateway of the route.
*
* If someone every needs to figure out the gateway address as well,
* it's here as the attribute payload.
*/
has_gateway = true;
break;
case RTA_DST: {
/* The destination address.
* 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);
if (dstlen != nr_zeroes) {
break;
}
for (uint32_t i = 0; i < dstlen; i += 1) {
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));
break;
default:
break;
}
}
/* If this is the default route, and we know the interface index,
* we can stop parsing this message.
*/
if (has_gateway && !has_destination && temp_idx != -1) {
ifidx = temp_idx;
break;
}
}
} while (true);
out:
return ifidx;
}
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);
}
}
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 = {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::handleEvents(struct nl_msg *msg, void *data) {
int ret = 0;
auto net = static_cast<waybar::modules::Network *>(data);
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];
@@ -40,26 +448,29 @@ 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])
}
if (tb[NL80211_ATTR_BSS] == nullptr) {
return NL_SKIP;
if (nla_parse_nested(bss, NL80211_BSS_MAX, tb[NL80211_ATTR_BSS], bss_policy))
}
if (nla_parse_nested(bss, NL80211_BSS_MAX, tb[NL80211_ATTR_BSS], bss_policy) != 0) {
return NL_SKIP;
if (!net->_associatedOrJoined(bss))
}
if (!net->associatedOrJoined(bss)) {
return NL_SKIP;
net->_parseEssid(bss);
net->_parseSignal(bss);
// TODO: parse quality
}
net->parseEssid(bss);
net->parseSignal(bss);
// TODO(someone): parse quality
return NL_SKIP;
}
void waybar::modules::Network::_parseEssid(struct nlattr **bss)
{
_essid.clear();
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 = 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) {
@@ -69,29 +480,33 @@ void waybar::modules::Network::_parseEssid(struct nlattr **bss)
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));
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) {
void waybar::modules::Network::parseSignal(struct nlattr **bss) {
if (bss[NL80211_BSS_SIGNAL_MBM] != nullptr) {
// signalstrength in dBm
_signalStrengthdBm =
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;
_signalStrength = ((double)(_signalStrengthdBm - 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)
{
if (!bss[NL80211_BSS_STATUS])
bool waybar::modules::Network::associatedOrJoined(struct nlattr **bss) {
if (bss[NL80211_BSS_STATUS] == nullptr) {
return false;
}
auto status = nla_get_u32(bss[NL80211_BSS_STATUS]);
switch (status) {
case NL80211_BSS_STATUS_ASSOCIATED:
@@ -103,37 +518,18 @@ bool waybar::modules::Network::_associatedOrJoined(struct nlattr** bss)
}
}
auto waybar::modules::Network::_getInfo() -> void
{
struct nl_sock *sk = nl_socket_alloc();
if (genl_connect(sk) != 0) {
nl_socket_free(sk);
auto waybar::modules::Network::getInfo() -> void {
getInterfaceAddress();
struct nl_msg *nl_msg = nlmsg_alloc();
if (nl_msg == nullptr) {
return;
}
if (nl_socket_modify_cb(sk, NL_CB_VALID, NL_CB_CUSTOM, _scanCb, this) < 0) {
nl_socket_free(sk);
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;
}
const int nl80211_id = genl_ctrl_resolve(sk, "nl80211");
if (nl80211_id < 0) {
nl_socket_free(sk);
return;
}
struct nl_msg *msg = nlmsg_alloc();
if (!msg) {
nl_socket_free(sk);
return;
}
if (!genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, nl80211_id, 0,
NLM_F_DUMP, NL80211_CMD_GET_SCAN, 0) ||
nla_put_u32(msg, NL80211_ATTR_IFINDEX, _ifid) < 0) {
nlmsg_free(msg);
return;
}
nl_send_sync(sk, msg);
nl_socket_free(sk);
}
waybar::modules::Network::operator Gtk::Widget &() {
return _label;
nl_send_sync(sk_, nl_msg);
}

View File

@@ -1,86 +1,155 @@
#include "modules/pulseaudio.hpp"
#include <array>
waybar::modules::Pulseaudio::Pulseaudio(Json::Value config)
: _config(config), _mainloop(nullptr), _mainloop_api(nullptr),
_context(nullptr), _sinkIdx(0), _volume(0), _muted(false)
{
_label.set_name("pulseaudio");
_mainloop = pa_threaded_mainloop_new();
if (!_mainloop)
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.");
pa_threaded_mainloop_lock(_mainloop);
_mainloop_api = pa_threaded_mainloop_get_api(_mainloop);
_context = pa_context_new(_mainloop_api, "waybar");
if (!_context)
}
pa_threaded_mainloop_lock(mainloop_);
mainloop_api_ = pa_threaded_mainloop_get_api(mainloop_);
context_ = pa_context_new(mainloop_api_, "waybar");
if (context_ == nullptr) {
throw std::runtime_error("pa_context_new() failed.");
if (pa_context_connect(_context, NULL, PA_CONTEXT_NOAUTOSPAWN, NULL) < 0)
throw std::runtime_error(fmt::format("pa_context_connect() failed: {}",
pa_strerror(pa_context_errno(_context))));
pa_context_set_state_callback(_context, _contextStateCb, this);
if (pa_threaded_mainloop_start(_mainloop) < 0)
}
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);
if (pa_threaded_mainloop_start(mainloop_) < 0) {
throw std::runtime_error("pa_mainloop_run() failed.");
pa_threaded_mainloop_unlock(_mainloop);
};
}
pa_threaded_mainloop_unlock(mainloop_);
void waybar::modules::Pulseaudio::_contextStateCb(pa_context *c, void *data)
{
// 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() {
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) {
auto pa = static_cast<waybar::modules::Pulseaudio *>(data);
switch (pa_context_get_state(c)) {
case PA_CONTEXT_TERMINATED:
pa->_mainloop_api->quit(pa->_mainloop_api, 0);
pa->mainloop_api_->quit(pa->mainloop_api_, 0);
break;
case PA_CONTEXT_READY:
pa_context_get_server_info(c, _serverInfoCb, data);
pa_context_set_subscribe_callback(c, _subscribeCb, data);
pa_context_subscribe(c, PA_SUBSCRIPTION_MASK_SINK, nullptr,
nullptr);
pa_context_get_server_info(c, serverInfoCb, 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;
pa_operation *op = nullptr;
switch (facility) {
case PA_SUBSCRIPTION_EVENT_SINK:
pa_context_get_sink_info_by_index(context, idx, _sinkInfoCb, data);
break;
default:
assert(0);
break;
if (facility == PA_SUBSCRIPTION_EVENT_SINK) {
pa_context_get_sink_info_by_index(context, idx, sinkInfoCb, data);
}
}
/*
* 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);
}
if (op)
pa_operation_unref(op);
}
/*
* 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)
{
if (i) {
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 = (float)pa_cvolume_avg(&(i->volume)) / (float)PA_VOLUME_NORM;
pa->_sinkIdx = i->index;
pa->_volume = volume * 100.0f;
pa->_muted = i->mute;
pa->_desc = i->description;
Glib::signal_idle().connect_once(sigc::mem_fun(*pa, &Pulseaudio::update));
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_ = 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();
}
}
@@ -88,36 +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
{
auto format = _config["format"] ? _config["format"].asString() : "{volume}%";
if (_muted) {
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"].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-muted"] ? _config["format-muted"].asString() : format;
_label.get_style_context()->add_class("muted");
} else
_label.get_style_context()->remove_class("muted");
_label.set_label(fmt::format(format,
fmt::arg("volume", _volume),
fmt::arg("icon", _getIcon(_volume))));
_label.set_tooltip_text(_desc);
config_["format-bluetooth"].isString() ? config_["format-bluetooth"].asString() : format;
label_.get_style_context()->add_class("bluetooth");
} else {
label_.get_style_context()->remove_class("bluetooth");
}
std::string waybar::modules::Pulseaudio::_getIcon(uint16_t percentage)
{
if (!_config["format-icons"] || !_config["format-icons"].isArray()) return "";
auto size = _config["format-icons"].size();
auto idx = std::clamp(percentage / (100 / size), 0U, size - 1);
return _config["format-icons"][idx].asString();
}
waybar::modules::Pulseaudio::operator Gtk::Widget &() {
return _label;
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;
}
}

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

@@ -0,0 +1,149 @@
#include "modules/sway/ipc/client.hpp"
#include <fcntl.h>
namespace waybar::modules::sway {
Ipc::Ipc() {
const std::string& socketPath = getSocketPath();
fd_ = open(socketPath);
fd_event_ = open(socketPath);
}
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};
if ((in = popen("sway --get-socketpath 2>/dev/null", "r")) == nullptr) {
throw std::runtime_error("Failed to get socket path");
}
while (fgets(buf, sizeof(buf), in) != nullptr) {
str_buf.append(buf, sizeof(buf));
}
pclose(in);
str = str_buf;
if (str.empty()) {
throw std::runtime_error("Socket path is empty");
}
}
if (str.back() == '\n') {
str.pop_back();
}
return str;
}
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 Sway");
}
return fd;
}
struct Ipc::ipc_response Ipc::recv(int fd) {
std::string header;
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_) {
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 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.resize(data32[0]);
while (total < data32[0]) {
auto res = ::recv(fd, payload.data() + total, data32[0] - total, 0);
if (res < 0) {
if (errno == EINTR || errno == EAGAIN) {
continue;
}
throw std::runtime_error("Unable to receive IPC payload");
}
total += res;
}
std::lock_guard<std::mutex> lock(mutex_parser_);
auto parsed = parser_.parse(&payload.front());
return {data32[0], data32[1], parsed};
}
struct Ipc::ipc_response Ipc::send(int fd, uint32_t type, const std::string& payload) {
std::string header;
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;
if (::send(fd, header.data(), ipc_header_size_, 0) == -1) {
throw std::runtime_error("Unable to send IPC header");
}
if (::send(fd, payload.c_str(), payload.size(), 0) == -1) {
throw std::runtime_error("Unable to send IPC payload");
}
return Ipc::recv(fd);
}
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 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");
}
}
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

115
src/modules/sway/window.cpp Normal file
View File

@@ -0,0 +1,115 @@
#include "modules/sway/window.hpp"
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");
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 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 (!app_id_.empty()) {
bar_.window.get_style_context()->remove_class(app_id_);
}
if (nb == 0) {
bar_.window.get_style_context()->add_class("empty");
} else 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");
bar_.window.get_style_context()->remove_class("empty");
}
app_id_ = app_id;
if (windowId_ != id || window_ != name) {
windowId_ = id;
window_ = name;
dp.emit();
}
}
void Window::worker() {
thread_ = [this] {
try {
ipc_.handleEvent();
} catch (const std::exception& e) {
std::cerr << "Window: " << e.what() << std::endl;
}
};
}
auto Window::update() -> void {
label_.set_markup(fmt::format(format_, window_));
if (tooltipEnabled()) {
label_.set_tooltip_text(window_);
}
}
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") {
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 [nb, id, name, app_id] = getFocusedNode(node);
if (id > -1 && !name.empty()) {
return {nb, id, name, app_id};
}
}
return {0, -1, "", ""};
}
void Window::getTree() {
try {
ipc_.sendCmd(IPC_GET_TREE);
} catch (const std::exception& e) {
std::cerr << e.what() << std::endl;
}
}
} // namespace waybar::modules::sway

View File

@@ -0,0 +1,233 @@
#include "modules/sway/workspaces.hpp"
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");
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 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 {
ipc_.handleEvent();
} catch (const std::exception &e) {
std::cerr << "Workspaces: " << e.what() << std::endl;
}
};
}
bool Workspaces::filterButtons() {
bool needReorder = false;
for (auto it = buttons_.begin(); it != buttons_.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;
}
}
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;
}
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);
}
}
Gtk::Button &Workspaces::addButton(const Json::Value &node) {
auto pair = buttons_.emplace(node["name"].asString(), node["name"].asString());
auto &button = pair.first->second;
box_.pack_start(button, false, false, 0);
button.set_relief(Gtk::RELIEF_NONE);
button.signal_clicked().connect([this, pair] {
try {
ipc_.sendCmd(IPC_COMMAND, fmt::format("workspace \"{}\"", pair.first->first));
} catch (const std::exception &e) {
std::cerr << e.what() << std::endl;
}
});
if (!config_["disable-scroll"].asBool()) {
button.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);
button.signal_scroll_event().connect(sigc::mem_fun(*this, &Workspaces::handleScroll));
}
return button;
}
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 Workspaces::handleScroll(GdkEventScroll *e) {
// Avoid concurrent scroll event
if (scrolling_) {
return false;
}
std::string name;
scrolling_ = true;
{
std::lock_guard<std::mutex> lock(mutex_);
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 \"{}\"", name));
return true;
}
const std::string Workspaces::getCycleWorkspace(std::vector<Json::Value>::iterator it,
bool prev) const {
if (prev && it == workspaces_.begin()) {
return (*(--workspaces_.end()))["name"].asString();
}
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();
}
std::string Workspaces::trimWorkspaceName(std::string name) {
std::size_t found = name.find(':');
if (found != std::string::npos) {
return name.substr(found + 1);
}
return name;
}
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();
}

View File

@@ -1,184 +0,0 @@
#include "modules/workspaces.hpp"
#include "ipc/client.hpp"
waybar::modules::Workspaces::Workspaces(Bar &bar, Json::Value config)
: _bar(bar), _config(config), _scrolling(false)
{
_box.set_name("workspaces");
std::string socketPath = get_socketpath();
_ipcfd = ipc_open_socket(socketPath);
_ipcEventfd = ipc_open_socket(socketPath);
const char *subscribe = "[ \"workspace\" ]";
uint32_t len = strlen(subscribe);
ipc_single_command(_ipcEventfd, IPC_SUBSCRIBE, subscribe, &len);
_thread = [this] {
try {
if (_bar.outputName.empty()) {
// Wait for the name of the output
while (_bar.outputName.empty())
_thread.sleep_for(chrono::milliseconds(150));
} else
ipc_recv_response(_ipcEventfd);
uint32_t len = 0;
std::lock_guard<std::mutex> lock(_mutex);
auto str = ipc_single_command(_ipcfd, IPC_GET_WORKSPACES, nullptr, &len);
_workspaces = _getWorkspaces(str);
Glib::signal_idle().connect_once(sigc::mem_fun(*this, &Workspaces::update));
} catch (const std::exception& e) {
std::cerr << e.what() << std::endl;
}
};
}
auto waybar::modules::Workspaces::update() -> void
{
std::lock_guard<std::mutex> lock(_mutex);
bool needReorder = false;
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()) {
it = _buttons.erase(it);
needReorder = true;
} else
++it;
}
for (auto node : _workspaces) {
if (_bar.outputName != node["output"].asString())
continue;
auto it = _buttons.find(node["num"].asInt());
if (it == _buttons.end()) {
_addWorkspace(node);
needReorder = true;
} else {
auto &button = it->second;
if (node["focused"].asBool())
button.get_style_context()->add_class("current");
else
button.get_style_context()->remove_class("current");
if (needReorder)
_box.reorder_child(button, node["num"].asInt());
button.show();
}
}
if (_scrolling)
_scrolling = false;
}
void waybar::modules::Workspaces::_addWorkspace(Json::Value node)
{
auto pair = _buttons.emplace(node["num"].asInt(),
_getIcon(node["name"].asString()));
auto &button = pair.first->second;
_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 value = fmt::format("workspace \"{}\"", pair.first->first);
uint32_t size = value.size();
ipc_single_command(_ipcfd, IPC_COMMAND, value.c_str(), &size);
} catch (const std::exception& e) {
std::cerr << e.what() << std::endl;
}
});
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("current");
button.show();
}
std::string waybar::modules::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();
return name;
}
bool waybar::modules::Workspaces::_handleScroll(GdkEventScroll *e)
{
std::lock_guard<std::mutex> lock(_mutex);
// Avoid concurrent scroll event
if (_scrolling)
return false;
_scrolling = true;
int id = -1;
uint16_t idx = 0;
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 ((const GdkEvent *) e, &delta_x, &delta_y);
if (delta_y < 0)
id = _getNextWorkspace();
else if (delta_y > 0)
id = _getPrevWorkspace();
}
if (id == _workspaces[idx]["num"].asInt()) {
_scrolling = false;
return false;
}
auto value = fmt::format("workspace \"{}\"", id);
uint32_t size = value.size();
ipc_single_command(_ipcfd, IPC_COMMAND, value.c_str(), &size);
std::this_thread::sleep_for(std::chrono::milliseconds(150));
return true;
}
int waybar::modules::Workspaces::_getPrevWorkspace()
{
int current = -1;
for (uint16_t i = 0; i != _workspaces.size(); i += 1)
if (_workspaces[i]["focused"].asBool()) {
current = _workspaces[i]["num"].asInt();
if (i > 0)
return _workspaces[i - 1]["num"].asInt();
return _workspaces[_workspaces.size() - 1]["num"].asInt();
}
return current;
}
int waybar::modules::Workspaces::_getNextWorkspace()
{
int current = -1;
for (uint16_t i = 0; i != _workspaces.size(); i += 1)
if (_workspaces[i]["focused"].asBool()) {
current = _workspaces[i]["num"].asInt();
if (i + 1U < _workspaces.size())
return _workspaces[i + 1]["num"].asInt();
return _workspaces[0]["num"].asInt();
}
return current;
}
Json::Value waybar::modules::Workspaces::_getWorkspaces(const std::string data)
{
Json::Value res;
try {
std::string err;
res = _parser.parse(data);
} catch (const std::exception& e) {
std::cerr << e.what() << std::endl;
}
return res;
}
waybar::modules::Workspaces::operator Gtk::Widget &() {
return _box;
}

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