Compare commits

..

278 Commits
0.2.1 ... 0.6.1

Author SHA1 Message Date
82bed9dd5e chore: v0.6.1 2019-05-02 14:31:02 +02:00
d027243a19 fix: json thread safe 2019-05-02 14:24:54 +02:00
e6d59f05cc fix s/hidded/hidden/ typo (#295)
fix s/hidded/hidden/ typo
2019-05-02 13:59:57 +02:00
4d4562aade fix s/hidded/hidden/ typo 2019-05-01 12:40:12 +01:00
e8f31a0c4f revert: infinite seconds for once custom modules 2019-04-26 21:57:15 +02:00
f8c06b27ae Revert "feat(Cpu): dynamic fmt args"
This reverts commit 2d9bcb1a2d.
2019-04-26 21:49:16 +02:00
717a07d584 refactor(Window): simpler conditions 2019-04-26 15:29:54 +02:00
2d9bcb1a2d feat(Cpu): dynamic fmt args 2019-04-26 14:07:31 +02:00
4dd36890c1 style: background color transition 2019-04-26 12:37:35 +02:00
66acaeca7f style: workspaces button color for chromium class 2019-04-26 12:01:42 +02:00
20cf7592aa Merge pull request #287 from jonvaldes/master
Allow rotating label contents by specifying a new "rotate" property
2019-04-26 09:39:52 +02:00
9fe29c37b4 Fix indentation 2019-04-25 22:56:14 +02:00
f8ae1534db Allow rotating label contents by specifying a new "rotate" property in the label config 2019-04-25 22:47:58 +02:00
46c91a26ac style: workspaces button color for chromium class 2019-04-25 17:14:16 +02:00
07c592cc86 chore: v0.6.0 2019-04-25 16:59:22 +02:00
bb8ff5a99f feat(Bar): add class depend of window in the workspace 2019-04-25 16:47:51 +02:00
79a5e9ecee feat: multiple bar with same process 2019-04-25 13:25:06 +02:00
9504b7ac03 fix(Bar): typo 2019-04-24 12:42:16 +02:00
311c34ecbc feat(Bar): handle widget size changes 2019-04-24 12:37:24 +02:00
90d89fe974 refactor: kill custom modules scripts en destroy 2019-04-23 15:56:38 +02:00
cccf60c30e fix(Workspaces): fix concurrence and move json parser to ipc client 2019-04-23 11:42:08 +02:00
07dba791cf Update fmt build dependency (#284)
Update fmt build dependency
2019-04-23 09:46:03 +02:00
3ee99946c7 chore: update fmt build dependency
Closes #279
2019-04-23 04:40:27 +02:00
0d0f5ed7db chore: update fmt wrap 2019-04-21 19:19:38 +02:00
263a32c9af Merge pull request #278 from minijackson/mpd-timeout
fix(mpd): regularly timeout the event listener to prevent timeout
2019-04-21 19:13:38 +02:00
b50650f63f fix(mpd): regularly timeout the event listener to prevent timeout
The MPD server has a connection_timeout that defaults to 60. If no data
is transferred in this timespan, the connection is closed. If the
connection is closed while the event listener is listening for events,
it will block forever. By timing out the event listening and
re-connecting regularly, we prevent this issue. An option "timeout" has
been added for users that have a lower server connection_timeout than
default. Fixes #277
2019-04-21 10:58:40 +02:00
768bc29bc1 Merge pull request #275 from cole-h/issue-273
Ensure no NULL tags are set
2019-04-20 23:32:13 +02:00
12e1233d38 Fix compile-time warning of catch by value 2019-04-20 09:16:11 -07:00
160837b900 Ensure no NULL tags are set
Because `mpd_song_get_tag` from libmpdclient can return NULL, verify the
value of tag is valid. Otherwise, set a default string of "N/A". Also
adds configuration to specify what this default string should be.
2019-04-20 09:12:30 -07:00
d03997bdaa Merge pull request #272 from Alexays/refactoring
Refactoring
2019-04-19 17:33:49 +02:00
471b5b1ea1 Merge branch 'master' into refactoring 2019-04-19 17:33:18 +02:00
29d8f365f8 refactor(Tray): proper lookup in the default theme 2019-04-19 17:30:40 +02:00
8cf19826aa fix(Tray): Unexport on exit 2019-04-19 17:03:46 +02:00
cbb6f2a307 refactor(Workspaces, IPC): no more mutex in the workspaces modules, moved to the IPC client for a proper handling 2019-04-19 16:48:02 +02:00
e77c155ede fix(workspaces): avoid mutex block 2019-04-19 12:11:55 +02:00
811468c0ab Merge pull request #274 from minijackson/mpd-escape
fix(mpd): Escape MPD values in the label
2019-04-19 11:57:36 +02:00
171ecd53aa refactor(Bar): roundtrip before setup widgets 2019-04-19 11:56:40 +02:00
66b0420391 fix(mpd): Escape MPD values in the label 2019-04-19 11:11:44 +02:00
42dc0c4c71 fix(ipc): typo 2019-04-19 11:10:48 +02:00
bb1cf7570e refactor(IPC): use sigc signal 2019-04-19 11:09:06 +02:00
a14b933d3e fix(config): add missing comma 2019-04-18 17:55:02 +02:00
4c8f4f82dc fix(config): add missing comma 2019-04-18 17:54:38 +02:00
6ed8f94dab refactor: format code 2019-04-18 17:52:00 +02:00
807ef32357 refactor: format && better output management 2019-04-18 17:47:40 +02:00
817c42841b Merge pull request #268 from minijackson/mpd
Add MPD support
2019-04-18 16:11:16 +02:00
3e54c3c669 fix(mpd): better sample theme 2019-04-18 15:57:58 +02:00
3656035c89 fix(mpd): slightly better and safer error handling 2019-04-18 15:57:57 +02:00
0ce8821aec feat(mpd): Add playing / paused classes 2019-04-18 15:57:57 +02:00
38e37f3680 chore(mpd): add sample MPD config 2019-04-18 15:57:57 +02:00
ab43d34a1e refactor(mpd): Add module name to log messages 2019-04-18 15:57:57 +02:00
22eccc2ac2 feat(mpd): reset player state when connection drops 2019-04-18 15:57:57 +02:00
cd92b475ad chore: Add clang-format configuration and format MPD module 2019-04-18 15:57:57 +02:00
235997fa73 feat(mpd): Add support for elapsed and total time 2019-04-18 15:55:46 +02:00
80a12d0238 feat(mpd): play/pause on click & stop on right-click 2019-04-18 15:55:46 +02:00
07dab2baec feat(mpd): Add support for options (random, repeat, etc.) 2019-04-18 15:55:45 +02:00
cbfcec4867 feat(mpd): Add support for play/pause icons 2019-04-18 15:55:45 +02:00
557b786ce0 feat(mpd): Allow for specifying the reconnect interval 2019-04-18 15:55:45 +02:00
8c9dd94670 feat(mpd): Add support for setting tooltip label when disconnected 2019-04-18 15:55:45 +02:00
06aff70e2e feat: Add basic support for MPD 2019-04-18 15:55:45 +02:00
85b6e6f709 chore: Add EditorConfig file 2019-04-18 15:55:45 +02:00
6d6df4be00 refactor(sni-item): better way to search in default theme 2019-04-17 22:15:18 +02:00
9564adb5b4 refactor(Bar): avoid reinterpret_cast 2019-04-17 19:33:49 +02:00
aeaa1927d9 fix: add default_paths on init 2019-04-17 19:23:52 +02:00
346ec68578 refactor: format tray && partial fix for #235 2019-04-17 14:19:04 +02:00
93a1bafb46 chore: add clang-format 2019-04-17 13:47:34 +02:00
5f37abbd3f Update README.md 2019-04-16 13:42:49 +02:00
3273ee8b42 fix(Tray): icon size lookup 2019-04-15 12:10:37 +02:00
d05b8398fa fix: prefer to hold running even when no window is open 2019-04-15 11:42:16 +02:00
ecc5f48dd7 feat: partially hide waybar on toggle 2019-04-15 11:11:16 +02:00
316b948d86 Merge pull request #265 from Alexays/custom-multiple-classes
Custom: Ability to add multiple classes
2019-04-15 10:58:34 +02:00
5828d34fa0 Merge branch 'master' into custom-multiple-classes 2019-04-15 10:58:27 +02:00
bc9a49787a feat: enable pango markup on sway workspaces 2019-04-15 10:55:44 +02:00
6aee51479d feat: ability to add multiple classes 2019-04-15 10:18:27 +02:00
57c99dc526 refactor(Tray): also search in default theme 2019-04-11 15:28:38 +02:00
78067462be fix(Tray): icons update 2019-04-11 15:20:39 +02:00
5870421f84 refactor(temperature): check if file exist on init 2019-04-11 15:08:23 +02:00
24684ca71b chore: v0.5.1 2019-04-04 12:01:00 +02:00
8351dea292 refactor(network): process all messages 2019-04-04 11:58:27 +02:00
a68011bce6 style(workspaces): urgent style 2019-04-02 09:31:40 +02:00
1f6f443c48 Revert "refactor(network): fix skipped messages"
This reverts commit 1ccf372f8e.
2019-04-01 11:41:43 +02:00
7fac483530 fix: don't bind RTMGRP_IPV(4|6)_ROUTE 2019-03-31 16:33:01 +02:00
0ad9a03d3d Merge pull request #256 from SibrenVasse/spotify-json
Convert spotify module to generic json version
2019-03-31 16:30:16 +02:00
0f689b733d feat(custom): make spotify module generic
Set class via json attribute
Choose icon via alt attribute
2019-03-31 16:20:43 +02:00
618fe80670 chore(custom): fix typo and quoting style 2019-03-31 16:10:42 +02:00
3a8cd91cc0 Revert "refactor: partial revert of 1ccf372f8e9d74cb18e92220c18a0729832fe69e"
This reverts commit 949a4ecf2e.
2019-03-30 09:20:28 +01:00
30725824d0 Merge pull request #251 from DanielVoogsgerd/feature-select-player
Feature select player
2019-03-30 09:19:24 +01:00
6522a7acb4 chore: let compile sway modules even if we can't find the binary 2019-03-30 09:15:51 +01:00
fd3e4ab2d4 Merge branch 'master' into feature-select-player 2019-03-30 09:07:31 +01:00
949a4ecf2e refactor: partial revert of 1ccf372f8e 2019-03-30 09:03:31 +01:00
860a6a1b81 Merge pull request #252 from alebastr/tray-items-refresh
Process tray icon updates
2019-03-30 08:40:55 +01:00
5a2b5624dc feat(tray): process tray icon update signals 2019-03-29 18:40:28 -07:00
82fcee33b3 refactor(tray): use Gio::DBus bindings in SNI Item class 2019-03-29 18:28:29 -07:00
842e8501f9 fix(tray): free icon data on pixbuf update 2019-03-28 10:52:25 -07:00
7bd5454e43 Merge pull request #250 from DanielVoogsgerd/feature-arguments-logging
Feature: Command line arguments/Logging
2019-03-28 09:38:33 +01:00
8340e88795 Merge pull request #249 from DanielVoogsgerd/refactor-main_method
refactor: Add main method / Clean global scope
2019-03-28 09:38:07 +01:00
095294389b feat(Spotify): Add option to select player 2019-03-27 22:27:23 +01:00
3bcf3befec chore(Spotify): Add logging for events 2019-03-27 17:28:02 +01:00
db69b449ba feat(Spotify): Add logger 2019-03-27 16:37:59 +01:00
ed358ef024 feat(Spotify): Add argument parser 2019-03-27 16:33:15 +01:00
faa79ea216 refactor: Add main method / Clean global scope 2019-03-27 16:10:36 +01:00
b17984f75e Merge pull request #248 from ndrewtl/patch-1
Note availability in Arch community repository
2019-03-27 08:15:44 +01:00
4521db66ce Note availability in Arch community repository 2019-03-26 18:20:09 -07:00
7912b7f119 Merge pull request #245 from SibrenVasse/json-icon
[custom] Allow icon selection based on json alt attribute
2019-03-26 09:26:58 +01:00
55a6e4907b feat(custom): allow icon selection based on json alt attribute 2019-03-26 00:35:49 +01:00
dda0cc793e fix: check vertical after parsing multiple outputs 2019-03-25 21:02:00 +01:00
5144426f0e fix(workspace): scroll direction 2019-03-25 11:55:01 +01:00
98c028226d Merge pull request #241 from jlbribeiro/fix/essid-unescaped-input
Fix #240: Escape ESSID text before interpreting as pango
2019-03-24 10:28:00 +01:00
3eb901f800 Escape ESSID text before interpreting as pango
Fixes #240.
2019-03-24 03:11:54 +00:00
47142a61ae feat: allow waybar to be positioned on left/right 2019-03-22 12:25:05 +01:00
f700319d7f chore: v0.5.0 2019-03-20 10:51:40 +01:00
418767c405 Merge pull request #228 from hoellen/feat-rtsignal
Add pkill signals to custom modules
2019-03-18 19:53:02 +01:00
1f924c9c06 Merge branch 'master' into feat-rtsignal 2019-03-18 19:04:11 +01:00
38fa7ceab1 add signalhandler for module update 2019-03-18 18:46:44 +01:00
22cddc5e26 refactor(workspaces): scroll output aware 2019-03-18 14:44:07 +01:00
1ccf372f8e refactor(network): fix skipped messages 2019-03-18 11:07:36 +01:00
3257968a28 Merge pull request #222 from alebastr/pulseaudio-ci-icon-lookup
fix(pulseaudio) use case-insensitive comparison for icon lookup
2019-03-15 09:43:02 +01:00
6fc06fe9db Merge branch 'master' into pulseaudio-ci-icon-lookup 2019-03-15 09:41:40 +01:00
75afcd3ad1 Merge pull request #221 from alebastr/sway-ipc-string-assertion
fix(sway): ipc client crash when compiled with -D_GLIBCXX_ASSERTIONS
2019-03-15 09:40:41 +01:00
9ad80849b1 fix(pulseaudio): Avoid allocation of string vector on every call of getPortIcon() 2019-03-14 18:35:16 -07:00
492d151079 fix(pulseaudio) use case-insensitive comparison for icon lookup 2019-03-14 18:08:12 -07:00
00176c9514 fix(sway): ipc client crash when compiled with -D_GLIBCXX_ASSERTIONS
reserve() does not change string size and any access beyond data() + size() is UB
2019-03-14 17:53:45 -07:00
6d2dcd8ac7 fix(temperature): default thermal zone 2019-03-14 14:01:10 +01:00
7d6079bb06 style: update default temperature style/config 2019-03-13 22:39:39 +01:00
c820ed3495 Merge pull request #216 from Groggy/temperature
Add temperature module
2019-03-13 22:21:11 +01:00
0314bf70b3 feat(temperature): update README 2019-03-13 16:56:13 +01:00
7ae549dc9e Add temperature module 2019-03-13 13:35:43 +01:00
cd13180199 Merge pull request #213 from jubalh/rdmos
Add available on openSUSE
2019-03-12 11:41:05 +01:00
8d6d8e8210 Add available on openSUSE 2019-03-12 11:01:18 +01:00
2995da845d fix: config per output 2019-03-10 10:34:56 +01:00
973cec1713 feat(idle): add status class 2019-03-10 10:29:06 +01:00
94d7b083c5 fix(Pulseaudio): switch case 2019-03-08 15:30:41 +01:00
ef88f0a223 fix: clock rounding 2019-03-07 12:34:21 +01:00
ab0dcbfb2e Merge pull request #204 from Organic-Code/master
Improving mouse buttons support
2019-03-05 10:16:19 +01:00
1974766125 Merge branch 'master' into master 2019-03-05 10:15:05 +01:00
9c0c0d262e Using "inclusive or" for format-alt-click and other click events
c.f. https://github.com/Alexays/Waybar/pull/204#discussion_r262009635

Co-Authored-By: Organic-Code <Lazarelucas@yahoo.fr>
2019-03-04 15:00:44 -05:00
768e57dd37 Merge pull request #208 from jonfin/idle_inhibitor_bugfix
[bugfix] idle_inhibitor: handle click events correctly
2019-03-04 11:20:23 +01:00
67c756b28e Merge branch 'master' into idle_inhibitor_bugfix 2019-03-04 11:19:14 +01:00
584750d0fe Merge pull request #207 from jonfin/tooltip-network
Add custom tooltip format for network module
2019-03-04 11:17:52 +01:00
80ef63791d [bugfix] idle_inhibitor handles click events correctly
- Declare event handler in ALabel virtual so the idle_inhibitor can
  overriding them
- Handle the right click event in idle_inhibitor and call ALabel handler if needed
2019-03-03 22:02:34 +01:00
a9f680b06e Add custom tooltip format for network module 2019-03-03 21:35:35 +01:00
7e4fed8218 Merge pull request #206 from dikeert/issue/205-add-option-to-show-current-workspace-only
resolves #205
2019-03-03 18:04:03 +01:00
737da3615f resolves #205 2019-03-03 21:35:32 +11:00
d0f56b7bdd Improving mouse buttons support
Adding support for middle, backward, and forward mouse buttons click events, adds config keys : "on-click-middle", "on-click-forward" and "on-click-backward"
Adding the key "format-alt-click" to choose what mouse clicks toggles the alternative format, when present. Possible values (in config): "click-right", "click-left", "click-middle", "click-forward", "click-backward". Other values have the same effect than "click-left". Previous behaviour was to toggle it whenever any click was registered and any click that was not handled by "on-click-right" or "on-click-left" occurred
2019-03-02 14:07:12 -05:00
f47492c371 chore: v0.4.0 2019-03-01 17:12:02 +01:00
79b0367e6c feat(custom): escape option 2019-03-01 17:03:01 +01:00
2c411b9848 Merge pull request #199 from ianhattendorf/fix/backlight-disable-not-found
Disable backlight module if no backlights found
2019-03-01 09:31:49 +01:00
ac6d833d4d Ignore all potential build directories
This is useful when using multiple build directories, eg:
- build-debug
- build-release
- build-sanitize
2019-02-28 19:57:49 -07:00
39de8e544c Disable backlight module if no backlight found 2019-02-28 19:50:57 -07:00
0271e9bc1a Revert "fix: Escape user controlled input"
This reverts commit 7247360e29.
2019-02-28 19:30:27 +01:00
470f539346 Merge pull request #193 from Robinhuett/custom_alt
feat(custom): Add field for additional data to json
2019-02-26 12:18:47 +01:00
6633e34bbd Merge branch 'master' into custom_alt 2019-02-26 12:18:34 +01:00
4b3725ec31 Merge pull request #194 from ianhattendorf/fix/backlight-crash-unplug-monitor
Fix crash when monitor unplugged
2019-02-26 12:18:19 +01:00
c1295c8fd6 Fix crash when monitor unplugged
`Backlight#devices_` was being destructed before
`Backlight#udev_thread_`. Also check if thread is still running after
`epoll_wait`
2019-02-25 20:05:44 -07:00
39bf403505 feat(custom): Add field for additional data to json 2019-02-25 22:04:09 +01:00
4499a23e51 Merge pull request #192 from DutchMofo/clock-tooltip
Fix tooltip format
2019-02-25 20:39:31 +01:00
821a009c32 Fix tooltip format 2019-02-25 19:25:19 +01:00
961a05bedf Merge pull request #191 from Robinhuett/escape_userinput
fix: Escape user controlled input
2019-02-25 18:46:17 +01:00
7247360e29 fix: Escape user controlled input 2019-02-25 18:28:56 +01:00
abfa428dab Merge pull request #190 from Robinhuett/fix_backlight_fmtalt
fix(backlight) Allow format-alt
2019-02-25 10:52:42 +01:00
63e97df9ff fix(backlight) Allow format-alt 2019-02-24 22:15:41 +01:00
5a7f801922 Merge pull request #189 from DutchMofo/clock-tooltip
Added clock tooltip with optional alternate format
2019-02-24 12:10:01 +01:00
8fa30f8ce3 Forgot comma in json 2019-02-24 11:54:59 +01:00
da4661f97c Removed clock tooltip from example config 2019-02-24 11:49:58 +01:00
7245fb5d0a Added clock tooltip with optional alternate format 2019-02-24 09:25:34 +01:00
b4f36436c3 style: update idle inhibitor color 2019-02-22 17:34:13 +01:00
eb7e265eb1 Merge pull request #187 from jonfin/idle_inhibitor
Add module Idle inhibitor
2019-02-22 17:31:51 +01:00
c2ed0cb832 Add the possibility to disable the tooltip 2019-02-22 16:58:36 +01:00
d708ce2be9 Add idle inhibitor module 2019-02-22 16:55:46 +01:00
83a6475510 feat: can disable tooltip 2019-02-22 11:35:47 +01:00
331b28393a Merge pull request #181 from ianhattendorf/feature/module-brightness
Add backlight module
2019-02-21 16:04:32 +01:00
afbf2de1b9 Merge branch 'master' into feature/module-brightness 2019-02-21 15:29:17 +01:00
e67347f6ad Gate backlight module behind libudev availability 2019-02-18 21:11:18 -07:00
bef8520937 Remove opensuse ci for now 2019-02-18 12:33:40 +01:00
8bfcb106c0 style: comment about workspace hover effect 2019-02-18 12:30:41 +01:00
875306804c Add backlight module
Monitor the backlight level via udev. Poll every `interval` as well,
in case backlight udev events aren't supported.
2019-02-17 15:29:49 -07:00
6bf4f65228 fix: two finger scroll 2019-02-16 09:56:53 +01:00
0c9edb0c4b fix(custom): check exist_status in endless script before clean the output 2019-02-16 09:48:27 +01:00
3e18673451 fix: remove bluetooth class on else 2019-02-12 17:55:50 +01:00
d0370acb21 refactor(network): better network disconnection 2019-02-11 19:06:39 +01:00
aeec80f375 fix(Tray): big icon 2019-02-07 13:34:30 +01:00
dc9fe04d11 refactor: add retry to get external interface 2019-02-06 10:33:12 +01:00
01cec9fcb7 fix(bar): multi screens 2019-02-04 22:09:01 +01:00
6fb25ade7e chore: update opensuse Dockerfile 2019-02-04 11:20:18 +01:00
e10e9554ab Update README.md 2019-02-04 10:32:16 +01:00
93173851d3 fix(Workspaces): index 2019-02-02 12:07:59 +01:00
add9e925f4 fix(Workspaces): button order 2019-02-02 00:36:52 +01:00
60af51fc17 chore: update opensuse dockerfile 2019-02-01 21:56:34 +01:00
3021ef2d51 Update README.md 2019-02-01 21:52:38 +01:00
0ddcf26a45 feat: output configuration 2019-02-01 21:45:59 +01:00
4d3c2191cb chore: v0.3.0 2019-01-28 19:38:58 +01:00
38fc62ea27 feat: bar id 2019-01-28 19:26:16 +01:00
2f7b2677d7 Merge pull request #155 from Robinhuett/window_ignore_self_fix
fix(window): Fix #154
2019-01-27 12:06:58 +01:00
6d03d9f6a9 fix(window): Fix #154 2019-01-27 11:59:07 +01:00
6f2d784d4e Merge pull request #154 from Robinhuett/window_ignore_self
fix(window): Ignore waybar
2019-01-27 02:09:44 +01:00
ce6816737c fix(window): Ignore waybar
With this, the window title waybar will be ignored so it wont flicker as decribed in #148
2019-01-26 19:02:11 +01:00
a0fd99b112 Merge pull request #152 from jubalh/fmt
Add fmt dependency to README.md
2019-01-25 16:42:41 +01:00
12a48b70ae Add fmt dependency to README.md 2019-01-25 16:38:38 +01:00
aedf133b16 Merge pull request #146 from Alexays/network_alt
fix(Network): format-alt
2019-01-14 09:05:52 +01:00
02aed73295 Merge branch 'master' into network_alt 2019-01-13 22:39:04 +01:00
9348e88592 Merge pull request #130 from Alexays/destructor
refactor: proper modules destruction
2019-01-13 22:37:19 +01:00
eace8c549f fix(Network): format-alt 2019-01-13 22:36:37 +01:00
f8116132a7 fix(Workspaces): check thread is running before parse response 2019-01-13 22:23:09 +01:00
171e0e5ae3 revert: don't disable seq check 2019-01-13 22:22:22 +01:00
30781757e3 revert: prefer nl_send_sync 2019-01-13 22:22:22 +01:00
800d2f388e refactor(network): proper signal strength type 2019-01-13 22:22:22 +01:00
1647e31b48 refactor: free netlink message 2019-01-13 22:22:22 +01:00
399f61df98 refactor: proper modules destruction 2019-01-13 22:22:22 +01:00
8af813c2f1 Merge pull request #140 from Robinhuett/headers
refactor: replace all gtkmm.h includes
2019-01-09 01:00:00 +01:00
29a2ee1744 refactor: Replace all occurencec of gtkmm.h and only use the necessary headers 2019-01-08 21:05:44 +01:00
afa1cc8287 Update README.md 2018-12-29 00:06:59 +01:00
ab78698ffd Merge pull request #139 from cjbassi/master
Remove ws index from sway ws names
2018-12-28 18:45:58 +01:00
8ea0659ee2 Remove ws index from sway ws names 2018-12-28 09:36:02 -08:00
aa6da11ba4 Merge pull request #138 from cjbassi/master
Add install and build-debug commands to makefile
2018-12-28 15:58:51 +01:00
6a5a4881ad Add install and build-debug commands to makefile 2018-12-28 06:37:07 -08:00
3a9bf932b4 Merge pull request #133 from cjbassi/feature/makefile
Add simple makefile
2018-12-28 01:23:23 +01:00
a078be991a Merge branch 'master' into feature/makefile 2018-12-28 01:16:56 +01:00
83ae5ec5c9 Merge pull request #134 from cjbassi/fix/typo
Fix typo
2018-12-28 01:16:31 +01:00
b4d38294a7 Fix typo 2018-12-27 16:03:29 -08:00
7804514c5c Add simple makefile 2018-12-27 15:56:47 -08:00
513278597a Merge pull request #129 from Robinhuett/custom-module-icon
Add format-icons to custom module
2018-12-26 11:42:25 +01:00
4698c9d2cf chore(custom): Change int conversion 2018-12-26 11:35:58 +01:00
11c98f13e3 feat(custom): Add format-icons to custom module
This commit allows custom modules (json only) to set a percentage. This can be displayed either by using {percentage} or by using {icon} with format-icons set.
2018-12-26 03:52:05 +01:00
76bbdd0425 Merge pull request #128 from Robinhuett/wifiicon
Use SignalStrength for format icons
2018-12-25 21:07:25 +01:00
34df2b0695 fix(ALabel): Better fix for getIcon 2018-12-25 21:03:13 +01:00
ad638357b5 feat(network): Use Signal Strength for format-icons 2018-12-25 14:17:34 +01:00
7404f80122 fix(ALabel): getIcon 2018-12-25 14:15:59 +01:00
de0ee9fcb2 fix(battery): adapter status 2018-12-24 12:17:07 +01:00
755fad6bc3 fix(battery): typo 2018-12-24 08:50:58 +01:00
e3c0624c48 fix(battery): typo 2018-12-24 08:38:37 +01:00
87e55ea993 feat(battery): check adapter online as fallback when battery status report unknown 2018-12-24 08:37:10 +01:00
3b8bfb08a4 fix(network): typo 2018-12-22 14:00:56 +01:00
00728fe877 Merge pull request #125 from chep/master
fix(pulseaudio): return puleseaudio port string if unknown
2018-12-20 10:24:55 +01:00
a4062455cd fix(pulseaudio): return puleseaudio port string if unknown 2018-12-20 09:54:10 +01:00
3691d84543 chore: v0.2.3 2018-12-18 17:42:35 +01:00
b554094c7e feat: args && class id 2018-12-18 17:30:54 +01:00
66ad2864c2 fix(Pulseaudio): use markup 2018-12-17 09:00:40 +01:00
c7b0639f32 fix(workspaces): check thread is running 2018-12-09 10:49:28 +01:00
0acc50264e fix(custom): failed getline after closed pipe 2018-12-08 13:57:56 +01:00
1b13f9e38c fix(custom): close endless scripts 2018-12-08 12:58:47 +01:00
7befd27059 fix: clock interval 2018-12-04 09:38:08 +01:00
2ec34e4adb feat: add $HOME to valid path 2018-12-03 20:12:34 +01:00
812a7a9861 fix: typo 2018-12-03 11:13:56 +01:00
1d96d57b75 feat: warn user about stopped endless custom module 2018-12-03 09:35:10 +01:00
691fb88057 refactor(modules): const bar 2018-12-01 00:10:41 +01:00
281bab4943 feat(ci): opensuse 2018-11-30 21:48:42 +01:00
8e93fd3ae0 revert: Update to new Sway IPC protocol 2018-11-29 09:50:00 +01:00
37c7e586d6 Merge pull request #110 from tokyovigilante/ipc-fix
Update to new Sway IPC protocol
2018-11-29 00:40:57 +01:00
3a5a470d90 Update to new Sway IPC protocol 2018-11-28 22:27:06 +00:00
c051b517ca Merge pull request #108 from SibrenVasse/spotify
fix(custom): fix conditional statement
2018-11-26 20:09:21 +01:00
a0cdef569b fix(custom): fix conditional statement 2018-11-26 19:35:03 +01:00
a123711a8d fix(network): disconnected forced interface 2018-11-25 21:32:50 +01:00
391a7de94a fix(window): escape window name 2018-11-25 15:21:00 +01:00
952d877652 Merge pull request #103 from SibrenVasse/spotify
Event based mediaplayer script
2018-11-24 18:13:08 +01:00
5356d25a9b Merge branch 'master' into spotify 2018-11-24 18:11:48 +01:00
65b9911df4 Merge pull request #105 from David96/master
Allow custom plugins that don't execute anything
2018-11-24 18:01:31 +01:00
2aed121903 Allow custom plugins that don't execute anything 2018-11-24 17:24:02 +01:00
08bfdda4cb revert(network): wait interface 2018-11-24 16:01:22 +01:00
8f8ec3b999 feat(Label): on-click-right 2018-11-24 15:56:16 +01:00
a17220054d feat(client): pefix module name on update error 2018-11-24 11:20:03 +01:00
4cc48b3cfd fix(client): catch error on update 2018-11-24 11:13:52 +01:00
087de4e956 refactor(client): lambda to method 2018-11-24 11:04:56 +01:00
2c2a0473f4 feat(client): throw when we don't have required resources files 2018-11-23 19:31:40 +01:00
2e1f8b2fc5 fix(network): check len of netlinkResponse 2018-11-23 17:52:10 +01:00
686bc4828e refactor(network): only get info when there is an interface 2018-11-23 17:46:14 +01:00
f6c2a8d9b7 fix(network): free the message instead of the socket 2018-11-23 17:42:26 +01:00
baa7f52e21 refactor(network): wait for new address 2018-11-23 16:04:29 +01:00
c4c0c01a2f feat(custom): event based mediaplayer script 2018-11-23 12:08:15 +01:00
2b05b8e69a chore: v0.2.2 2018-11-23 12:03:23 +01:00
ad7400d5ce refactor(ALabel): add interval 2018-11-23 11:57:37 +01:00
36652158ad refactor(tray): more cpp binding 2018-11-23 10:46:58 +01:00
8db94f7efd revert(ALabel): ref on format 2018-11-22 16:50:42 +01:00
aaaa17d94f fix(config): remove duplicate tray 2018-11-22 16:21:46 +01:00
0b1b0eb1a7 feat(tray): multiple hosts 2018-11-22 16:20:49 +01:00
f00be0b552 refactor(tray): remove useless variable 2018-11-22 15:51:55 +01:00
ba79b4d397 refactor(tray): cleanup and fixes 2018-11-22 15:47:23 +01:00
bd62c4cbc5 feat(Label): use set_markup 2018-11-21 20:49:09 +01:00
b3559328f1 fix(window): ellipsize 2018-11-20 23:24:33 +01:00
69fceb1c92 fix(tray): sigsev on click when multiple icons is available 2018-11-16 12:01:03 +01:00
76 changed files with 5571 additions and 2399 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

2
.gitignore vendored
View File

@ -5,7 +5,7 @@ vgcore.*
*.swp *.swp
packagecache packagecache
/subprojects/**/ /subprojects/**/
/build /build*
/dist /dist
/meson.egg-info /meson.egg-info

View File

@ -6,6 +6,7 @@ services:
env: env:
- distro: debian - distro: debian
- distro: archlinux - distro: archlinux
# - distro: opensuse
before_install: before_install:
- docker pull alexays/waybar:${distro} - docker pull alexays/waybar:${distro}

5
Dockerfiles/opensuse Normal file
View File

@ -0,0 +1,5 @@
FROM opensuse/tumbleweed:latest
RUN zypper -n up && \
zypper -n install -t pattern devel_C_C++ && \
zypper -n install git meson clang libinput10 libinput-devel libwayland-client0 libwayland-cursor0 wayland-protocols-devel wayland-devel Mesa-libEGL-devel Mesa-libGLESv2-devel libgbm-devel libxkbcommon-devel libudev-devel libpixman-1-0-devel gtkmm3-devel jsoncpp-devel

20
Makefile Normal file
View File

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

View File

@ -1,22 +1,25 @@
# Waybar [![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)<br>![Waybar](https://raw.githubusercontent.com/alexays/waybar/master/preview-2.png) # Waybar [![Travis](https://travis-ci.org/Alexays/Waybar.svg?branch=master)](https://travis-ci.org/Alexays/Waybar) [![Licence](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) [![Paypal Donate](https://img.shields.io/badge/Donate-Paypal-2244dd.svg)](https://paypal.me/ARouillard)<br>![Waybar](https://raw.githubusercontent.com/alexays/waybar/master/preview-2.png)
**Proof of concept**
> Highly customizable Wayland bar for Sway and Wlroots based compositors.<br> > Highly customizable Wayland bar for Sway and Wlroots based compositors.<br>
> Available on [AUR](https://aur.archlinux.org/packages/waybar-git/) > Available in Arch [community](https://www.archlinux.org/packages/community/x86_64/waybar/) or
[AUR](https://aur.archlinux.org/packages/waybar-git/) and [openSUSE](https://build.opensuse.org/package/show/X11:Wayland/waybar)
**Current features** **Current features**
- Sway (Workspaces, Binding mode, Focused window name) - Sway (Workspaces, Binding mode, Focused window name)
- Tray (Beta) [#21](https://github.com/Alexays/Waybar/issues/21) - Tray [#21](https://github.com/Alexays/Waybar/issues/21)
- Local time - Local time
- Battery - Battery
- Network - Network
- Pulseaudio - Pulseaudio
- Memory - Memory
- Cpu load average - Cpu load average
- Temperature
- MPD
- Custom scripts - Custom scripts
- Multiple output configuration
- And much more customizations - And much more customizations
**Configuration and Customization** **Configuration and Styling**
[See the wiki for more details](https://github.com/Alexays/Waybar/wiki). [See the wiki for more details](https://github.com/Alexays/Waybar/wiki).
@ -28,6 +31,26 @@ $ cd Waybar
$ meson build $ meson build
$ ninja -C build $ ninja -C build
$ ./build/waybar $ ./build/waybar
# If you want to install it
$ ninja -C build install
$ waybar
```
**Dependencies**
```
gtkmm3
jsoncpp
libinput
libsigc++
fmt
wayland
wlroots
libpulse [Pulseaudio module]
libnl [Network module]
sway [Sway modules]
libdbusmenu-gtk3 [Tray module]
libmpdclient [MPD module]
``` ```
Contributions welcome! - have fun :)<br> Contributions welcome! - have fun :)<br>

View File

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

View File

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

View File

@ -1,59 +1,71 @@
#pragma once #pragma once
#include <glibmm/refptr.h>
#include <gtkmm/cssprovider.h>
#include <gtkmm/main.h>
#include <gtkmm/window.h>
#include <json/json.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 "wlr-layer-shell-unstable-v1-client-protocol.h"
#include "xdg-output-unstable-v1-client-protocol.h" #include "xdg-output-unstable-v1-client-protocol.h"
#include "IModule.hpp"
namespace waybar { namespace waybar {
class Client;
class Factory; class Factory;
struct waybar_output {
class Bar { struct wl_output * output;
public: std::string name;
Bar(const Client&, std::unique_ptr<struct wl_output *>&&, uint32_t); uint32_t wl_name;
Bar(const Bar&) = delete; struct zxdg_output_v1 *xdg_output;
auto toggle() -> void;
const Client& client;
Gtk::Window window;
struct wl_surface *surface;
struct zwlr_layer_surface_v1 *layer_surface;
std::unique_ptr<struct wl_output *> output;
std::string output_name;
uint32_t wl_name;
bool visible = true;
private:
static void handleLogicalPosition(void *, struct zxdg_output_v1 *, int32_t,
int32_t);
static void handleLogicalSize(void *, struct zxdg_output_v1 *, int32_t,
int32_t);
static void handleDone(void *, struct zxdg_output_v1 *);
static void handleName(void *, struct zxdg_output_v1 *, const char *);
static void handleDescription(void *, struct zxdg_output_v1 *,
const char *);
static void layerSurfaceHandleConfigure(void *,
struct zwlr_layer_surface_v1 *, uint32_t, uint32_t, uint32_t);
static void layerSurfaceHandleClosed(void *,
struct zwlr_layer_surface_v1 *);
auto setupConfig() -> void;
auto setupWidgets() -> void;
auto setupCss() -> void;
void getModules(const Factory&, const std::string&);
uint32_t width_ = 0;
uint32_t height_ = 30;
Json::Value config_;
Glib::RefPtr<Gtk::StyleContext> style_context_;
Glib::RefPtr<Gtk::CssProvider> css_provider_;
struct zxdg_output_v1 *xdg_output_;
std::vector<std::unique_ptr<waybar::IModule>> modules_left_;
std::vector<std::unique_ptr<waybar::IModule>> modules_center_;
std::vector<std::unique_ptr<waybar::IModule>> modules_right_;
}; };
} 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,39 +1,56 @@
#pragma once #pragma once
#include <unistd.h>
#include <wordexp.h>
#include <fmt/format.h> #include <fmt/format.h>
#include <gdk/gdk.h> #include <gdk/gdk.h>
#include <wayland-client.h>
#include <gdk/gdkwayland.h> #include <gdk/gdkwayland.h>
#include <unistd.h>
#include <wayland-client.h>
#include <wordexp.h>
#include "bar.hpp" #include "bar.hpp"
namespace waybar { namespace waybar {
class Client { class Client {
public: public:
Client(int argc, char *argv[]); static Client *inst();
int main(int argc, char *argv[]); int main(int argc, char *argv[]);
Glib::RefPtr<Gtk::Application> gtk_app; Glib::RefPtr<Gtk::Application> gtk_app;
std::string css_file; Glib::RefPtr<Gdk::Display> gdk_display;
std::string config_file; struct wl_display * wl_display = nullptr;
Glib::RefPtr<Gdk::Display> gdk_display; struct wl_registry * registry = nullptr;
struct wl_display *wl_display = nullptr; struct zwlr_layer_shell_v1 * layer_shell = nullptr;
struct wl_registry *registry = nullptr; struct zxdg_output_manager_v1 * xdg_output_manager = nullptr;
struct zwlr_layer_shell_v1 *layer_shell = nullptr; struct zwp_idle_inhibit_manager_v1 *idle_inhibit_manager = nullptr;
struct zxdg_output_manager_v1 *xdg_output_manager = nullptr; std::vector<std::unique_ptr<Bar>> bars;
struct wl_seat *seat = nullptr;
std::vector<std::unique_ptr<Bar>> bars;
private: private:
void bindInterfaces(); Client() = default;
auto setupCss(); 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, static void handleGlobal(void *data, struct wl_registry *registry, uint32_t name,
uint32_t name, const char *interface, uint32_t version); const char *interface, uint32_t version);
static void handleGlobalRemove(void *data, static void handleGlobalRemove(void *data, struct wl_registry *registry, uint32_t name);
struct wl_registry *registry, uint32_t name); static void handleLogicalPosition(void *, struct zxdg_output_v1 *, int32_t, int32_t);
static void handleLogicalSize(void *, struct zxdg_output_v1 *, int32_t, int32_t);
static void handleDone(void *, struct zxdg_output_v1 *);
static void handleName(void *, struct zxdg_output_v1 *, const char *);
static void handleDescription(void *, struct zxdg_output_v1 *, const char *);
Json::Value config_;
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

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

@ -9,31 +9,31 @@
namespace waybar::modules { namespace waybar::modules {
class Pulseaudio : public ALabel { class Pulseaudio : public ALabel {
public: public:
Pulseaudio(const Json::Value&); Pulseaudio(const std::string&, const Json::Value&);
~Pulseaudio(); ~Pulseaudio();
auto update() -> void; auto update() -> void;
private:
static void subscribeCb(pa_context*, pa_subscription_event_type_t,
uint32_t, void*);
static void contextStateCb(pa_context*, void*);
static void sinkInfoCb(pa_context*, const pa_sink_info*, int, void*);
static void serverInfoCb(pa_context*, const pa_server_info*, void*);
static void volumeModifyCb(pa_context*, int, void*);
bool handleScroll(GdkEventScroll* e);
const std::string getPortIcon() const; private:
static void subscribeCb(pa_context*, pa_subscription_event_type_t, uint32_t, void*);
static void contextStateCb(pa_context*, void*);
static void sinkInfoCb(pa_context*, const pa_sink_info*, int, void*);
static void serverInfoCb(pa_context*, const pa_server_info*, void*);
static void volumeModifyCb(pa_context*, int, void*);
bool handleScroll(GdkEventScroll* e);
pa_threaded_mainloop* mainloop_; const std::string getPortIcon() const;
pa_mainloop_api* mainloop_api_;
pa_context* context_; pa_threaded_mainloop* mainloop_;
uint32_t sink_idx_{0}; pa_mainloop_api* mainloop_api_;
uint16_t volume_; pa_context* context_;
pa_cvolume pa_volume_; uint32_t sink_idx_{0};
bool muted_; uint16_t volume_;
std::string port_name_; pa_cvolume pa_volume_;
std::string desc_; bool muted_;
bool scrolling_; std::string port_name_;
std::string desc_;
bool scrolling_;
}; };
} // namespace waybar::modules } // 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

@ -1,38 +0,0 @@
#pragma once
#include <gtkmm.h>
#include <json/json.h>
#include <tuple>
#include <dbus-status-notifier-watcher.h>
#include "modules/sni/sni.hpp"
namespace waybar::modules::SNI {
class Host {
public:
Host(Glib::Dispatcher*, const Json::Value&);
std::vector<Item> items;
private:
static void busAcquired(GDBusConnection*, const gchar*, gpointer);
static void nameAppeared(GDBusConnection*, const gchar*, const gchar*,
gpointer);
static void nameVanished(GDBusConnection*, const gchar*, gpointer);
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 gchar*);
void addRegisteredItem(const gchar* service);
uint32_t bus_name_id_;
uint32_t watcher_id_;
std::string bus_name_;
std::string object_path_;
Glib::Dispatcher* dp_;
GCancellable* cancellable_ = nullptr;
SnWatcher* watcher_ = nullptr;
const Json::Value &config_;
};
}

View File

@ -1,59 +0,0 @@
#pragma once
#include <dbus-status-notifier-item.h>
#include <gtkmm.h>
#include <json/json.h>
#ifdef FILESYSTEM_EXPERIMENTAL
#include <experimental/filesystem>
#else
#include <filesystem>
#endif
namespace waybar::modules::SNI {
class Item {
public:
Item(std::string, std::string, Glib::Dispatcher*, Json::Value);
std::string bus_name;
std::string object_path;
Gtk::EventBox event_box;
int icon_size;
int effective_icon_size;
Gtk::Image *image;
Gtk::Menu *gtk_menu = nullptr;
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;
std::string overlay_icon_name;
std::string attention_icon_name;
std::string attention_movie_name;
std::string icon_theme_path;
std::string menu;
bool item_is_menu;
private:
static void proxyReady(GObject *obj, GAsyncResult *res, gpointer data);
static void getAll(GObject *obj, GAsyncResult *res, gpointer data);
static void handleActivate(GObject *, GAsyncResult *, gpointer);
static void handleSecondaryActivate(GObject *, GAsyncResult *, gpointer);
void updateImage();
void updateMenu();
Glib::RefPtr<Gdk::Pixbuf> extractPixBuf(GVariant *variant);
Glib::RefPtr<Gdk::Pixbuf> getIconByName(std::string name, int size);
bool handleClick(GdkEventButton *const & /*ev*/);
Glib::Dispatcher *dp_;
GCancellable *cancellable_ = nullptr;
SnItem *proxy_ = nullptr;
Json::Value config_;
};
} // namespace waybar::modules::SNI

View File

@ -1,47 +0,0 @@
#pragma once
#include <gtkmm.h>
#include <dbus-status-notifier-watcher.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;
static void busAcquired(GDBusConnection *, const gchar *, gpointer);
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);
void updateRegisteredItems(SnWatcher *obj);
uint32_t bus_name_id_;
uint32_t watcher_id_;
GSList *hosts_ = nullptr;
GSList *items_ = nullptr;
SnWatcher *watcher_ = nullptr;
};
} // namespace waybar::modules::SNI

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

@ -1,38 +1,45 @@
#pragma once #pragma once
#include <fmt/format.h> #include <fmt/format.h>
#include <gtkmm/button.h>
#include <gtkmm/label.h>
#include "IModule.hpp"
#include "bar.hpp" #include "bar.hpp"
#include "client.hpp" #include "client.hpp"
#include "util/chrono.hpp"
#include "util/json.hpp"
#include "IModule.hpp"
#include "modules/sway/ipc/client.hpp" #include "modules/sway/ipc/client.hpp"
#include "util/sleeper_thread.hpp"
namespace waybar::modules::sway { namespace waybar::modules::sway {
class Workspaces : public IModule { class Workspaces : public IModule {
public: public:
Workspaces(waybar::Bar&, const Json::Value&); Workspaces(const std::string&, const waybar::Bar&, const Json::Value&);
auto update() -> void; ~Workspaces() = default;
operator Gtk::Widget &(); auto update() -> void;
private: operator Gtk::Widget&();
void worker();
void addWorkspace(Json::Value);
std::string getIcon(std::string, Json::Value);
bool handleScroll(GdkEventScroll*);
std::string getPrevWorkspace();
std::string getNextWorkspace();
Bar& bar_; private:
const Json::Value& config_; void onCmd(const struct Ipc::ipc_response&);
waybar::util::SleeperThread thread_; void onEvent(const struct Ipc::ipc_response&);
Gtk::Box box_; void worker();
util::JsonParser parser_; bool filterButtons();
std::mutex mutex_; Gtk::Button& addButton(const Json::Value&);
bool scrolling_; void onButtonReady(const Json::Value&, Gtk::Button&);
std::unordered_map<std::string, Gtk::Button> buttons_; std::string getIcon(const std::string&, const Json::Value&);
Json::Value workspaces_; bool handleScroll(GdkEventScroll*);
Ipc ipc_; 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,94 +0,0 @@
#pragma once
#include <chrono>
#include <ctime>
#include <functional>
#include <condition_variable>
#include <thread>
#include <gtkmm.h>
namespace waybar::chrono {
using namespace std::chrono;
using clock = std::chrono::system_clock;
using duration = clock::duration;
using time_point = std::chrono::time_point<clock, duration>;
}
namespace waybar::util {
struct SleeperThread {
SleeperThread() = default;
SleeperThread(std::function<void()> func)
: thread_{[this, func] {
while(true) {
{
std::lock_guard<std::mutex> lock(mutex_);
if (!do_run_) {
break;
}
}
func();
}
}}
{}
SleeperThread& operator=(std::function<void()> func)
{
thread_ = std::thread([this, func] {
while(true) {
{
std::lock_guard<std::mutex> lock(mutex_);
if (!do_run_) {
break;
}
}
func();
}
});
return *this;
}
auto sleep_for(chrono::duration dur)
{
auto lock = std::unique_lock(mutex_);
return condvar_.wait_for(lock, dur);
}
auto sleep_until(chrono::time_point time)
{
auto lock = std::unique_lock(mutex_);
return condvar_.wait_until(lock, time);
}
auto wake_up()
{
condvar_.notify_all();
}
auto stop()
{
do_run_ = false;
condvar_.notify_all();
if (thread_.joinable()) {
thread_.detach();
}
}
~SleeperThread()
{
stop();
}
private:
std::thread thread_;
std::condition_variable condvar_;
std::mutex mutex_;
bool do_run_ = true;
};
}

1264
include/util/clara.hpp Normal file

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -5,27 +5,24 @@
namespace waybar::util { namespace waybar::util {
struct JsonParser { struct JsonParser {
JsonParser() : reader_(builder_.newCharReader()) {}
JsonParser() const Json::Value parse(const std::string& data) const {
: reader_(builder_.newCharReader()) Json::Value root(Json::objectValue);
{} if (data.empty()) {
return root;
const Json::Value parse(const std::string data) const }
{
Json::Value root;
std::string err; std::string err;
bool res = bool res = reader_->parse(data.c_str(), data.c_str() + data.size(), &root, &err);
reader_->parse(data.c_str(), data.c_str() + data.size(), &root, &err); if (!res) throw std::runtime_error(err);
if (!res)
throw std::runtime_error(err);
return root; return root;
} }
~JsonParser() = default; ~JsonParser() = default;
private: private:
Json::CharReaderBuilder builder_; Json::CharReaderBuilder builder_;
std::unique_ptr<Json::CharReader> const reader_; 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,6 +1,6 @@
project( project(
'waybar', 'cpp', 'c', 'waybar', 'cpp', 'c',
version: '0.2.1', version: '0.6.1',
license: 'MIT', license: 'MIT',
default_options : [ default_options : [
'cpp_std=c++17', 'cpp_std=c++17',
@ -22,6 +22,16 @@ else
endif endif
compiler = meson.get_compiler('cpp') 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') if not compiler.has_header('filesystem')
add_project_arguments('-DFILESYSTEM_EXPERIMENTAL', language: 'cpp') add_project_arguments('-DFILESYSTEM_EXPERIMENTAL', language: 'cpp')
@ -32,7 +42,7 @@ add_global_link_arguments(cpp_link_args, language : 'cpp')
thread_dep = dependency('threads') thread_dep = dependency('threads')
libinput = dependency('libinput') libinput = dependency('libinput')
fmt = dependency('fmt', version : ['>=5.2.1'], fallback : ['fmt', 'fmt_dep']) fmt = dependency('fmt', version : ['>=5.3.0'], fallback : ['fmt', 'fmt_dep'])
wayland_client = dependency('wayland-client') wayland_client = dependency('wayland-client')
wayland_cursor = dependency('wayland-cursor') wayland_cursor = dependency('wayland-cursor')
wayland_protos = dependency('wayland-protocols') wayland_protos = dependency('wayland-protocols')
@ -45,6 +55,8 @@ sigcpp = dependency('sigc++-2.0')
libnl = dependency('libnl-3.0', required: get_option('libnl')) libnl = dependency('libnl-3.0', required: get_option('libnl'))
libnlgen = dependency('libnl-genl-3.0', required: get_option('libnl')) libnlgen = dependency('libnl-genl-3.0', required: get_option('libnl'))
libpulse = dependency('libpulse', required: get_option('pulseaudio')) libpulse = dependency('libpulse', required: get_option('pulseaudio'))
libudev = dependency('libudev', required: get_option('libudev'))
libmpdclient = dependency('libmpdclient', required: get_option('mpd'))
src_files = files( src_files = files(
'src/factory.cpp', 'src/factory.cpp',
@ -54,12 +66,14 @@ src_files = files(
'src/modules/clock.cpp', 'src/modules/clock.cpp',
'src/modules/custom.cpp', 'src/modules/custom.cpp',
'src/modules/cpu.cpp', 'src/modules/cpu.cpp',
'src/modules/idle_inhibitor.cpp',
'src/modules/temperature.cpp',
'src/main.cpp', 'src/main.cpp',
'src/bar.cpp', 'src/bar.cpp',
'src/client.cpp' 'src/client.cpp'
) )
if find_program('sway', required : false).found() if true # find_program('sway', required : false).found()
add_project_arguments('-DHAVE_SWAY', language: 'cpp') add_project_arguments('-DHAVE_SWAY', language: 'cpp')
src_files += [ src_files += [
'src/modules/sway/ipc/client.cpp', 'src/modules/sway/ipc/client.cpp',
@ -83,12 +97,22 @@ if dbusmenu_gtk.found()
add_project_arguments('-DHAVE_DBUSMENU', language: 'cpp') add_project_arguments('-DHAVE_DBUSMENU', language: 'cpp')
src_files += files( src_files += files(
'src/modules/sni/tray.cpp', 'src/modules/sni/tray.cpp',
'src/modules/sni/snw.cpp', 'src/modules/sni/watcher.cpp',
'src/modules/sni/snh.cpp', 'src/modules/sni/host.cpp',
'src/modules/sni/sni.cpp' 'src/modules/sni/item.cpp'
) )
endif 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') subdir('protocol')
executable( executable(
@ -109,7 +133,9 @@ executable(
giounix, giounix,
libnl, libnl,
libnlgen, libnlgen,
libpulse libpulse,
libudev,
libmpdclient
], ],
include_directories: [include_directories('include')], include_directories: [include_directories('include')],
install: true, install: true,

View File

@ -1,4 +1,6 @@
option('libnl', type: 'feature', value: 'auto', description: 'Enable libnl support for network related features') option('libnl', type: 'feature', value: 'auto', description: 'Enable libnl support for network related features')
option('libudev', type: 'feature', value: 'auto', description: 'Enable libudev support for udev related features')
option('pulseaudio', type: 'feature', value: 'auto', description: 'Enable support for pulseaudio') option('pulseaudio', type: 'feature', value: 'auto', description: 'Enable support for pulseaudio')
option('dbusmenu-gtk', type: 'feature', value: 'auto', description: 'Enable support for tray') option('dbusmenu-gtk', type: 'feature', value: 'auto', description: 'Enable support for tray')
option('mpd', type: 'feature', value: 'auto', description: 'Enable support for the Music Player Daemon')
option('out', type: 'string', value : '/', description: 'output prefix directory') option('out', type: 'string', value : '/', description: 'output prefix directory')

View File

@ -1,78 +1,47 @@
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" <!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node> <node>
<interface name="org.kde.StatusNotifierItem"> <interface name='org.kde.StatusNotifierItem'>
<annotation name="org.gtk.GDBus.C.Name" value="Item" /> <annotation name="org.gtk.GDBus.C.Name" value="Item" />
<property name="Category" type="s" access="read"/> <method name='ContextMenu'>
<property name="Id" type="s" access="read"/> <arg type='i' direction='in' name='x'/>
<property name="Title" type="s" access="read"/> <arg type='i' direction='in' name='y'/>
<property name="Status" type="s" access="read"/>
<property name="WindowId" type="i" access="read"/>
<property name="Menu" type="o" access="read" />
<!-- main icon -->
<!-- names are preferred over pixmaps -->
<property name="IconName" type="s" access="read" />
<property name="IconThemePath" type="s" access="read" />
<!-- struct containing width, height and image data-->
<!-- implementation has been dropped as of now -->
<property name="IconPixmap" type="a(iiay)" access="read" />
<!-- not used in ayatana code, no test case so far -->
<property name="OverlayIconName" type="s" access="read"/>
<property name="OverlayIconPixmap" type="a(iiay)" access="read" />
<!-- Requesting attention icon -->
<property name="AttentionIconName" type="s" access="read"/>
<!--same definition as image-->
<property name="AttentionIconPixmap" type="a(iiay)" access="read" />
<!-- tooltip data -->
<!-- unimplemented as of now -->
<!--(iiay) is an image-->
<property name="ToolTip" type="(sa(iiay)ss)" access="read" />
<!-- interaction: actually, we do not use them. -->
<method name="Activate">
<arg name="x" type="i" direction="in"/>
<arg name="y" type="i" direction="in"/>
</method> </method>
<method name="SecondaryActivate"> <method name='Activate'>
<arg name="x" type="i" direction="in"/> <arg type='i' direction='in' name='x'/>
<arg name="y" type="i" direction="in"/> <arg type='i' direction='in' name='y'/>
</method> </method>
<method name="Scroll"> <method name='SecondaryActivate'>
<arg name="delta" type="i" direction="in"/> <arg type='i' direction='in' name='x'/>
<arg name="dir" type="s" direction="in"/> <arg type='i' direction='in' name='y'/>
</method> </method>
<method name='Scroll'>
<!-- Signals: the client wants to change something in the status--> <arg type='i' direction='in' name='delta'/>
<signal name="NewTitle"></signal> <arg type='s' direction='in' name='orientation'/>
<signal name="NewIcon"></signal> </method>
<signal name="NewIconThemePath"> <signal name='NewTitle'/>
<arg type="s" name="icon_theme_path" direction="out" /> <signal name='NewIcon'/>
<signal name='NewAttentionIcon'/>
<signal name='NewOverlayIcon'/>
<signal name='NewToolTip'/>
<signal name='NewStatus'>
<arg type='s' name='status'/>
</signal> </signal>
<signal name="NewAttentionIcon"></signal> <property name='Category' type='s' access='read'/>
<signal name="NewOverlayIcon"></signal> <property name='Id' type='s' access='read'/>
<signal name="NewToolTip"></signal> <property name='Title' type='s' access='read'/>
<signal name="NewStatus"> <property name='Status' type='s' access='read'/>
<arg name="status" type="s" /> <property name='WindowId' type='u' access='read'/>
</signal> <property name='IconThemePath' type='s' access='read'/>
<property name='IconName' type='s' access='read'/>
<!-- ayatana labels --> <property name='IconPixmap' type='a(iiay)' access='read'/>
<!-- These are commented out because GDBusProxy would otherwise require them, <property name='OverlayIconName' type='s' access='read'/>
but they are not available for KDE indicators <property name='OverlayIconPixmap' type='a(iiay)' access='read'/>
--> <property name='AttentionIconName' type='s' access='read'/>
<!--<signal name="XAyatanaNewLabel"> <property name='AttentionIconPixmap' type='a(iiay)' access='read'/>
<arg type="s" name="label" direction="out" /> <property name='AttentionMovieName' type='s' access='read'/>
<arg type="s" name="guide" direction="out" /> <property name='ToolTip' type='(sa(iiay)ss)' access='read'/>
</signal> <property name='Menu' type='o' access='read'/>
<property name="XAyatanaLabel" type="s" access="read" /> <property name='ItemIsMenu' type='b' access='read'/>
<property name="XAyatanaLabelGuide" type="s" access="read" />--> </interface>
</interface>
</node> </node>

View File

@ -24,6 +24,7 @@ wayland_scanner_client = generator(
client_protocols = [ client_protocols = [
[wl_protocol_dir, 'stable/xdg-shell/xdg-shell.xml'], [wl_protocol_dir, 'stable/xdg-shell/xdg-shell.xml'],
[wl_protocol_dir, 'unstable/xdg-output/xdg-output-unstable-v1.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'], ['wlr-layer-shell-unstable-v1.xml'],
] ]

View File

@ -1,12 +1,12 @@
{ {
"layer": "top", // Waybar at top layer "layer": "top", // Waybar at top layer
// "position": "bottom", // Waybar at the bottom of your screen // "position": "bottom", // Waybar position (top|bottom|left|right)
// "height": 30, // Waybar height "height": 30, // Waybar height (to be removed for auto height)
// "width": 1280, // Waybar width // "width": 1280, // Waybar width
// Choose the order of the modules // Choose the order of the modules
"modules-left": ["sway/workspaces", "sway/mode", "custom/spotify"], "modules-left": ["sway/workspaces", "sway/mode", "custom/media"],
"modules-center": ["sway/window"], "modules-center": ["sway/window"],
"modules-right": ["pulseaudio", "network", "cpu", "memory", "battery", "battery#bat2", "clock", "tray"], "modules-right": ["mpd", "idle_inhibitor", "pulseaudio", "network", "cpu", "memory", "temperature", "backlight", "battery", "battery#bat2", "clock", "tray"],
// Modules configuration // Modules configuration
// "sway/workspaces": { // "sway/workspaces": {
// "disable-scroll": true, // "disable-scroll": true,
@ -24,24 +24,68 @@
// } // }
// }, // },
"sway/mode": { "sway/mode": {
"format": "{}" "format": "<span style=\"italic\">{}</span>"
}, },
"sway/window": { "mpd": {
"max-length": 50 "format": "{stateIcon} {consumeIcon}{randomIcon}{repeatIcon}{singleIcon}{artist} - {album} - {title} ({elapsedTime:%M:%S}/{totalTime:%M:%S}) ",
"format-disconnected": "Disconnected ",
"format-stopped": "{consumeIcon}{randomIcon}{repeatIcon}{singleIcon}Stopped ",
"unknown-tag": "N/A",
"interval": 2,
"consume-icons": {
"on": " "
},
"random-icons": {
"off": "<span color=\"#f53c3c\"></span> ",
"on": " "
},
"repeat-icons": {
"on": " "
},
"single-icons": {
"on": "1 "
},
"state-icons": {
"paused": "",
"playing": ""
},
"tooltip-format": "MPD (connected)",
"tooltip-format-disconnected": "MPD (disconnected)"
},
"idle_inhibitor": {
"format": "{icon}",
"format-icons": {
"activated": "",
"deactivated": ""
}
}, },
"tray": { "tray": {
// "icon-size": 21, // "icon-size": 21,
"spacing": 10 "spacing": 10
}, },
"clock": { "clock": {
"tooltip-format": "{:%Y-%m-%d | %H:%M}",
"format-alt": "{:%Y-%m-%d}" "format-alt": "{:%Y-%m-%d}"
}, },
"cpu": { "cpu": {
"format": "{usage}% " "format": "{usage}% ",
"tooltip": false
}, },
"memory": { "memory": {
"format": "{}% " "format": "{}% "
}, },
"temperature": {
// "thermal-zone": 2,
// "hwmon-path": "/sys/class/hwmon/hwmon2/temp1_input",
"critical-threshold": 80,
// "format-critical": "{temperatureC}°C ",
"format": "{temperatureC}°C "
},
"backlight": {
// "device": "acpi_video1",
"format": "{percent}% {icon}",
"format-icons": ["", ""]
},
"battery": { "battery": {
"states": { "states": {
// "good": 95, // "good": 95,
@ -78,11 +122,15 @@
}, },
"on-click": "pavucontrol" "on-click": "pavucontrol"
}, },
"custom/spotify": { "custom/media": {
"format": " {}", "format": "{icon} {}",
"return-type": "json",
"max-length": 40, "max-length": 40,
"interval": 30, // Remove this if your script is endless and write in loop "format-icons": {
"exec": "$HOME/.config/waybar/mediaplayer.sh 2> /dev/null", // Script in resources folder "spotify": "",
"exec-if": "pgrep spotify" "default": "🎜"
},
"escape": true,
"exec": "$HOME/.config/waybar/mediaplayer.py 2> /dev/null" // Script in resources folder
} }
} }

View File

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

View File

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

View File

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

View File

@ -3,37 +3,45 @@
#include <iostream> #include <iostream>
waybar::ALabel::ALabel(const Json::Value& config, const std::string format) waybar::ALabel::ALabel(const Json::Value& config, const std::string& format, uint16_t interval)
: config_(config), : config_(config),
format_(config_["format"].isString() ? config_["format"].asString() : format), format_(config_["format"].isString() ? config_["format"].asString() : format),
default_format_(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_); event_box_.add(label_);
if (config_["max-length"].isUInt()) { if (config_["max-length"].isUInt()) {
label_.set_max_width_chars(config_["max-length"].asUInt()); label_.set_max_width_chars(config_["max-length"].asUInt());
label_.set_ellipsize(Pango::EllipsizeMode::ELLIPSIZE_END); label_.set_ellipsize(Pango::EllipsizeMode::ELLIPSIZE_END);
} }
if (config_["rotate"].isUInt()) {
label_.set_angle(config["rotate"].asUInt());
}
if (config_["format-alt"].isString()) { if (config_["format-alt"].isString()) {
event_box_.add_events(Gdk::BUTTON_PRESS_MASK); event_box_.add_events(Gdk::BUTTON_PRESS_MASK);
event_box_.signal_button_press_event().connect( event_box_.signal_button_press_event().connect(sigc::mem_fun(*this, &ALabel::handleToggle));
sigc::mem_fun(*this, &ALabel::handleToggle));
} }
// configure events' user commands // configure events' user commands
if (config_["on-click"].isString()) { if (config_["on-click"].isString() || config_["on-click-right"].isString()) {
event_box_.add_events(Gdk::BUTTON_PRESS_MASK); event_box_.add_events(Gdk::BUTTON_PRESS_MASK);
event_box_.signal_button_press_event().connect( event_box_.signal_button_press_event().connect(sigc::mem_fun(*this, &ALabel::handleToggle));
sigc::mem_fun(*this, &ALabel::handleToggle));
} }
if (config_["on-scroll-up"].isString()) { if (config_["on-scroll-up"].isString() || config_["on-scroll-down"].isString()) {
event_box_.add_events(Gdk::SCROLL_MASK); event_box_.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);
event_box_.signal_scroll_event().connect( event_box_.signal_scroll_event().connect(sigc::mem_fun(*this, &ALabel::handleScroll));
sigc::mem_fun(*this, &ALabel::handleScroll));
} }
if (config_["on-scroll-down"].isString()) { }
event_box_.add_events(Gdk::SCROLL_MASK);
event_box_.signal_scroll_event().connect( waybar::ALabel::~ALabel() {
sigc::mem_fun(*this, &ALabel::handleScroll)); for (const auto& pid : pid_) {
if (pid != -1) {
kill(-pid, 9);
}
} }
} }
@ -43,10 +51,19 @@ auto waybar::ALabel::update() -> void {
bool waybar::ALabel::handleToggle(GdkEventButton* const& e) { bool waybar::ALabel::handleToggle(GdkEventButton* const& e) {
if (config_["on-click"].isString() && e->button == 1) { if (config_["on-click"].isString() && e->button == 1) {
waybar::util::command::forkExec(config_["on-click"].asString()); pid_.push_back(waybar::util::command::forkExec(config_["on-click"].asString()));
} else { } else if (config_["on-click-middle"].isString() && e->button == 2) {
alt = !alt; pid_.push_back(waybar::util::command::forkExec(config_["on-click-middle"].asString()));
if (alt) { } 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(); format_ = config_["format-alt"].asString();
} else { } else {
format_ = default_format_; format_ = default_format_;
@ -58,10 +75,9 @@ bool waybar::ALabel::handleToggle(GdkEventButton* const& e) {
} }
bool waybar::ALabel::handleScroll(GdkEventScroll* e) { bool waybar::ALabel::handleScroll(GdkEventScroll* e) {
// Avoid concurrent scroll event // Avoid concurrent scroll event
std::lock_guard<std::mutex> lock(mutex_); std::lock_guard<std::mutex> lock(mutex_);
bool direction_up = false; bool direction_up = false;
if (e->direction == GDK_SCROLL_UP) { if (e->direction == GDK_SCROLL_UP) {
direction_up = true; direction_up = true;
@ -71,8 +87,7 @@ bool waybar::ALabel::handleScroll(GdkEventScroll* e) {
} }
if (e->direction == GDK_SCROLL_SMOOTH) { if (e->direction == GDK_SCROLL_SMOOTH) {
gdouble delta_x, delta_y; gdouble delta_x, delta_y;
gdk_event_get_scroll_deltas(reinterpret_cast<const GdkEvent*>(e), gdk_event_get_scroll_deltas(reinterpret_cast<const GdkEvent*>(e), &delta_x, &delta_y);
&delta_x, &delta_y);
if (delta_y < 0) { if (delta_y < 0) {
direction_up = true; direction_up = true;
} else if (delta_y > 0) { } else if (delta_y > 0) {
@ -80,19 +95,18 @@ bool waybar::ALabel::handleScroll(GdkEventScroll* e) {
} }
} }
if (direction_up && config_["on-scroll-up"].isString()) { if (direction_up && config_["on-scroll-up"].isString()) {
waybar::util::command::forkExec(config_["on-scroll-up"].asString()); pid_.push_back(waybar::util::command::forkExec(config_["on-scroll-up"].asString()));
} else if (config_["on-scroll-down"].isString()) { } else if (config_["on-scroll-down"].isString()) {
waybar::util::command::forkExec(config_["on-scroll-down"].asString()); pid_.push_back(waybar::util::command::forkExec(config_["on-scroll-down"].asString()));
} }
dp.emit(); dp.emit();
return true; return true;
} }
std::string waybar::ALabel::getIcon(uint16_t percentage, std::string waybar::ALabel::getIcon(uint16_t percentage, const std::string& alt) {
const std::string& alt) {
auto format_icons = config_["format-icons"]; auto format_icons = config_["format-icons"];
if (format_icons.isObject()) { if (format_icons.isObject()) {
if (!alt.empty() && format_icons[alt].isString()) { if (!alt.empty() && (format_icons[alt].isString() || format_icons[alt].isArray())) {
format_icons = format_icons[alt]; format_icons = format_icons[alt];
} else { } else {
format_icons = format_icons["default"]; format_icons = format_icons["default"];
@ -109,4 +123,8 @@ std::string waybar::ALabel::getIcon(uint16_t percentage,
return ""; return "";
} }
bool waybar::ALabel::tooltipEnabled() {
return config_["tooltip"].isBool() ? config_["tooltip"].asBool() : true;
}
waybar::ALabel::operator Gtk::Widget&() { return event_box_; } waybar::ALabel::operator Gtk::Widget&() { return event_box_; }

View File

@ -1,178 +1,214 @@
#include "bar.hpp" #include "bar.hpp"
#include "client.hpp" #include "client.hpp"
#include "factory.hpp" #include "factory.hpp"
#include "util/json.hpp"
waybar::Bar::Bar(const Client& client, waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config)
std::unique_ptr<struct wl_output *> &&p_output, uint32_t p_wl_name) : output(w_output),
: client(client), window{Gtk::WindowType::WINDOW_TOPLEVEL}, config(w_config),
surface(nullptr), layer_surface(nullptr), window{Gtk::WindowType::WINDOW_TOPLEVEL},
output(std::move(p_output)), wl_name(p_wl_name) surface(nullptr),
{ layer_surface(nullptr),
static const struct zxdg_output_v1_listener xdgOutputListener = { left_(Gtk::ORIENTATION_HORIZONTAL, 0),
.logical_position = handleLogicalPosition, center_(Gtk::ORIENTATION_HORIZONTAL, 0),
.logical_size = handleLogicalSize, right_(Gtk::ORIENTATION_HORIZONTAL, 0),
.done = handleDone, box_(Gtk::ORIENTATION_HORIZONTAL, 0) {
.name = handleName,
.description = handleDescription,
};
xdg_output_ =
zxdg_output_manager_v1_get_xdg_output(client.xdg_output_manager, *output);
zxdg_output_v1_add_listener(xdg_output_, &xdgOutputListener, this);
window.set_title("waybar"); window.set_title("waybar");
window.set_name("waybar"); window.set_name("waybar");
window.set_decorated(false); window.set_decorated(false);
window.set_resizable(false);
setupConfig();
setupCss();
auto wrap = reinterpret_cast<GtkWidget*>(window.gobj()); if (config["position"] == "right" || config["position"] == "left") {
gtk_widget_realize(wrap); height_ = 0;
GdkWindow *gdk_window = gtk_widget_get_window(wrap); width_ = 1;
}
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); gdk_wayland_window_set_use_custom_surface(gdk_window);
surface = gdk_wayland_window_get_wl_surface(gdk_window); surface = gdk_wayland_window_get_wl_surface(gdk_window);
std::size_t layer_top = config_["layer"] == "top" std::size_t layer = config["layer"] == "top" ? ZWLR_LAYER_SHELL_V1_LAYER_TOP
? ZWLR_LAYER_SHELL_V1_LAYER_TOP : ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM; : ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM;
auto client = waybar::Client::inst();
layer_surface = zwlr_layer_shell_v1_get_layer_surface( layer_surface = zwlr_layer_shell_v1_get_layer_surface(
client.layer_shell, surface, *output, layer_top, "waybar"); client->layer_shell, surface, output->output, layer, "waybar");
static const struct zwlr_layer_surface_v1_listener layer_surface_listener = { static const struct zwlr_layer_surface_v1_listener layer_surface_listener = {
.configure = layerSurfaceHandleConfigure, .configure = layerSurfaceHandleConfigure,
.closed = layerSurfaceHandleClosed, .closed = layerSurfaceHandleClosed,
}; };
zwlr_layer_surface_v1_add_listener(layer_surface, zwlr_layer_surface_v1_add_listener(layer_surface, &layer_surface_listener, this);
&layer_surface_listener, this);
std::size_t anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT auto height = config["height"].isUInt() ? config["height"].asUInt() : height_;
| ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; auto width = config["width"].isUInt() ? config["width"].asUInt() : width_;
if (config_["position"] == "bottom") {
anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM; window.signal_configure_event().connect_notify([&](GdkEventConfigure* ev) {
} else { auto tmp_height = height_;
anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP; auto tmp_width = width_;
if (ev->height > static_cast<int>(height_)) {
// Default minimal value
if (height_ != 1) {
std::cout << fmt::format(MIN_HEIGHT_MSG, height_, ev->height) << std::endl;
}
if (config["height"].isUInt()) {
std::cout << fmt::format(SIZE_DEFINED, "Height") << std::endl;
} else {
tmp_height = ev->height;
}
}
if (ev->width > static_cast<int>(width_)) {
// Default minimal value
if (width_ != 1) {
std::cout << fmt::format(MIN_WIDTH_MSG, width_, ev->width) << std::endl;
}
if (config["width"].isUInt()) {
std::cout << fmt::format(SIZE_DEFINED, "Width") << std::endl;
} else {
tmp_width = ev->width;
}
}
if (tmp_width != width_ || tmp_height != height_) {
zwlr_layer_surface_v1_set_size(layer_surface, tmp_width, tmp_height);
}
});
std::size_t anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP;
if (config["position"] == "bottom") {
anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM;
} else if (config["position"] == "left") {
anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT;
} else if (config["position"] == "right") {
anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
}
if (anchor == ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM || anchor == ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP) {
anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
} else if (anchor == ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT ||
anchor == ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT) {
anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM;
left_ = Gtk::Box(Gtk::ORIENTATION_VERTICAL, 0);
center_ = Gtk::Box(Gtk::ORIENTATION_VERTICAL, 0);
right_ = Gtk::Box(Gtk::ORIENTATION_VERTICAL, 0);
box_ = Gtk::Box(Gtk::ORIENTATION_VERTICAL, 0);
vertical = true;
} }
auto height = config_["height"].isUInt() ? config_["height"].asUInt() : height_;
auto width = config_["width"].isUInt() ? config_["width"].asUInt() : width_;
zwlr_layer_surface_v1_set_anchor(layer_surface, anchor); zwlr_layer_surface_v1_set_anchor(layer_surface, anchor);
zwlr_layer_surface_v1_set_exclusive_zone(layer_surface, height); zwlr_layer_surface_v1_set_exclusive_zone(layer_surface, vertical ? width : height);
zwlr_layer_surface_v1_set_size(layer_surface, width, height); zwlr_layer_surface_v1_set_size(layer_surface, width, height);
wl_surface_commit(surface); wl_surface_commit(surface);
wl_display_roundtrip(client->wl_display);
setupWidgets(); setupWidgets();
} }
void waybar::Bar::handleLogicalPosition(void* /*data*/, // Converting string to button code rn as to avoid doing it later
struct zxdg_output_v1* /*zxdg_output_v1*/, int32_t /*x*/, int32_t /*y*/) void waybar::Bar::setupAltFormatKeyForModule(const std::string& module_name) {
{ if (config.isMember(module_name)) {
// Nothing here 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*/, void waybar::Bar::setupAltFormatKeyForModuleList(const char* module_list_name) {
struct zxdg_output_v1* /*zxdg_output_v1*/, int32_t /*width*/, if (config.isMember(module_list_name)) {
int32_t /*height*/) Json::Value& modules = config[module_list_name];
{ for (const Json::Value& module_name : modules) {
// Nothing here if (module_name.isString()) {
setupAltFormatKeyForModule(module_name.asString());
}
}
}
} }
void waybar::Bar::handleDone(void* /*data*/, void waybar::Bar::handleSignal(int signal) {
struct zxdg_output_v1* /*zxdg_output_v1*/) for (auto& module : modules_left_) {
{ auto* custom = dynamic_cast<waybar::modules::Custom*>(module.get());
// Nothing here if (custom != nullptr) {
custom->refresh(signal);
}
}
for (auto& module : modules_center_) {
auto* custom = dynamic_cast<waybar::modules::Custom*>(module.get());
if (custom != nullptr) {
custom->refresh(signal);
}
}
for (auto& module : modules_right_) {
auto* custom = dynamic_cast<waybar::modules::Custom*>(module.get());
if (custom != nullptr) {
custom->refresh(signal);
}
}
} }
void waybar::Bar::handleName(void* data, struct zxdg_output_v1* /*xdg_output*/, void waybar::Bar::layerSurfaceHandleConfigure(void* data, struct zwlr_layer_surface_v1* surface,
const char* name) uint32_t serial, uint32_t width, uint32_t height) {
{ auto o = static_cast<waybar::Bar*>(data);
auto o = static_cast<waybar::Bar *>(data);
o->output_name = name;
}
void waybar::Bar::handleDescription(void* /*data*/,
struct zxdg_output_v1* /*zxdg_output_v1*/, const char* /*description*/)
{
// Nothing here
}
void waybar::Bar::layerSurfaceHandleConfigure(void* data,
struct zwlr_layer_surface_v1* surface, uint32_t serial, uint32_t width,
uint32_t height)
{
auto o = static_cast<waybar::Bar *>(data);
o->window.show_all();
zwlr_layer_surface_v1_ack_configure(surface, serial);
if (width != o->width_ || height != o->height_) { if (width != o->width_ || height != o->height_) {
o->width_ = width; o->width_ = width;
o->height_ = height; o->height_ = height;
o->window.set_size_request(o->width_, o->height_); o->window.set_size_request(o->width_, o->height_);
o->window.resize(o->width_, o->height_); o->window.resize(o->width_, o->height_);
zwlr_layer_surface_v1_set_exclusive_zone(o->layer_surface,
int dummy_width, min_height; o->vertical ? o->width_ : o->height_);
o->window.get_size(dummy_width, min_height); std::cout << fmt::format(BAR_SIZE_MSG,
if (o->height_ < static_cast<uint32_t>(min_height)) { o->width_ == 1 ? "auto" : std::to_string(o->width_),
std::cout << fmt::format("Requested height: {} exceeds the minimum \ o->height_ == 1 ? "auto" : std::to_string(o->height_),
height: {} required by the modules", o->height_, min_height) << std::endl; o->output->name)
o->height_ = min_height; << std::endl;
}
std::cout << fmt::format(
"Bar configured (width: {}, height: {}) for output: {}",
o->width_, o->height_, o->output_name) << std::endl;
wl_surface_commit(o->surface); wl_surface_commit(o->surface);
} }
zwlr_layer_surface_v1_ack_configure(surface, serial);
} }
void waybar::Bar::layerSurfaceHandleClosed(void* data, void waybar::Bar::layerSurfaceHandleClosed(void* data, struct zwlr_layer_surface_v1* /*surface*/) {
struct zwlr_layer_surface_v1* /*surface*/) auto o = static_cast<waybar::Bar*>(data);
{
auto o = static_cast<waybar::Bar *>(data);
std::cout << "Bar removed from output: " + o->output_name << std::endl;
zwlr_layer_surface_v1_destroy(o->layer_surface); zwlr_layer_surface_v1_destroy(o->layer_surface);
wl_output_destroy(*o->output);
zxdg_output_v1_destroy(o->xdg_output_);
o->modules_left_.clear(); o->modules_left_.clear();
o->modules_center_.clear(); o->modules_center_.clear();
o->modules_right_.clear(); o->modules_right_.clear();
} }
auto waybar::Bar::toggle() -> void auto waybar::Bar::toggle() -> void {
{
visible = !visible; visible = !visible;
auto zone = visible ? height_ : 0; auto zone = visible ? height_ : 0;
if (!visible) {
window.get_style_context()->add_class("hidden");
} else {
window.get_style_context()->remove_class("hidden");
}
zwlr_layer_surface_v1_set_exclusive_zone(layer_surface, zone); zwlr_layer_surface_v1_set_exclusive_zone(layer_surface, zone);
wl_surface_commit(surface); wl_surface_commit(surface);
} }
auto waybar::Bar::setupConfig() -> void void waybar::Bar::getModules(const Factory& factory, const std::string& pos) {
{ if (config[pos].isArray()) {
std::ifstream file(client.config_file); for (const auto& name : config[pos]) {
if (!file.is_open()) {
throw std::runtime_error("Can't open config file");
}
std::string str((std::istreambuf_iterator<char>(file)),
std::istreambuf_iterator<char>());
util::JsonParser parser;
config_ = parser.parse(str);
}
auto waybar::Bar::setupCss() -> void
{
css_provider_ = Gtk::CssProvider::create();
style_context_ = Gtk::StyleContext::create();
// Load our css file, wherever that may be hiding
if (css_provider_->load_from_path(client.css_file)) {
Glib::RefPtr<Gdk::Screen> screen = window.get_screen();
style_context_->add_provider_for_screen(screen, css_provider_,
GTK_STYLE_PROVIDER_PRIORITY_USER);
}
}
void waybar::Bar::getModules(const Factory& factory, const std::string& pos)
{
if (config_[pos].isArray()) {
for (const auto &name : config_[pos]) {
try { try {
auto module = factory.makeModule(name.asString()); auto module = factory.makeModule(name.asString());
if (pos == "modules-left") { if (pos == "modules-left") {
@ -184,7 +220,13 @@ void waybar::Bar::getModules(const Factory& factory, const std::string& pos)
if (pos == "modules-right") { if (pos == "modules-right") {
modules_right_.emplace_back(module); modules_right_.emplace_back(module);
} }
module->dp.connect([module] { module->update(); }); module->dp.connect([module, &name] {
try {
module->update();
} catch (const std::exception& e) {
std::cerr << name.asString() + ": " + e.what() << std::endl;
}
});
} catch (const std::exception& e) { } catch (const std::exception& e) {
std::cerr << e.what() << std::endl; std::cerr << e.what() << std::endl;
} }
@ -192,30 +234,30 @@ void waybar::Bar::getModules(const Factory& factory, const std::string& pos)
} }
} }
auto waybar::Bar::setupWidgets() -> void auto waybar::Bar::setupWidgets() -> void {
{ window.add(box_);
auto &left = *Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 0)); box_.pack_start(left_, true, true);
auto &center = *Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 0)); box_.set_center_widget(center_);
auto &right = *Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 0)); box_.pack_end(right_, true, true);
auto &box = *Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 0)); // Convert to button code for every module that is used.
window.add(box); setupAltFormatKeyForModuleList("modules-left");
box.pack_start(left, true, true); setupAltFormatKeyForModuleList("modules-right");
box.set_center_widget(center); setupAltFormatKeyForModuleList("modules-center");
box.pack_end(right, true, true);
Factory factory(*this, config_); Factory factory(*this, config);
getModules(factory, "modules-left"); getModules(factory, "modules-left");
getModules(factory, "modules-center"); getModules(factory, "modules-center");
getModules(factory, "modules-right"); getModules(factory, "modules-right");
for (auto const& module : modules_left_) { for (auto const& module : modules_left_) {
left.pack_start(*module, false, true, 0); left_.pack_start(*module, false, true, 0);
} }
for (auto const& module : modules_center_) { for (auto const& module : modules_center_) {
center.pack_start(*module, true, true, 0); center_.pack_start(*module, true, true, 0);
} }
std::reverse(modules_right_.begin(), modules_right_.end()); std::reverse(modules_right_.begin(), modules_right_.end());
for (auto const& module : modules_right_) { for (auto const& module : modules_right_) {
right.pack_end(*module, false, false, 0); right_.pack_end(*module, false, false, 0);
} }
window.show_all();
} }

View File

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

View File

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

View File

@ -1,25 +1,27 @@
#include "client.hpp"
#include <csignal> #include <csignal>
#include <iostream> #include <iostream>
#include "client.hpp"
namespace waybar { int main(int argc, char* argv[]) {
static Client* client;
} // namespace waybar
int main(int argc, char* argv[])
{
try { try {
waybar::Client c(argc, argv); auto client = waybar::Client::inst();
waybar::client = &c; std::signal(SIGUSR1, [](int /*signal*/) {
std::signal(SIGUSR1, [] (int /*signal*/) { for (auto& bar : waybar::Client::inst()->bars) {
for (auto& bar : waybar::client->bars) { bar->toggle();
(*bar).toggle();
} }
}); });
return c.main(argc, argv); for (int sig = SIGRTMIN + 1; sig <= SIGRTMAX; ++sig) {
std::signal(sig, [](int sig) {
for (auto& bar : waybar::Client::inst()->bars) {
bar->handleSignal(sig);
}
});
}
auto ret = client->main(argc, argv);
delete client;
return ret;
} catch (const std::exception& e) { } catch (const std::exception& e) {
std::cerr << e.what() << std::endl; std::cerr << e.what() << std::endl;
return 1; 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,60 +1,42 @@
#include "modules/battery.hpp" #include "modules/battery.hpp"
waybar::modules::Battery::Battery(const Json::Value& config) waybar::modules::Battery::Battery(const std::string& id, const Json::Value& config)
: ALabel(config, "{capacity}%") : ALabel(config, "{capacity}%", 60) {
{ label_.set_name("battery");
try { if (!id.empty()) {
if (config_["bat"].isString()) { label_.get_style_context()->add_class(id);
auto dir = data_dir_ / config_["bat"].asString();
if (fs::is_directory(dir) && fs::exists(dir / "capacity")
&& fs::exists(dir / "status") && fs::exists(dir / "uevent")) {
batteries_.push_back(dir);
}
} else {
for (auto const& node : fs::directory_iterator(data_dir_)) {
if (fs::is_directory(node) && fs::exists(node / "capacity")
&& fs::exists(node / "status") && fs::exists(node / "uevent")) {
batteries_.push_back(node);
}
}
}
} catch (fs::filesystem_error &e) {
throw std::runtime_error(e.what());
}
if (batteries_.empty()) {
if (config_["bat"].isString()) {
throw std::runtime_error("No battery named " + config_["bat"].asString());
}
throw std::runtime_error("No batteries.");
} }
getBatteries();
fd_ = inotify_init1(IN_CLOEXEC); fd_ = inotify_init1(IN_CLOEXEC);
if (fd_ == -1) { if (fd_ == -1) {
throw std::runtime_error("Unable to listen batteries."); throw std::runtime_error("Unable to listen batteries.");
} }
for (auto const& bat : batteries_) { for (auto const& bat : batteries_) {
inotify_add_watch(fd_, (bat / "uevent").c_str(), IN_ACCESS); auto wd = inotify_add_watch(fd_, (bat / "uevent").c_str(), IN_ACCESS);
if (wd != -1) {
wds_.push_back(wd);
}
} }
worker(); worker();
} }
waybar::modules::Battery::~Battery() waybar::modules::Battery::~Battery() {
{ for (auto wd : wds_) {
inotify_rm_watch(fd_, wd);
}
close(fd_); close(fd_);
} }
void waybar::modules::Battery::worker() void waybar::modules::Battery::worker() {
{ thread_timer_ = [this] {
// Trigger first values
update();
uint32_t interval = config_["interval"].isUInt() ? config_["interval"].asUInt() : 60;
thread_timer_ = [this, interval] {
thread_.sleep_for(chrono::seconds(interval));
dp.emit(); dp.emit();
thread_timer_.sleep_for(interval_);
}; };
thread_ = [this] { thread_ = [this] {
struct inotify_event event = {0}; struct inotify_event event = {0};
int nbytes = read(fd_, &event, sizeof(event)); int nbytes = read(fd_, &event, sizeof(event));
if (nbytes != sizeof(event)) { if (nbytes != sizeof(event) || event.mask & IN_IGNORED) {
thread_.stop();
return; return;
} }
// TODO: don't stop timer for now since there is some bugs :? // TODO: don't stop timer for now since there is some bugs :?
@ -63,13 +45,42 @@ void waybar::modules::Battery::worker()
}; };
} }
std::tuple<uint16_t, std::string> waybar::modules::Battery::getInfos() void waybar::modules::Battery::getBatteries() {
{
try { try {
uint16_t total = 0; for (auto const& node : fs::directory_iterator(data_dir_)) {
if (!fs::is_directory(node)) {
continue;
}
auto dir_name = node.path().filename();
auto bat_defined = config_["bat"].isString();
if (((bat_defined && dir_name == config_["bat"].asString()) || !bat_defined) &&
fs::exists(node / "capacity") && fs::exists(node / "uevent") &&
fs::exists(node / "status")) {
batteries_.push_back(node);
}
auto adap_defined = config_["adapter"].isString();
if (((adap_defined && dir_name == config_["adapter"].asString()) || !adap_defined) &&
fs::exists(node / "online")) {
adapter_ = node;
}
}
} catch (fs::filesystem_error& e) {
throw std::runtime_error(e.what());
}
if (batteries_.empty()) {
if (config_["bat"].isString()) {
throw std::runtime_error("No battery named " + config_["bat"].asString());
}
throw std::runtime_error("No batteries.");
}
}
const std::tuple<uint8_t, std::string> waybar::modules::Battery::getInfos() const {
try {
uint16_t total = 0;
std::string status = "Unknown"; std::string status = "Unknown";
for (auto const& bat : batteries_) { for (auto const& bat : batteries_) {
uint16_t capacity; uint16_t capacity;
std::string _status; std::string _status;
std::ifstream(bat / "capacity") >> capacity; std::ifstream(bat / "capacity") >> capacity;
std::ifstream(bat / "status") >> _status; std::ifstream(bat / "status") >> _status;
@ -86,37 +97,50 @@ std::tuple<uint16_t, std::string> waybar::modules::Battery::getInfos()
} }
} }
std::string waybar::modules::Battery::getState(uint16_t capacity) const std::string waybar::modules::Battery::getAdapterStatus(uint8_t capacity) const {
{ if (!adapter_.empty()) {
bool online;
std::ifstream(adapter_ / "online") >> online;
if (capacity == 100) {
return "Full";
}
return online ? "Charging" : "Discharging";
}
return "Unknown";
}
const std::string waybar::modules::Battery::getState(uint8_t capacity) const {
// Get current state // Get current state
std::vector<std::pair<std::string, uint16_t>> states; std::vector<std::pair<std::string, uint8_t>> states;
if (config_["states"].isObject()) { if (config_["states"].isObject()) {
for (auto it = config_["states"].begin(); it != config_["states"].end(); ++it) { for (auto it = config_["states"].begin(); it != config_["states"].end(); ++it) {
if (it->isUInt() && it.key().isString()) { if (it->isUInt() && it.key().isString()) {
states.push_back({it.key().asString(), it->asUInt()}); states.emplace_back(it.key().asString(), it->asUInt());
} }
} }
} }
// Sort states // Sort states
std::sort(states.begin(), states.end(), [](auto &a, auto &b) { std::sort(states.begin(), states.end(), [](auto& a, auto& b) { return a.second < b.second; });
return a.second < b.second; std::string valid_state;
}); for (auto const& state : states) {
std::string validState = ""; if (capacity <= state.second && valid_state.empty()) {
for (auto state : states) {
if (capacity <= state.second && validState.empty()) {
label_.get_style_context()->add_class(state.first); label_.get_style_context()->add_class(state.first);
validState = state.first; valid_state = state.first;
} else { } else {
label_.get_style_context()->remove_class(state.first); label_.get_style_context()->remove_class(state.first);
} }
} }
return validState; return valid_state;
} }
auto waybar::modules::Battery::update() -> void auto waybar::modules::Battery::update() -> void {
{
auto [capacity, status] = getInfos(); auto [capacity, status] = getInfos();
label_.set_tooltip_text(status); if (status == "Unknown") {
status = getAdapterStatus(capacity);
}
if (tooltipEnabled()) {
label_.set_tooltip_text(status);
}
std::transform(status.begin(), status.end(), status.begin(), ::tolower); std::transform(status.begin(), status.end(), status.begin(), ::tolower);
auto format = format_; auto format = format_;
auto state = getState(capacity); auto state = getState(capacity);
@ -132,11 +156,9 @@ auto waybar::modules::Battery::update() -> void
} }
if (format.empty()) { if (format.empty()) {
event_box_.hide(); event_box_.hide();
label_.set_name("");
} else { } else {
event_box_.show(); event_box_.show();
label_.set_name("battery"); label_.set_markup(
label_.set_text(fmt::format(format, fmt::arg("capacity", capacity), fmt::format(format, fmt::arg("capacity", capacity), fmt::arg("icon", getIcon(capacity))));
fmt::arg("icon", getIcon(capacity))));
} }
} }

View File

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

View File

@ -1,56 +1,51 @@
#include "modules/cpu.hpp" #include "modules/cpu.hpp"
waybar::modules::Cpu::Cpu(const Json::Value& config) waybar::modules::Cpu::Cpu(const std::string& id, const Json::Value& config)
: ALabel(config, "{usage}%") : ALabel(config, "{usage}%", 10) {
{
label_.set_name("cpu"); label_.set_name("cpu");
uint32_t interval = config_["interval"].isUInt() ? config_["interval"].asUInt() : 10; if (!id.empty()) {
thread_ = [this, interval] { label_.get_style_context()->add_class(id);
}
thread_ = [this] {
dp.emit(); dp.emit();
thread_.sleep_for(chrono::seconds(interval)); thread_.sleep_for(interval_);
}; };
} }
auto waybar::modules::Cpu::update() -> void auto waybar::modules::Cpu::update() -> void {
{ // TODO: as creating dynamic fmt::arg arrays is buggy we have to calc both
try { auto cpu_load = getCpuLoad();
// TODO: as creating dynamic fmt::arg arrays is buggy we have to calc both auto [cpu_usage, tooltip] = getCpuUsage();
auto cpu_load = getCpuLoad(); if (tooltipEnabled()) {
auto [cpu_usage, tooltip] = getCpuUsage();
label_.set_tooltip_text(tooltip); label_.set_tooltip_text(tooltip);
label_.set_text(fmt::format(format_,
fmt::arg("load", cpu_load), fmt::arg("usage", cpu_usage)));
} catch (const std::exception& e) {
std::cerr << e.what() << std::endl;
} }
label_.set_markup(fmt::format(format_, fmt::arg("load", cpu_load), fmt::arg("usage", cpu_usage)));
} }
uint16_t waybar::modules::Cpu::getCpuLoad() uint16_t waybar::modules::Cpu::getCpuLoad() {
{
struct sysinfo info = {0}; struct sysinfo info = {0};
if (sysinfo(&info) == 0) { if (sysinfo(&info) == 0) {
float f_load = 1.f / (1u << SI_LOAD_SHIFT); float f_load = 1.F / (1U << SI_LOAD_SHIFT);
uint16_t load = info.loads[0] * f_load * 100 / get_nprocs(); uint16_t load = info.loads[0] * f_load * 100 / get_nprocs();
return load; return load;
} }
throw std::runtime_error("Can't get Cpu load"); throw std::runtime_error("Can't get Cpu load");
} }
std::tuple<uint16_t, std::string> waybar::modules::Cpu::getCpuUsage() std::tuple<uint16_t, std::string> waybar::modules::Cpu::getCpuUsage() {
{
if (prev_times_.empty()) { if (prev_times_.empty()) {
prev_times_ = parseCpuinfo(); prev_times_ = parseCpuinfo();
std::this_thread::sleep_for(chrono::milliseconds(100)); std::this_thread::sleep_for(std::chrono::milliseconds(100));
} }
std::vector<std::tuple<size_t, size_t>> curr_times = parseCpuinfo(); std::vector<std::tuple<size_t, size_t>> curr_times = parseCpuinfo();
std::string tooltip; std::string tooltip;
uint16_t usage = 0; uint16_t usage = 0;
for (size_t i = 0; i < curr_times.size(); ++i) { for (size_t i = 0; i < curr_times.size(); ++i) {
auto [curr_idle, curr_total] = curr_times[i]; auto [curr_idle, curr_total] = curr_times[i];
auto [prev_idle, prev_total] = prev_times_[i]; auto [prev_idle, prev_total] = prev_times_[i];
const float delta_idle = curr_idle - prev_idle; const float delta_idle = curr_idle - prev_idle;
const float delta_total = curr_total - prev_total; const float delta_total = curr_total - prev_total;
uint16_t tmp = 100 * (1 - delta_idle / delta_total); uint16_t tmp = 100 * (1 - delta_idle / delta_total);
if (i == 0) { if (i == 0) {
usage = tmp; usage = tmp;
tooltip = fmt::format("Total: {}%", tmp); tooltip = fmt::format("Total: {}%", tmp);
@ -62,21 +57,21 @@ std::tuple<uint16_t, std::string> waybar::modules::Cpu::getCpuUsage()
return {usage, tooltip}; return {usage, tooltip};
} }
std::vector<std::tuple<size_t, size_t>> waybar::modules::Cpu::parseCpuinfo() std::vector<std::tuple<size_t, size_t>> waybar::modules::Cpu::parseCpuinfo() {
{
std::ifstream info(data_dir_); std::ifstream info(data_dir_);
if (!info.is_open()) { if (!info.is_open()) {
throw std::runtime_error("Can't open " + data_dir_); throw std::runtime_error("Can't open " + data_dir_);
} }
std::vector< std::tuple<size_t, size_t> > cpuinfo; std::vector<std::tuple<size_t, size_t>> cpuinfo;
std::string line; std::string line;
while (getline(info, line)) { while (getline(info, line)) {
if (line.substr(0,3).compare("cpu") != 0) { if (line.substr(0, 3).compare("cpu") != 0) {
break; break;
} }
std::stringstream sline(line.substr(5)); std::stringstream sline(line.substr(5));
std::vector<size_t> times; std::vector<size_t> times;
for (size_t time; sline >> time; times.push_back(time)); for (size_t time = 0; sline >> time; times.push_back(time))
;
size_t idle_time = 0; size_t idle_time = 0;
size_t total_time = 0; size_t total_time = 0;
@ -84,7 +79,7 @@ std::vector<std::tuple<size_t, size_t>> waybar::modules::Cpu::parseCpuinfo()
idle_time = times[3]; idle_time = times[3];
total_time = std::accumulate(times.begin(), times.end(), 0); total_time = std::accumulate(times.begin(), times.end(), 0);
} }
cpuinfo.push_back({idle_time, total_time}); cpuinfo.emplace_back(idle_time, total_time);
} }
return cpuinfo; return cpuinfo;
} }

View File

@ -1,120 +1,137 @@
#include "modules/custom.hpp" #include "modules/custom.hpp"
waybar::modules::Custom::Custom(const std::string name, waybar::modules::Custom::Custom(const std::string& name, const Json::Value& config)
const Json::Value& config) : ALabel(config, "{}"), name_(name), fp_(nullptr), pid_(-1) {
: ALabel(config, "{}"), name_(name) label_.set_name("custom-" + name_);
{ if (config_["exec"].isString()) {
if (!config_["exec"].isString()) { if (interval_.count() > 0) {
throw std::runtime_error(name_ + " has no exec path."); delayWorker();
} else {
continuousWorker();
}
} }
if (config_["interval"].isUInt()) { dp.emit();
delayWorker(); }
} else {
continuousWorker(); waybar::modules::Custom::~Custom() {
if (pid_ != -1) {
kill(-pid_, 9);
pid_ = -1;
} }
} }
void waybar::modules::Custom::delayWorker() void waybar::modules::Custom::delayWorker() {
{ thread_ = [this] {
auto interval = config_["interval"].asUInt();
thread_ = [this, interval] {
bool can_update = true; bool can_update = true;
if (config_["exec-if"].isString()) { if (config_["exec-if"].isString()) {
auto res = waybar::util::command::exec(config_["exec-if"].asString()); auto res = waybar::util::command::exec(config_["exec-if"].asString());
if (res.exit_code != 0) { if (res.exit_code != 0) {
can_update = false; can_update = false;
label_.hide(); event_box_.hide();
label_.set_name("");
} }
} }
if (can_update) { if (can_update) {
output_ = waybar::util::command::exec(config_["exec"].asString()); output_ = waybar::util::command::exec(config_["exec"].asString());
dp.emit(); dp.emit();
} }
thread_.sleep_for(chrono::seconds(interval)); thread_.sleep_for(interval_);
}; };
} }
void waybar::modules::Custom::continuousWorker() void waybar::modules::Custom::continuousWorker() {
{
auto cmd = config_["exec"].asString(); auto cmd = config_["exec"].asString();
FILE* fp(popen(cmd.c_str(), "r")); pid_ = -1;
if (!fp) { fp_ = util::command::open(cmd, pid_);
if (!fp_) {
throw std::runtime_error("Unable to open " + cmd); throw std::runtime_error("Unable to open " + cmd);
} }
thread_ = [this, fp] { thread_ = [&] {
char* buff = nullptr; char* buff = nullptr;
size_t len = 0; size_t len = 0;
if (getline(&buff, &len, fp) == -1) { if (getline(&buff, &len, fp_) == -1) {
pclose(fp); int exit_code = 1;
if (fp_) {
exit_code = WEXITSTATUS(util::command::close(fp_, pid_));
fp_ = nullptr;
}
thread_.stop(); thread_.stop();
output_ = { 1, "" }; if (exit_code != 0) {
dp.emit(); output_ = {exit_code, ""};
dp.emit();
std::cerr << name_ + " just stopped unexpectedly, is it endless?" << std::endl;
}
return; return;
} }
std::string output = buff; std::string output = buff;
// Remove last newline // Remove last newline
if (!output.empty() && output[output.length()-1] == '\n') { if (!output.empty() && output[output.length() - 1] == '\n') {
output.erase(output.length()-1); output.erase(output.length() - 1);
} }
output_ = { 0, output }; output_ = {0, output};
dp.emit(); dp.emit();
}; };
} }
auto waybar::modules::Custom::update() -> void void waybar::modules::Custom::refresh(int sig /*signal*/) {
{ if (sig == SIGRTMIN + config_["signal"].asInt()) {
// Hide label if output is empty thread_.wake_up();
if (output_.out.empty() || output_.exit_code != 0) { }
label_.hide(); }
label_.set_name("");
} else {
label_.set_name("custom-" + name_);
auto waybar::modules::Custom::update() -> void {
// Hide label if output is empty
if (config_["exec"].isString() && (output_.out.empty() || output_.exit_code != 0)) {
event_box_.hide();
} else {
if (config_["return-type"].asString() == "json") { if (config_["return-type"].asString() == "json") {
parseOutputJson(); parseOutputJson();
} else { } else {
parseOutputRaw(); parseOutputRaw();
} }
auto str = fmt::format(format_, text_); auto str = fmt::format(format_,
label_.set_text(str); text_,
if (text_ == tooltip_) { fmt::arg("alt", alt_),
label_.set_tooltip_text(str); fmt::arg("icon", getIcon(percentage_, alt_)),
} else { fmt::arg("percentage", percentage_));
label_.set_tooltip_text(tooltip_); label_.set_markup(str);
} if (tooltipEnabled()) {
if (class_ != "") { if (text_ == tooltip_) {
if (prevclass_ != "") { label_.set_tooltip_text(str);
label_.get_style_context()->remove_class(prevclass_); } else {
label_.set_tooltip_text(tooltip_);
} }
label_.get_style_context()->add_class(class_); }
prevclass_ = class_; auto classes = label_.get_style_context()->list_classes();
} else { for (auto const& c : classes) {
label_.get_style_context()->remove_class(prevclass_); label_.get_style_context()->remove_class(c);
prevclass_ = ""; }
for (auto const& c : class_) {
label_.get_style_context()->add_class(c);
} }
label_.show(); event_box_.show();
} }
} }
void waybar::modules::Custom::parseOutputRaw() void waybar::modules::Custom::parseOutputRaw() {
{
std::istringstream output(output_.out); std::istringstream output(output_.out);
std::string line; std::string line;
int i = 0; int i = 0;
while (getline(output, line)) { while (getline(output, line)) {
if (i == 0) { if (i == 0) {
text_ = line; if (config_["escape"].isBool() && config_["escape"].asBool()) {
text_ = Glib::Markup::escape_text(line);
} else {
text_ = line;
}
tooltip_ = line; tooltip_ = line;
class_ = ""; class_.clear();
} else if (i == 1) { } else if (i == 1) {
tooltip_ = line; tooltip_ = line;
} else if (i == 2) { } else if (i == 2) {
class_ = line; class_.push_back(line);
} else { } else {
break; break;
} }
@ -122,15 +139,35 @@ void waybar::modules::Custom::parseOutputRaw()
} }
} }
void waybar::modules::Custom::parseOutputJson() void waybar::modules::Custom::parseOutputJson() {
{
std::istringstream output(output_.out); std::istringstream output(output_.out);
std::string line; std::string line;
class_.clear();
while (getline(output, line)) { while (getline(output, line)) {
auto parsed = parser_.parse(line); auto parsed = parser_.parse(line);
text_ = parsed["text"].asString(); 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(); tooltip_ = parsed["tooltip"].asString();
class_ = parsed["class"].asString(); if (parsed["class"].isString()) {
class_.push_back(parsed["class"].asString());
} else if (parsed["class"].isArray()) {
for (auto const& c : parsed["class"]) {
class_.push_back(c.asString());
}
}
if (!parsed["percentage"].asString().empty() && parsed["percentage"].isUInt()) {
percentage_ = parsed["percentage"].asUInt();
} else {
percentage_ = 0;
}
break; 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,46 +1,46 @@
#include "modules/memory.hpp" #include "modules/memory.hpp"
waybar::modules::Memory::Memory(const Json::Value& config) waybar::modules::Memory::Memory(const std::string& id, const Json::Value& config)
: ALabel(config, "{}%") : ALabel(config, "{}%", 30) {
{ label_.set_name("memory");
uint32_t interval = config_["interval"].isUInt() ? config_["interval"].asUInt() : 30; if (!id.empty()) {
thread_ = [this, interval] { label_.get_style_context()->add_class(id);
}
thread_ = [this] {
dp.emit(); dp.emit();
thread_.sleep_for(chrono::seconds(interval)); thread_.sleep_for(interval_);
}; };
} }
auto waybar::modules::Memory::update() -> void auto waybar::modules::Memory::update() -> void {
{
parseMeminfo(); parseMeminfo();
if (memtotal_ > 0 && memfree_ >= 0) { if (memtotal_ > 0 && memfree_ >= 0) {
int used_ram_percentage = 100 * (memtotal_ - memfree_) / memtotal_; int used_ram_percentage = 100 * (memtotal_ - memfree_) / memtotal_;
label_.set_text(fmt::format(format_, used_ram_percentage)); label_.set_markup(fmt::format(format_, used_ram_percentage));
auto used_ram_gigabytes = (memtotal_ - memfree_) / std::pow(1024, 2); auto used_ram_gigabytes = (memtotal_ - memfree_) / std::pow(1024, 2);
label_.set_tooltip_text(fmt::format("{:.{}f}Gb used", used_ram_gigabytes, 1)); if (tooltipEnabled()) {
label_.set_name("memory"); label_.set_tooltip_text(fmt::format("{:.{}f}Gb used", used_ram_gigabytes, 1));
label_.show(); }
event_box_.show();
} else { } else {
label_.set_name(""); event_box_.hide();
label_.hide();
} }
} }
void waybar::modules::Memory::parseMeminfo() void waybar::modules::Memory::parseMeminfo() {
{ int64_t memfree = -1, membuffer = -1, memcache = -1, memavail = -1;
long memfree = -1, membuffer = -1, memcache = -1, memavail = -1;
std::ifstream info(data_dir_); std::ifstream info(data_dir_);
if (!info.is_open()) { if (!info.is_open()) {
throw std::runtime_error("Can't open " + data_dir_); throw std::runtime_error("Can't open " + data_dir_);
} }
std::string line; std::string line;
while (getline(info, line)) { while (getline(info, line)) {
auto posDelim = line.find(":"); auto posDelim = line.find(':');
if (posDelim == std::string::npos) { if (posDelim == std::string::npos) {
continue; continue;
} }
std::string name = line.substr(0, posDelim); std::string name = line.substr(0, posDelim);
long value = std::stol(line.substr(posDelim + 1)); int64_t value = std::stol(line.substr(posDelim + 1));
if (name.compare("MemTotal") == 0) { if (name.compare("MemTotal") == 0) {
memtotal_ = value; memtotal_ = value;
@ -53,8 +53,7 @@ void waybar::modules::Memory::parseMeminfo()
} else if (name.compare("Cached") == 0) { } else if (name.compare("Cached") == 0) {
memcache = value; memcache = value;
} }
if (memtotal_ > 0 && if (memtotal_ > 0 && (memavail >= 0 || (memfree > -1 && membuffer > -1 && memcache > -1))) {
(memavail >= 0 || (memfree > -1 && membuffer > -1 && memcache > -1))) {
break; break;
} }
} }

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,19 +1,20 @@
#include "modules/network.hpp" #include "modules/network.hpp"
#include <sys/eventfd.h>
waybar::modules::Network::Network(const Json::Value& config) waybar::modules::Network::Network(const std::string &id, const Json::Value &config)
: ALabel(config, "{ifname}"), family_(AF_INET), : ALabel(config, "{ifname}", 60),
signal_strength_dbm_(0), signal_strength_(0) family_(AF_INET),
{ efd_(-1),
sock_fd_ = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); ev_fd_(-1),
if (sock_fd_ < 0) { cidr_(-1),
throw std::runtime_error("Can't open network socket"); signal_strength_dbm_(0),
} signal_strength_(0) {
nladdr_.nl_family = AF_NETLINK; label_.set_name("network");
nladdr_.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR; if (!id.empty()) {
if (bind(sock_fd_, reinterpret_cast<struct sockaddr *>(&nladdr_), label_.get_style_context()->add_class(id);
sizeof(nladdr_)) != 0) {
throw std::runtime_error("Can't bind network socket");
} }
createInfoSocket();
createEventSocket();
if (config_["interface"].isString()) { if (config_["interface"].isString()) {
ifid_ = if_nametoindex(config_["interface"].asCString()); ifid_ = if_nametoindex(config_["interface"].asCString());
ifname_ = config_["interface"].asString(); ifname_ = config_["interface"].asString();
@ -26,145 +27,203 @@ waybar::modules::Network::Network(const Json::Value& config)
char ifname[IF_NAMESIZE]; char ifname[IF_NAMESIZE];
if_indextoname(ifid_, ifname); if_indextoname(ifid_, ifname);
ifname_ = ifname; ifname_ = ifname;
getInterfaceAddress();
} }
} }
initNL80211(); dp.emit();
label_.set_name("network");
// Trigger first values
getInfo();
update();
worker(); worker();
} }
waybar::modules::Network::~Network() waybar::modules::Network::~Network() {
{ if (ev_fd_ > -1) {
close(sock_fd_); eventfd_write(ev_fd_, 1);
nl_socket_free(sk_); 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::worker() void waybar::modules::Network::createInfoSocket() {
{ info_sock_ = nl_socket_alloc();
thread_ = [this] { if (nl_connect(info_sock_, NETLINK_ROUTE) != 0) {
char buf[4096]; throw std::runtime_error("Can't connect network socket");
uint64_t len = netlinkResponse(sock_fd_, buf, sizeof(buf), }
RTMGRP_LINK | RTMGRP_IPV4_IFADDR); if (nl_socket_add_membership(info_sock_, RTMGRP_LINK) != 0) {
bool need_update = false; throw std::runtime_error("Can't add membership");
for (auto nh = reinterpret_cast<struct nlmsghdr *>(buf); NLMSG_OK(nh, len); }
nh = NLMSG_NEXT(nh, len)) { if (nl_socket_add_membership(info_sock_, RTMGRP_IPV4_IFADDR) != 0) {
if (nh->nlmsg_type == NLMSG_DONE) { throw std::runtime_error("Can't add membership");
break; }
} nl_socket_disable_seq_check(info_sock_);
if (nh->nlmsg_type == NLMSG_ERROR) { nl_socket_set_nonblocking(info_sock_);
continue; nl_socket_modify_cb(info_sock_, NL_CB_VALID, NL_CB_CUSTOM, handleEvents, this);
} efd_ = epoll_create1(EPOLL_CLOEXEC);
if (nh->nlmsg_type < RTM_NEWADDR) { if (efd_ < 0) {
auto rtif = static_cast<struct ifinfomsg *>(NLMSG_DATA(nh)); throw std::runtime_error("Can't create epoll");
if (rtif->ifi_index == static_cast<int>(ifid_)) { }
need_update = true; {
if (!(rtif->ifi_flags & IFF_RUNNING)) { ev_fd_ = eventfd(0, EFD_NONBLOCK);
disconnected(); 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");
} }
if (ifid_ <= 0 && !config_["interface"].isString()) { }
// Need to wait before get external interface {
thread_.sleep_for(std::chrono::seconds(1)); auto fd = nl_socket_get_fd(info_sock_);
ifid_ = getExternalInterface(); struct epoll_event event = {0};
if (ifid_ > 0) { event.events = EPOLLIN | EPOLLET | EPOLLRDHUP;
char ifname[IF_NAMESIZE]; event.data.fd = fd;
if_indextoname(ifid_, ifname); if (epoll_ctl(efd_, EPOLL_CTL_ADD, fd, &event) == -1) {
ifname_ = ifname; throw std::runtime_error("Can't add epoll event");
getInterfaceAddress();
need_update = true;
}
} }
if (need_update) { }
getInfo(); }
dp.emit();
} void waybar::modules::Network::createEventSocket() {
}; sk_ = nl_socket_alloc();
uint32_t interval = config_["interval"].isUInt() ? config_["interval"].asUInt() : 60; if (genl_connect(sk_) != 0) {
thread_timer_ = [this, interval] { throw std::runtime_error("Can't connect to netlink socket");
thread_.sleep_for(std::chrono::seconds(interval)); }
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) { if (ifid_ > 0) {
getInfo(); getInfo();
dp.emit(); 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 auto waybar::modules::Network::update() -> void {
{ std::string connectiontype;
auto format = format_; std::string tooltip_format = "";
if (ifid_ <= 0) { if (config_["tooltip-format"].isString()) {
format = config_["format-disconnected"].isString() tooltip_format = config_["tooltip-format"].asString();
? config_["format-disconnected"].asString() : format; }
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"); label_.get_style_context()->add_class("disconnected");
connectiontype = "disconnected";
} else { } else {
if (essid_.empty()) { if (essid_.empty()) {
format = config_["format-ethernet"].isString() if (config_["format-ethernet"].isString()) {
? config_["format-ethernet"].asString() : format; default_format_ = config_["format-ethernet"].asString();
}
if (config_["tooltip-format-ethernet"].isString()) {
tooltip_format = config_["tooltip-format-ethernet"].asString();
}
connectiontype = "ethernet";
} else { } else {
format = config_["format-wifi"].isString() if (config_["format-wifi"].isString()) {
? config_["format-wifi"].asString() : format; 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"); label_.get_style_context()->remove_class("disconnected");
} }
label_.set_text(fmt::format(format, if (!alt_) {
fmt::arg("essid", essid_), format_ = default_format_;
fmt::arg("signaldBm", signal_strength_dbm_), }
fmt::arg("signalStrength", signal_strength_), auto text = fmt::format(format_,
fmt::arg("ifname", ifname_), fmt::arg("essid", essid_),
fmt::arg("netmask", netmask_), fmt::arg("signaldBm", signal_strength_dbm_),
fmt::arg("ipaddr", ipaddr_), fmt::arg("signalStrength", signal_strength_),
fmt::arg("cidr", cidr_) 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() void waybar::modules::Network::disconnected() {
{
essid_.clear(); essid_.clear();
signal_strength_dbm_ = 0; signal_strength_dbm_ = 0;
signal_strength_ = 0; signal_strength_ = 0;
ipaddr_.clear(); ipaddr_.clear();
netmask_.clear(); netmask_.clear();
cidr_ = 0; cidr_ = 0;
ifname_.clear(); if (!config_["interface"].isString()) {
ifid_ = -1; ifname_.clear();
} ifid_ = -1;
void waybar::modules::Network::initNL80211()
{
sk_ = nl_socket_alloc();
if (genl_connect(sk_) != 0) {
nl_socket_free(sk_);
throw std::runtime_error("Can't connect to netlink socket");
}
if (nl_socket_modify_cb(sk_, NL_CB_VALID, NL_CB_CUSTOM, scanCb, this) < 0) {
nl_socket_free(sk_);
throw std::runtime_error("Can't connect to netlink socket");
}
nl80211_id_ = genl_ctrl_resolve(sk_, "nl80211");
if (nl80211_id_ < 0) {
nl_socket_free(sk_);
throw std::runtime_error("Can't resolve nl80211 interface");
} }
// Need to wait otherwise we'll have the same information
thread_.sleep_for(std::chrono::seconds(1));
} }
// Based on https://gist.github.com/Yawning/c70d804d4b8ae78cc698 // Based on https://gist.github.com/Yawning/c70d804d4b8ae78cc698
int waybar::modules::Network::getExternalInterface() int waybar::modules::Network::getExternalInterface() {
{
static const uint32_t route_buffer_size = 8192; static const uint32_t route_buffer_size = 8192;
struct nlmsghdr *hdr = nullptr; struct nlmsghdr * hdr = nullptr;
struct rtmsg *rt = nullptr; struct rtmsg * rt = nullptr;
char resp[route_buffer_size] = {0}; char resp[route_buffer_size] = {0};
int ifidx = -1; int ifidx = -1;
/* Prepare request. */ /* Prepare request. */
constexpr uint32_t reqlen = NLMSG_SPACE(sizeof(*rt)); constexpr uint32_t reqlen = NLMSG_SPACE(sizeof(*rt));
char req[reqlen] = {0}; char req[reqlen] = {0};
/* Build the RTM_GETROUTE request. */ /* Build the RTM_GETROUTE request. */
hdr = reinterpret_cast<struct nlmsghdr *>(req); hdr = reinterpret_cast<struct nlmsghdr *>(req);
@ -176,7 +235,7 @@ int waybar::modules::Network::getExternalInterface()
rt->rtm_table = RT_TABLE_MAIN; rt->rtm_table = RT_TABLE_MAIN;
/* Issue the query. */ /* Issue the query. */
if (netlinkRequest(sock_fd_, req, reqlen) < 0) { if (netlinkRequest(req, reqlen) < 0) {
goto out; goto out;
} }
@ -186,14 +245,14 @@ int waybar::modules::Network::getExternalInterface()
* consume responses till NLMSG_DONE/NLMSG_ERROR is encountered). * consume responses till NLMSG_DONE/NLMSG_ERROR is encountered).
*/ */
do { do {
auto len = netlinkResponse(sock_fd_, resp, route_buffer_size); auto len = netlinkResponse(resp, route_buffer_size);
if (len < 0) { if (len < 0) {
goto out; goto out;
} }
/* Parse the response payload into netlink messages. */ /* Parse the response payload into netlink messages. */
for (hdr = reinterpret_cast<struct nlmsghdr *>(resp); NLMSG_OK(hdr, len); for (hdr = reinterpret_cast<struct nlmsghdr *>(resp); NLMSG_OK(hdr, len);
hdr = NLMSG_NEXT(hdr, len)) { hdr = NLMSG_NEXT(hdr, len)) {
if (hdr->nlmsg_type == NLMSG_DONE) { if (hdr->nlmsg_type == NLMSG_DONE) {
goto out; goto out;
} }
@ -220,10 +279,10 @@ int waybar::modules::Network::getExternalInterface()
/* Parse all the attributes for a single routing table entry. */ /* Parse all the attributes for a single routing table entry. */
struct rtattr *attr = RTM_RTA(rt); struct rtattr *attr = RTM_RTA(rt);
uint64_t attrlen = RTM_PAYLOAD(hdr); uint64_t attrlen = RTM_PAYLOAD(hdr);
bool has_gateway = false; bool has_gateway = false;
bool has_destination = false; bool has_destination = false;
int temp_idx = -1; int temp_idx = -1;
for (; RTA_OK(attr, attrlen); attr = RTA_NEXT(attr, attrlen)) { for (; RTA_OK(attr, attrlen); attr = RTA_NEXT(attr, attrlen)) {
/* Determine if this routing table entry corresponds to the default /* Determine if this routing table entry corresponds to the default
* route by seeing if it has a gateway, and if a destination addr is * route by seeing if it has a gateway, and if a destination addr is
@ -243,8 +302,8 @@ int waybar::modules::Network::getExternalInterface()
* Should be either missing, or maybe all 0s. Accept both. * Should be either missing, or maybe all 0s. Accept both.
*/ */
const uint32_t nr_zeroes = (family_ == AF_INET) ? 4 : 16; const uint32_t nr_zeroes = (family_ == AF_INET) ? 4 : 16;
unsigned char c = 0; unsigned char c = 0;
size_t dstlen = RTA_PAYLOAD(attr); size_t dstlen = RTA_PAYLOAD(attr);
if (dstlen != nr_zeroes) { if (dstlen != nr_zeroes) {
break; break;
} }
@ -256,7 +315,7 @@ int waybar::modules::Network::getExternalInterface()
} }
case RTA_OIF: case RTA_OIF:
/* The output interface index. */ /* The output interface index. */
temp_idx = *static_cast<int*>(RTA_DATA(attr)); temp_idx = *static_cast<int *>(RTA_DATA(attr));
break; break;
default: default:
break; break;
@ -277,21 +336,24 @@ out:
} }
void waybar::modules::Network::getInterfaceAddress() { void waybar::modules::Network::getInterfaceAddress() {
unsigned int cidrRaw; unsigned int cidrRaw;
struct ifaddrs *ifaddr, *ifa; struct ifaddrs *ifaddr, *ifa;
ipaddr_.clear();
netmask_.clear();
cidr_ = 0;
int success = getifaddrs(&ifaddr); int success = getifaddrs(&ifaddr);
if (success == 0) { if (success == 0) {
ifa = ifaddr; ifa = ifaddr;
while (ifa != nullptr && ipaddr_.empty() && netmask_.empty()) { while (ifa != nullptr && ipaddr_.empty() && netmask_.empty()) {
if (ifa->ifa_addr != nullptr && ifa->ifa_addr->sa_family == family_) { if (ifa->ifa_addr != nullptr && ifa->ifa_addr->sa_family == family_) {
if (strcmp(ifa->ifa_name, ifname_.c_str()) == 0) { if (strcmp(ifa->ifa_name, ifname_.c_str()) == 0) {
ipaddr_ = inet_ntoa(((struct sockaddr_in*)ifa->ifa_addr)->sin_addr); ipaddr_ = inet_ntoa(((struct sockaddr_in *)ifa->ifa_addr)->sin_addr);
netmask_ = inet_ntoa(((struct sockaddr_in*)ifa->ifa_netmask)->sin_addr); netmask_ = inet_ntoa(((struct sockaddr_in *)ifa->ifa_netmask)->sin_addr);
cidrRaw = ((struct sockaddr_in *)(ifa->ifa_netmask))->sin_addr.s_addr; cidrRaw = ((struct sockaddr_in *)(ifa->ifa_netmask))->sin_addr.s_addr;
unsigned int cidr = 0; unsigned int cidr = 0;
while (cidrRaw) { while (cidrRaw) {
cidr += cidrRaw & 1; cidr += cidrRaw & 1;
cidrRaw >>= 1; cidrRaw >>= 1;
} }
cidr_ = cidr; cidr_ = cidr;
} }
@ -299,45 +361,82 @@ void waybar::modules::Network::getInterfaceAddress() {
ifa = ifa->ifa_next; ifa = ifa->ifa_next;
} }
freeifaddrs(ifaddr); freeifaddrs(ifaddr);
} else {
ipaddr_.clear();
netmask_.clear();
cidr_ = 0;
} }
} }
int waybar::modules::Network::netlinkRequest(int fd, void *req, int waybar::modules::Network::netlinkRequest(void *req, uint32_t reqlen, uint32_t groups) {
uint32_t reqlen, uint32_t groups)
{
struct sockaddr_nl sa = {}; struct sockaddr_nl sa = {};
sa.nl_family = AF_NETLINK; sa.nl_family = AF_NETLINK;
sa.nl_groups = groups; sa.nl_groups = groups;
struct iovec iov = { req, reqlen }; struct iovec iov = {req, reqlen};
struct msghdr msg = { &sa, sizeof(sa), &iov, 1, nullptr, 0, 0 }; struct msghdr msg = {&sa, sizeof(sa), &iov, 1, nullptr, 0, 0};
return sendmsg(fd, &msg, 0); return sendmsg(nl_socket_get_fd(info_sock_), &msg, 0);
} }
int waybar::modules::Network::netlinkResponse(int fd, void *resp, int waybar::modules::Network::netlinkResponse(void *resp, uint32_t resplen, uint32_t groups) {
uint32_t resplen, uint32_t groups)
{
int ret;
struct sockaddr_nl sa = {}; struct sockaddr_nl sa = {};
sa.nl_family = AF_NETLINK; sa.nl_family = AF_NETLINK;
sa.nl_groups = groups; sa.nl_groups = groups;
struct iovec iov = { resp, resplen }; struct iovec iov = {resp, resplen};
struct msghdr msg = { &sa, sizeof(sa), &iov, 1, nullptr, 0, 0 }; struct msghdr msg = {&sa, sizeof(sa), &iov, 1, nullptr, 0, 0};
ret = recvmsg(fd, &msg, 0); auto ret = recvmsg(nl_socket_get_fd(info_sock_), &msg, 0);
if (msg.msg_flags & MSG_TRUNC) { if (msg.msg_flags & MSG_TRUNC) {
return -1; return -1;
} }
return ret; return ret;
} }
int waybar::modules::Network::scanCb(struct nl_msg *msg, void *data) { int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
int ret = 0;
auto net = static_cast<waybar::modules::Network *>(data); auto net = static_cast<waybar::modules::Network *>(data);
auto gnlh = static_cast<genlmsghdr *>(nlmsg_data(nlmsg_hdr(msg))); bool need_update = false;
struct nlattr* tb[NL80211_ATTR_MAX + 1]; for (nlmsghdr *nh = nlmsg_hdr(msg); NLMSG_OK(nh, ret); nh = NLMSG_NEXT(nh, ret)) {
struct nlattr* bss[NL80211_BSS_MAX + 1]; if (nh->nlmsg_type == RTM_NEWADDR) {
need_update = true;
}
if (nh->nlmsg_type < RTM_NEWADDR) {
auto rtif = static_cast<struct ifinfomsg *>(NLMSG_DATA(nh));
if (rtif->ifi_index == static_cast<int>(net->ifid_)) {
need_update = true;
if (!(rtif->ifi_flags & IFF_RUNNING)) {
net->disconnected();
net->dp.emit();
return NL_SKIP;
}
}
}
if (need_update) break;
}
if (net->ifid_ <= 0 && !net->config_["interface"].isString()) {
for (uint8_t i = 0; i < MAX_RETRY; i += 1) {
net->ifid_ = net->getExternalInterface();
if (net->ifid_ > 0) {
break;
}
// Need to wait before get external interface
net->thread_.sleep_for(std::chrono::seconds(1));
}
if (net->ifid_ > 0) {
char ifname[IF_NAMESIZE];
if_indextoname(net->ifid_, ifname);
net->ifname_ = ifname;
need_update = true;
}
}
if (need_update) {
if (net->ifid_ > 0) {
net->getInfo();
}
net->dp.emit();
}
return NL_SKIP;
}
int waybar::modules::Network::handleScan(struct nl_msg *msg, void *data) {
auto net = static_cast<waybar::modules::Network *>(data);
auto gnlh = static_cast<genlmsghdr *>(nlmsg_data(nlmsg_hdr(msg)));
struct nlattr * tb[NL80211_ATTR_MAX + 1];
struct nlattr * bss[NL80211_BSS_MAX + 1];
struct nla_policy bss_policy[NL80211_BSS_MAX + 1]{}; struct nla_policy bss_policy[NL80211_BSS_MAX + 1]{};
bss_policy[NL80211_BSS_TSF].type = NLA_U64; bss_policy[NL80211_BSS_TSF].type = NLA_U64;
bss_policy[NL80211_BSS_FREQUENCY].type = NLA_U32; bss_policy[NL80211_BSS_FREQUENCY].type = NLA_U32;
@ -349,7 +448,8 @@ int waybar::modules::Network::scanCb(struct nl_msg *msg, void *data) {
bss_policy[NL80211_BSS_SIGNAL_UNSPEC].type = NLA_U8; bss_policy[NL80211_BSS_SIGNAL_UNSPEC].type = NLA_U8;
bss_policy[NL80211_BSS_STATUS].type = NLA_U32; bss_policy[NL80211_BSS_STATUS].type = NLA_U32;
if (nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), nullptr) < 0) { if (nla_parse(
tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), nullptr) < 0) {
return NL_SKIP; return NL_SKIP;
} }
if (tb[NL80211_ATTR_BSS] == nullptr) { if (tb[NL80211_ATTR_BSS] == nullptr) {
@ -367,42 +467,43 @@ int waybar::modules::Network::scanCb(struct nl_msg *msg, void *data) {
return NL_SKIP; return NL_SKIP;
} }
void waybar::modules::Network::parseEssid(struct nlattr **bss) void waybar::modules::Network::parseEssid(struct nlattr **bss) {
{
essid_.clear(); essid_.clear();
if (bss[NL80211_BSS_INFORMATION_ELEMENTS] != nullptr) { if (bss[NL80211_BSS_INFORMATION_ELEMENTS] != nullptr) {
auto ies = auto ies = static_cast<char *>(nla_data(bss[NL80211_BSS_INFORMATION_ELEMENTS]));
static_cast<char*>(nla_data(bss[NL80211_BSS_INFORMATION_ELEMENTS])); auto ies_len = nla_len(bss[NL80211_BSS_INFORMATION_ELEMENTS]);
auto ies_len = nla_len(bss[NL80211_BSS_INFORMATION_ELEMENTS]);
const auto hdr_len = 2; const auto hdr_len = 2;
while (ies_len > hdr_len && ies[0] != 0) { while (ies_len > hdr_len && ies[0] != 0) {
ies_len -= ies[1] + hdr_len; ies_len -= ies[1] + hdr_len;
ies += ies[1] + hdr_len; ies += ies[1] + hdr_len;
} }
if (ies_len > hdr_len && ies_len > ies[1] + hdr_len) { if (ies_len > hdr_len && ies_len > ies[1] + hdr_len) {
auto essid_begin = ies + hdr_len; auto essid_begin = ies + hdr_len;
auto essid_end = essid_begin + ies[1]; auto essid_end = essid_begin + ies[1];
std::copy(essid_begin, essid_end, std::back_inserter(essid_)); std::string essid_raw;
std::copy(essid_begin, essid_end, std::back_inserter(essid_raw));
essid_ = Glib::Markup::escape_text(essid_raw);
} }
} }
} }
void waybar::modules::Network::parseSignal(struct nlattr **bss) { void waybar::modules::Network::parseSignal(struct nlattr **bss) {
if (bss[NL80211_BSS_SIGNAL_MBM] != nullptr) { if (bss[NL80211_BSS_SIGNAL_MBM] != nullptr) {
// signalstrength in dBm // signalstrength in dBm from mBm
signal_strength_dbm_ = signal_strength_dbm_ = nla_get_s32(bss[NL80211_BSS_SIGNAL_MBM]) / 100;
static_cast<int>(nla_get_u32(bss[NL80211_BSS_SIGNAL_MBM])) / 100;
// WiFi-hardware usually operates in the range -90 to -20dBm. // WiFi-hardware usually operates in the range -90 to -20dBm.
const int hardwareMax = -20; const int hardwareMax = -20;
const int hardwareMin = -90; const int hardwareMin = -90;
signal_strength_ = ((signal_strength_dbm_ - hardwareMin) signal_strength_ =
/ double{hardwareMax - hardwareMin}) * 100; ((signal_strength_dbm_ - hardwareMin) / double{hardwareMax - hardwareMin}) * 100;
}
if (bss[NL80211_BSS_SIGNAL_UNSPEC] != nullptr) {
signal_strength_ = nla_get_u8(bss[NL80211_BSS_SIGNAL_UNSPEC]);
} }
} }
bool waybar::modules::Network::associatedOrJoined(struct nlattr** bss) bool waybar::modules::Network::associatedOrJoined(struct nlattr **bss) {
{
if (bss[NL80211_BSS_STATUS] == nullptr) { if (bss[NL80211_BSS_STATUS] == nullptr) {
return false; return false;
} }
@ -417,16 +518,16 @@ bool waybar::modules::Network::associatedOrJoined(struct nlattr** bss)
} }
} }
auto waybar::modules::Network::getInfo() -> void auto waybar::modules::Network::getInfo() -> void {
{ getInterfaceAddress();
struct nl_msg* nl_msg = nlmsg_alloc(); struct nl_msg *nl_msg = nlmsg_alloc();
if (nl_msg == nullptr) { if (nl_msg == nullptr) {
nl_socket_free(sk_);
return; return;
} }
if (genlmsg_put(nl_msg, NL_AUTO_PORT, NL_AUTO_SEQ, nl80211_id_, 0, NLM_F_DUMP, if (genlmsg_put(
NL80211_CMD_GET_SCAN, 0) == nullptr nl_msg, NL_AUTO_PORT, NL_AUTO_SEQ, nl80211_id_, 0, NLM_F_DUMP, NL80211_CMD_GET_SCAN, 0) ==
|| nla_put_u32(nl_msg, NL80211_ATTR_IFINDEX, ifid_) < 0) { nullptr ||
nla_put_u32(nl_msg, NL80211_ATTR_IFINDEX, ifid_) < 0) {
nlmsg_free(nl_msg); nlmsg_free(nl_msg);
return; return;
} }

View File

@ -1,6 +1,7 @@
#include "modules/pulseaudio.hpp" #include "modules/pulseaudio.hpp"
#include <array>
waybar::modules::Pulseaudio::Pulseaudio(const Json::Value &config) waybar::modules::Pulseaudio::Pulseaudio(const std::string &id, const Json::Value &config)
: ALabel(config, "{volume}%"), : ALabel(config, "{volume}%"),
mainloop_(nullptr), mainloop_(nullptr),
mainloop_api_(nullptr), mainloop_api_(nullptr),
@ -10,6 +11,9 @@ waybar::modules::Pulseaudio::Pulseaudio(const Json::Value &config)
muted_(false), muted_(false),
scrolling_(false) { scrolling_(false) {
label_.set_name("pulseaudio"); label_.set_name("pulseaudio");
if (!id.empty()) {
label_.get_style_context()->add_class(id);
}
mainloop_ = pa_threaded_mainloop_new(); mainloop_ = pa_threaded_mainloop_new();
if (mainloop_ == nullptr) { if (mainloop_ == nullptr) {
throw std::runtime_error("pa_mainloop_new() failed."); throw std::runtime_error("pa_mainloop_new() failed.");
@ -20,10 +24,9 @@ waybar::modules::Pulseaudio::Pulseaudio(const Json::Value &config)
if (context_ == nullptr) { if (context_ == nullptr) {
throw std::runtime_error("pa_context_new() failed."); throw std::runtime_error("pa_context_new() failed.");
} }
if (pa_context_connect(context_, nullptr, PA_CONTEXT_NOAUTOSPAWN, if (pa_context_connect(context_, nullptr, PA_CONTEXT_NOAUTOSPAWN, nullptr) < 0) {
nullptr) < 0) { auto err =
auto err = fmt::format("pa_context_connect() failed: {}", fmt::format("pa_context_connect() failed: {}", pa_strerror(pa_context_errno(context_)));
pa_strerror(pa_context_errno(context_)));
throw std::runtime_error(err); throw std::runtime_error(err);
} }
pa_context_set_state_callback(context_, contextStateCb, this); pa_context_set_state_callback(context_, contextStateCb, this);
@ -34,11 +37,9 @@ waybar::modules::Pulseaudio::Pulseaudio(const Json::Value &config)
// define the pulse scroll events only when no user provided // define the pulse scroll events only when no user provided
// events are configured // events are configured
if (!config["on-scroll-up"].isString() && if (!config["on-scroll-up"].isString() && !config["on-scroll-down"].isString()) {
!config["on-scroll-down"].isString()) { event_box_.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);
event_box_.add_events(Gdk::SCROLL_MASK); event_box_.signal_scroll_event().connect(sigc::mem_fun(*this, &Pulseaudio::handleScroll));
event_box_.signal_scroll_event().connect(
sigc::mem_fun(*this, &Pulseaudio::handleScroll));
} }
} }
@ -48,8 +49,7 @@ waybar::modules::Pulseaudio::~Pulseaudio() {
pa_threaded_mainloop_free(mainloop_); pa_threaded_mainloop_free(mainloop_);
} }
void waybar::modules::Pulseaudio::contextStateCb(pa_context *c, void *data) void waybar::modules::Pulseaudio::contextStateCb(pa_context *c, void *data) {
{
auto pa = static_cast<waybar::modules::Pulseaudio *>(data); auto pa = static_cast<waybar::modules::Pulseaudio *>(data);
switch (pa_context_get_state(c)) { switch (pa_context_get_state(c)) {
case PA_CONTEXT_TERMINATED: case PA_CONTEXT_TERMINATED:
@ -60,21 +60,21 @@ void waybar::modules::Pulseaudio::contextStateCb(pa_context *c, void *data)
pa_context_set_subscribe_callback(c, subscribeCb, data); pa_context_set_subscribe_callback(c, subscribeCb, data);
pa_context_subscribe(c, PA_SUBSCRIPTION_MASK_SINK, nullptr, nullptr); pa_context_subscribe(c, PA_SUBSCRIPTION_MASK_SINK, nullptr, nullptr);
break; break;
case PA_CONTEXT_FAILED:
pa->mainloop_api_->quit(pa->mainloop_api_, 1);
break;
case PA_CONTEXT_CONNECTING: case PA_CONTEXT_CONNECTING:
case PA_CONTEXT_AUTHORIZING: case PA_CONTEXT_AUTHORIZING:
case PA_CONTEXT_SETTING_NAME: case PA_CONTEXT_SETTING_NAME:
break;
case PA_CONTEXT_FAILED:
default: default:
pa->mainloop_api_->quit(pa->mainloop_api_, 1);
break; break;
} }
} }
bool waybar::modules::Pulseaudio::handleScroll(GdkEventScroll *e) { bool waybar::modules::Pulseaudio::handleScroll(GdkEventScroll *e) {
// Avoid concurrent scroll event // Avoid concurrent scroll event
bool direction_up = false; bool direction_up = false;
uint16_t change = config_["scroll-step"].isUInt() ? config_["scroll-step"].asUInt() * 100 : 100; uint16_t change = config_["scroll-step"].isUInt() ? config_["scroll-step"].asUInt() * 100 : 100;
pa_cvolume pa_volume = pa_volume_; pa_cvolume pa_volume = pa_volume_;
if (scrolling_) { if (scrolling_) {
@ -90,8 +90,7 @@ bool waybar::modules::Pulseaudio::handleScroll(GdkEventScroll *e) {
if (e->direction == GDK_SCROLL_SMOOTH) { if (e->direction == GDK_SCROLL_SMOOTH) {
gdouble delta_x, delta_y; gdouble delta_x, delta_y;
gdk_event_get_scroll_deltas(reinterpret_cast<const GdkEvent *>(e), &delta_x, gdk_event_get_scroll_deltas(reinterpret_cast<const GdkEvent *>(e), &delta_x, &delta_y);
&delta_y);
if (delta_y < 0) { if (delta_y < 0) {
direction_up = true; direction_up = true;
} else if (delta_y > 0) { } else if (delta_y > 0) {
@ -100,13 +99,16 @@ bool waybar::modules::Pulseaudio::handleScroll(GdkEventScroll *e) {
} }
if (direction_up) { if (direction_up) {
if (volume_ + 1 < 100) pa_cvolume_inc(&pa_volume, change); if (volume_ + 1 < 100) {
pa_cvolume_inc(&pa_volume, change);
}
} else { } else {
if (volume_ - 1 > 0) pa_cvolume_dec(&pa_volume, change); if (volume_ - 1 > 0) {
pa_cvolume_dec(&pa_volume, change);
}
} }
pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume, pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume, volumeModifyCb, this);
volumeModifyCb, this);
return true; return true;
} }
@ -114,48 +116,39 @@ bool waybar::modules::Pulseaudio::handleScroll(GdkEventScroll *e) {
/* /*
* Called when an event we subscribed to occurs. * Called when an event we subscribed to occurs.
*/ */
void waybar::modules::Pulseaudio::subscribeCb(pa_context* context, void waybar::modules::Pulseaudio::subscribeCb(pa_context * context,
pa_subscription_event_type_t type, uint32_t idx, void* data) pa_subscription_event_type_t type, uint32_t idx,
{ void *data) {
unsigned facility = type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK; unsigned facility = type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK;
if (facility == PA_SUBSCRIPTION_EVENT_SINK) {
switch (facility) { pa_context_get_sink_info_by_index(context, idx, sinkInfoCb, data);
case PA_SUBSCRIPTION_EVENT_SINK:
pa_context_get_sink_info_by_index(context, idx, sinkInfoCb, data);
break;
default:
break;
} }
} }
/* /*
* Called in response to a volume change request * Called in response to a volume change request
*/ */
void waybar::modules::Pulseaudio::volumeModifyCb(pa_context *c, int success, void waybar::modules::Pulseaudio::volumeModifyCb(pa_context *c, int success, void *data) {
void *data) {
auto pa = static_cast<waybar::modules::Pulseaudio *>(data); auto pa = static_cast<waybar::modules::Pulseaudio *>(data);
if (success) { if (success != 0) {
pa_context_get_sink_info_by_index(pa->context_, pa->sink_idx_, sinkInfoCb, pa_context_get_sink_info_by_index(pa->context_, pa->sink_idx_, sinkInfoCb, data);
data);
} }
} }
/* /*
* Called when the requested sink information is ready. * Called when the requested sink information is ready.
*/ */
void waybar::modules::Pulseaudio::sinkInfoCb(pa_context * /*context*/, void waybar::modules::Pulseaudio::sinkInfoCb(pa_context * /*context*/, const pa_sink_info *i,
const pa_sink_info *i, int /*eol*/, int /*eol*/, void * data) {
void *data) {
if (i != nullptr) { if (i != nullptr) {
auto pa = static_cast<waybar::modules::Pulseaudio *>(data); auto pa = static_cast<waybar::modules::Pulseaudio *>(data);
pa->pa_volume_ = i->volume; pa->pa_volume_ = i->volume;
float volume = static_cast<float>(pa_cvolume_avg(&(pa->pa_volume_))) / float volume = static_cast<float>(pa_cvolume_avg(&(pa->pa_volume_))) / float{PA_VOLUME_NORM};
float{PA_VOLUME_NORM};
pa->sink_idx_ = i->index; pa->sink_idx_ = i->index;
pa->volume_ = std::round(volume * 100.0f); pa->volume_ = std::round(volume * 100.0F);
pa->muted_ = i->mute != 0; pa->muted_ = i->mute != 0;
pa->desc_ = i->description; pa->desc_ = i->description;
pa->port_name_ = i->active_port ? i->active_port->name : "Unknown"; pa->port_name_ = i->active_port != nullptr ? i->active_port->name : "Unknown";
pa->dp.emit(); pa->dp.emit();
} }
} }
@ -164,16 +157,12 @@ void waybar::modules::Pulseaudio::sinkInfoCb(pa_context * /*context*/,
* Called when the requested information on the server is ready. This is * Called when the requested information on the server is ready. This is
* used to find the default PulseAudio sink. * used to find the default PulseAudio sink.
*/ */
void waybar::modules::Pulseaudio::serverInfoCb(pa_context *context, void waybar::modules::Pulseaudio::serverInfoCb(pa_context *context, const pa_server_info *i,
const pa_server_info *i, void *data) void *data) {
{ pa_context_get_sink_info_by_name(context, i->default_sink_name, sinkInfoCb, data);
pa_context_get_sink_info_by_name(context, i->default_sink_name,
sinkInfoCb, data);
} }
const std::string waybar::modules::Pulseaudio::getPortIcon() const static const std::array<std::string, 9> ports = {
{
std::vector<std::string> ports = {
"headphones", "headphones",
"speaker", "speaker",
"hdmi", "hdmi",
@ -183,34 +172,39 @@ const std::string waybar::modules::Pulseaudio::getPortIcon() const
"car", "car",
"hifi", "hifi",
"phone", "phone",
}; };
for (auto const& port : ports) {
if (port_name_.find(port) != std::string::npos) { const std::string waybar::modules::Pulseaudio::getPortIcon() const {
std::string nameLC = port_name_;
std::transform(nameLC.begin(), nameLC.end(), nameLC.begin(), ::tolower);
for (auto const &port : ports) {
if (nameLC.find(port) != std::string::npos) {
return port; return port;
} }
} }
return ""; return port_name_;
} }
auto waybar::modules::Pulseaudio::update() -> void auto waybar::modules::Pulseaudio::update() -> void {
{
auto format = format_; auto format = format_;
if (muted_) { if (muted_) {
format = format = config_["format-muted"].isString() ? config_["format-muted"].asString() : format;
config_["format-muted"].isString() ? config_["format-muted"].asString() : format;
label_.get_style_context()->add_class("muted"); label_.get_style_context()->add_class("muted");
} else if (port_name_.find("a2dp_sink") != std::string::npos) {
format = config_["format-bluetooth"].isString()
? config_["format-bluetooth"].asString() : format;
label_.get_style_context()->add_class("bluetooth");
} else { } else {
label_.get_style_context()->remove_class("muted"); label_.get_style_context()->remove_class("muted");
label_.get_style_context()->add_class("bluetooth"); if (port_name_.find("a2dp_sink") != std::string::npos) {
format =
config_["format-bluetooth"].isString() ? config_["format-bluetooth"].asString() : format;
label_.get_style_context()->add_class("bluetooth");
} else {
label_.get_style_context()->remove_class("bluetooth");
}
}
label_.set_markup(fmt::format(
format, fmt::arg("volume", volume_), fmt::arg("icon", getIcon(volume_, getPortIcon()))));
if (tooltipEnabled()) {
label_.set_tooltip_text(desc_);
} }
label_.set_label(
fmt::format(format, fmt::arg("volume", volume_),
fmt::arg("icon", getIcon(volume_, getPortIcon()))));
label_.set_tooltip_text(desc_);
if (scrolling_) { if (scrolling_) {
scrolling_ = false; 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

View File

@ -1,151 +0,0 @@
#include "modules/sni/snh.hpp"
#include <iostream>
using namespace waybar::modules::SNI;
Host::Host(Glib::Dispatcher* dp, const Json::Value &config)
: dp_(dp), config_(config)
{
GBusNameOwnerFlags flags = static_cast<GBusNameOwnerFlags>(
G_BUS_NAME_OWNER_FLAGS_NONE);
bus_name_ = "org.kde.StatusNotifierHost-" + std::to_string(getpid());
object_path_ = "/StatusNotifierHost";
bus_name_id_ = g_bus_own_name(G_BUS_TYPE_SESSION,
bus_name_.c_str(), flags,
&Host::busAcquired, nullptr, nullptr, this, nullptr);
}
void Host::busAcquired(GDBusConnection* connection,
const gchar* name, gpointer data)
{
auto host = static_cast<SNI::Host *>(data);
host->watcher_id_ = g_bus_watch_name(
G_BUS_TYPE_SESSION,
"org.kde.StatusNotifierWatcher",
G_BUS_NAME_WATCHER_FLAGS_NONE,
&Host::nameAppeared, &Host::nameVanished, data, nullptr);
}
void Host::nameAppeared(GDBusConnection* connection,
const gchar* name, const gchar* name_owner, gpointer data)
{
auto host = static_cast<SNI::Host *>(data);
if (host->cancellable_ != nullptr) {
// TODO
return;
}
host->cancellable_ = g_cancellable_new();
sn_watcher_proxy_new(
connection,
G_DBUS_PROXY_FLAGS_NONE,
"org.kde.StatusNotifierWatcher",
"/StatusNotifierWatcher",
host->cancellable_, &Host::proxyReady, data);
}
void Host::nameVanished(GDBusConnection* connection,
const gchar* name, gpointer data)
{
auto host = static_cast<SNI::Host *>(data);
g_cancellable_cancel(host->cancellable_);
g_clear_object(&host->cancellable_);
g_clear_object(&host->watcher_);
host->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) {
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->items.erase(it);
break;
}
}
host->dp_->emit();
}
std::tuple<std::string, std::string> Host::getBusNameAndObjectPath(
const gchar* service)
{
std::string bus_name;
std::string object_path;
gchar* tmp = g_strstr_len(service, -1, "/");
if (tmp != nullptr) {
gchar** str = g_strsplit(service, "/", 2);
bus_name = str[0];
object_path = tmp;
g_strfreev(str);
} else {
bus_name = service;
object_path = "/StatusNotifierItem";
}
return { bus_name, object_path };
}
void Host::addRegisteredItem(const gchar* service)
{
auto [bus_name, object_path] = getBusNameAndObjectPath(service);
items.emplace_back(bus_name, object_path, dp_, config_);
}

View File

@ -1,278 +0,0 @@
#include "modules/sni/sni.hpp"
#include <iostream>
#include <libdbusmenu-gtk/dbusmenu-gtk.h>
waybar::modules::SNI::Item::Item(std::string bn, std::string op,
Glib::Dispatcher *dp, Json::Value config)
: bus_name(bn), object_path(op), event_box(), icon_size(16),
effective_icon_size(0), image(Gtk::manage(new Gtk::Image())),
dp_(dp), config_(config)
{
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_ = g_cancellable_new();
sn_item_proxy_new_for_bus(
G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_NONE, bus_name.c_str(),
object_path.c_str(), cancellable_, &Item::proxyReady, this);
}
void waybar::modules::SNI::Item::proxyReady(GObject *obj, GAsyncResult *res,
gpointer data) {
GError *error = nullptr;
SnItem *proxy = sn_item_proxy_new_for_bus_finish(res, &error);
if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
g_error_free(error);
return;
}
auto item = static_cast<SNI::Item *>(data);
item->proxy_ = proxy;
if (error) {
std::cerr << error->message << std::endl;
g_error_free(error);
return;
}
auto conn = g_dbus_proxy_get_connection(G_DBUS_PROXY(proxy));
g_dbus_connection_call(conn, item->bus_name.c_str(),
item->object_path.c_str(),
"org.freedesktop.DBus.Properties", "GetAll",
g_variant_new("(s)", "org.kde.StatusNotifierItem"),
G_VARIANT_TYPE("(a{sv})"), G_DBUS_CALL_FLAGS_NONE, -1,
item->cancellable_, &Item::getAll, data);
}
void waybar::modules::SNI::Item::getAll(GObject *obj, GAsyncResult *res,
gpointer data) {
GError *error = nullptr;
auto conn = G_DBUS_CONNECTION(obj);
GVariant *properties = g_dbus_connection_call_finish(conn, res, &error);
if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
g_error_free(error);
return;
}
auto item = static_cast<SNI::Item *>(data);
if (error) {
std::cerr << error->message << std::endl;
g_error_free(error);
return;
}
GVariantIter *it = nullptr;
g_variant_get(properties, "(a{sv})", &it);
gchar *key;
GVariant *value;
while (g_variant_iter_next(it, "{sv}", &key, &value)) {
if (g_strcmp0(key, "Category") == 0) {
item->category = g_variant_dup_string(value, nullptr);
} else if (g_strcmp0(key, "Id") == 0) {
item->id = g_variant_dup_string(value, nullptr);
} else if (g_strcmp0(key, "Title") == 0) {
item->title = g_variant_dup_string(value, nullptr);
} else if (g_strcmp0(key, "Status") == 0) {
item->status = g_variant_dup_string(value, nullptr);
} else if (g_strcmp0(key, "WindowId") == 0) {
item->window_id = g_variant_get_int32(value);
} else if (g_strcmp0(key, "IconName") == 0) {
item->icon_name = g_variant_dup_string(value, nullptr);
} else if (g_strcmp0(key, "IconPixmap") == 0) {
item->icon_pixmap = item->extractPixBuf(value);
} else if (g_strcmp0(key, "OverlayIconName") == 0) {
item->overlay_icon_name = g_variant_dup_string(value, nullptr);
} else if (g_strcmp0(key, "OverlayIconPixmap") == 0) {
// TODO: overlay_icon_pixmap
} else if (g_strcmp0(key, "AttentionIconName") == 0) {
item->attention_icon_name = g_variant_dup_string(value, nullptr);
} else if (g_strcmp0(key, "AttentionIconPixmap") == 0) {
// TODO: attention_icon_pixmap
} else if (g_strcmp0(key, "AttentionMovieName") == 0) {
item->attention_movie_name = g_variant_dup_string(value, nullptr);
} else if (g_strcmp0(key, "ToolTip") == 0) {
// TODO: tooltip
} else if (g_strcmp0(key, "IconThemePath") == 0) {
item->icon_theme_path = g_variant_dup_string(value, nullptr);
} else if (g_strcmp0(key, "Menu") == 0) {
item->menu = g_variant_dup_string(value, nullptr);
} else if (g_strcmp0(key, "ItemIsMenu") == 0) {
item->item_is_menu = g_variant_get_boolean(value);
}
g_variant_unref(value);
g_free(key);
}
g_variant_iter_free(it);
g_variant_unref(properties);
if (item->id.empty() || item->category.empty() || item->status.empty()) {
std::cerr << "Invalid Status Notifier Item: " + item->bus_name + "," +
item->object_path
<< std::endl;
return;
}
if (!item->icon_theme_path.empty()) {
GtkIconTheme *icon_theme = gtk_icon_theme_get_default();
gtk_icon_theme_append_search_path(icon_theme,
item->icon_theme_path.c_str());
}
item->updateImage();
item->updateMenu();
item->dp_->emit();
// TODO: handle change
}
Glib::RefPtr<Gdk::Pixbuf>
waybar::modules::SNI::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);
}
return Glib::RefPtr<Gdk::Pixbuf>{};
}
void waybar::modules::SNI::Item::updateMenu()
{
event_box.set_tooltip_text(title);
if (gtk_menu == nullptr && !menu.empty()) {
auto dbmenu = dbusmenu_gtkmenu_new(bus_name.data(), menu.data());
if (dbmenu != nullptr) {
g_object_ref_sink(dbmenu);
gtk_menu = Glib::wrap(GTK_MENU(dbmenu), false);
gtk_menu->attach_to_widget(event_box);
}
}
}
void waybar::modules::SNI::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>
waybar::modules::SNI::Item::getIconByName(std::string name, int request_size) {
int tmp_size = 0;
Glib::RefPtr<Gtk::IconTheme> icon_theme = Gtk::IconTheme::get_default();
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 || size > tmp_size) {
tmp_size = size;
}
}
if (tmp_size == 0) {
tmp_size = request_size;
}
return icon_theme->load_icon(name.c_str(), tmp_size,
Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE);
}
void waybar::modules::SNI::Item::handleActivate(GObject *src, GAsyncResult *res,
gpointer data) {
auto item = static_cast<SNI::Item *>(data);
sn_item_call_activate_finish(item->proxy_, res, nullptr);
}
void waybar::modules::SNI::Item::handleSecondaryActivate(GObject *src,
GAsyncResult *res,
gpointer data) {
auto item = static_cast<SNI::Item *>(data);
sn_item_call_secondary_activate_finish(item->proxy_, res, nullptr);
}
bool waybar::modules::SNI::Item::handleClick(GdkEventButton *const &ev) {
if (ev->type == GDK_BUTTON_PRESS) {
if (gtk_menu && gtk_menu->get_children().size() > 0) {
#if GTK_CHECK_VERSION(3, 22, 0)
gtk_menu->popup_at_widget(reinterpret_cast<Gtk::Widget*>(&event_box),
Gdk::GRAVITY_NORTH_WEST, Gdk::GRAVITY_NORTH_WEST,
reinterpret_cast<GdkEvent*>(ev));
#else
gtk_menu->popup(ev->button, ev->time);
#endif
gtk_menu->set_state_flags(Gtk::STATE_FLAG_ACTIVE, false);
} else {
sn_item_call_activate(
proxy_, ev->x, ev->y, nullptr, &Item::handleActivate, this);
}
} else if (ev->type == GDK_2BUTTON_PRESS) {
sn_item_call_secondary_activate(
proxy_, ev->x, ev->y, nullptr, &Item::handleSecondaryActivate, this);
} else {
return false;
}
return true;
}

View File

@ -1,167 +0,0 @@
#include "modules/sni/snw.hpp"
#include <iostream>
using namespace waybar::modules::SNI;
Watcher::Watcher()
{
GBusNameOwnerFlags flags = static_cast<GBusNameOwnerFlags>(
G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT
| G_BUS_NAME_OWNER_FLAGS_REPLACE);
bus_name_id_ = g_bus_own_name(G_BUS_TYPE_SESSION,
"org.kde.StatusNotifierWatcher", flags,
&Watcher::busAcquired, nullptr, nullptr, this, nullptr);
watcher_ = sn_watcher_skeleton_new();
sn_watcher_set_protocol_version(watcher_, 1);
}
Watcher::~Watcher()
{
}
void Watcher::busAcquired(GDBusConnection* connection, const gchar* name,
gpointer data)
{
GError* error = nullptr;
auto host = static_cast<SNI::Watcher*>(data);
g_dbus_interface_skeleton_export(G_DBUS_INTERFACE_SKELETON(host->watcher_),
connection, "/StatusNotifierWatcher", &error);
if (error != nullptr) {
std::cerr << error->message << std::endl;
g_error_free(error);
return;
}
g_signal_connect_swapped(host->watcher_, "handle-register-item",
G_CALLBACK(&Watcher::handleRegisterItem), data);
g_signal_connect_swapped(host->watcher_, "handle-register-host",
G_CALLBACK(&Watcher::handleRegisterHost), data);
}
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)) {
GfWatch* 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;
}
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)) {
GfWatch* watch = static_cast<GfWatch*>(l->data);
gchar* item = g_strdup_printf("%s%s", watch->bus_name, watch->object_path);
g_variant_builder_add(&builder, "s", item);
g_free(item);
}
GVariant* variant = g_variant_builder_end(&builder);
const gchar** items = g_variant_get_strv(variant, nullptr);
sn_watcher_set_registered_items(obj, items);
g_variant_unref(variant);
g_free(items);
}

View File

@ -1,30 +1,42 @@
#include "modules/sni/tray.hpp" #include "modules/sni/tray.hpp"
#include <iostream> #include <iostream>
waybar::modules::SNI::Tray::Tray(const Json::Value &config) namespace waybar::modules::SNI {
: config_(config), watcher_(), host_(&dp, config)
{ Tray::Tray(const std::string& id, const Bar& bar, const Json::Value& config)
std::cout << "Tray is in beta, so there may be bugs or even be unusable." << std::endl; : 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()) { if (config_["spacing"].isUInt()) {
box_.set_spacing(config_["spacing"].asUInt()); box_.set_spacing(config_["spacing"].asUInt());
} }
nb_hosts_ += 1;
dp.emit();
} }
auto waybar::modules::SNI::Tray::update() -> void { void Tray::onAdd(std::unique_ptr<Item>& item) {
auto childrens = box_.get_children(); box_.pack_start(item->event_box);
childrens.erase(childrens.begin(), childrens.end()); dp.emit();
for (auto &item : host_.items) { }
box_.pack_start(item.event_box);
} void Tray::onRemove(std::unique_ptr<Item>& item) {
if (box_.get_children().size() > 0) { box_.remove(item->event_box);
box_.set_name("tray"); dp.emit();
box_.show_all(); }
auto Tray::update() -> void {
if (box_.get_children().empty()) {
box_.hide();
} else { } else {
box_.set_name(""); box_.show_all();
} }
} }
waybar::modules::SNI::Tray::operator Gtk::Widget &() { Tray::operator Gtk::Widget&() { return box_; }
return box_;
} }

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

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

View File

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

View File

@ -1,43 +1,49 @@
#include "modules/sway/mode.hpp" #include "modules/sway/mode.hpp"
waybar::modules::sway::Mode::Mode(Bar& bar, const Json::Value& config) namespace waybar::modules::sway {
: ALabel(config, "{}"), bar_(bar)
{ Mode::Mode(const std::string& id, const Bar& bar, const Json::Value& config)
ipc_.connect(); : ALabel(config, "{}"), bar_(bar) {
ipc_.subscribe("[ \"mode\" ]"); 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 // Launch worker
worker(); worker();
dp.emit();
} }
void waybar::modules::sway::Mode::worker() 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] { thread_ = [this] {
try { try {
auto res = ipc_.handleEvent(); ipc_.handleEvent();
auto parsed = parser_.parse(res.payload);
if ((parsed["change"]) != "default" ) {
mode_ = parsed["change"].asString();
dp.emit();
}
else if ((parsed["change"]) == "default" ) {
mode_.clear();
dp.emit();
}
} catch (const std::exception& e) { } catch (const std::exception& e) {
std::cerr << e.what() << std::endl; std::cerr << "Mode: " << e.what() << std::endl;
} }
}; };
} }
auto waybar::modules::sway::Mode::update() -> void auto Mode::update() -> void {
{
if (mode_.empty()) { if (mode_.empty()) {
label_.set_name(""); event_box_.hide();
label_.hide();
} else { } else {
label_.set_name("mode"); label_.set_markup(fmt::format(format_, mode_));
label_.set_text(fmt::format(format_, mode_)); if (tooltipEnabled()) {
label_.set_tooltip_text(mode_); label_.set_tooltip_text(mode_);
label_.show(); }
event_box_.show();
} }
} }
} // namespace waybar::modules::sway

View File

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

View File

@ -1,147 +1,141 @@
#include "modules/sway/workspaces.hpp" #include "modules/sway/workspaces.hpp"
waybar::modules::sway::Workspaces::Workspaces(Bar& bar, namespace waybar::modules::sway {
const Json::Value& config)
: bar_(bar), config_(config), scrolling_(false) Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value &config)
{ : bar_(bar),
config_(config),
box_(bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0),
scrolling_(false) {
box_.set_name("workspaces"); box_.set_name("workspaces");
ipc_.connect(); if (!id.empty()) {
ipc_.subscribe("[ \"workspace\" ]"); 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 // Launch worker
worker(); worker();
} }
void waybar::modules::sway::Workspaces::worker() void Workspaces::onEvent(const struct Ipc::ipc_response &res) { ipc_.sendCmd(IPC_GET_WORKSPACES); }
{
void Workspaces::onCmd(const struct Ipc::ipc_response &res) {
if (res.type == IPC_GET_WORKSPACES) {
if (res.payload.isArray()) {
std::lock_guard<std::mutex> lock(mutex_);
workspaces_.clear();
std::copy_if(res.payload.begin(),
res.payload.end(),
std::back_inserter(workspaces_),
[&](const auto &workspace) {
return !config_["all-outputs"].asBool()
? workspace["output"].asString() == bar_.output->name
: true;
});
dp.emit();
}
} else {
if (scrolling_) {
scrolling_ = false;
}
}
}
void Workspaces::worker() {
thread_ = [this] { thread_ = [this] {
try { try {
// Wait for the name of the output ipc_.handleEvent();
if (!config_["all-outputs"].asBool() && bar_.output_name.empty()) { } catch (const std::exception &e) {
while (bar_.output_name.empty()) { std::cerr << "Workspaces: " << e.what() << std::endl;
thread_.sleep_for(chrono::milliseconds(150));
}
} else if (!workspaces_.empty()) {
ipc_.handleEvent();
}
{
std::lock_guard<std::mutex> lock(mutex_);
auto res = ipc_.sendCmd(IPC_GET_WORKSPACES);
workspaces_ = parser_.parse(res.payload);
}
dp.emit();
} catch (const std::exception& e) {
std::cerr << e.what() << std::endl;
} }
}; };
} }
auto waybar::modules::sway::Workspaces::update() -> void bool Workspaces::filterButtons() {
{
bool needReorder = false; bool needReorder = false;
std::lock_guard<std::mutex> lock(mutex_);
for (auto it = buttons_.begin(); it != buttons_.end();) { for (auto it = buttons_.begin(); it != buttons_.end();) {
auto ws = std::find_if(workspaces_.begin(), workspaces_.end(), auto ws = std::find_if(workspaces_.begin(), workspaces_.end(), [it](const auto &node) {
[it](auto node) -> bool { return node["name"].asString() == it->first; }); return node["name"].asString() == it->first;
});
if (ws == workspaces_.end() || if (ws == workspaces_.end() ||
(!config_["all-outputs"].asBool() && (!config_["all-outputs"].asBool() && (*ws)["output"].asString() != bar_.output->name)) {
(*ws)["output"].asString() != bar_.output_name)) {
it = buttons_.erase(it); it = buttons_.erase(it);
needReorder = true; needReorder = true;
} else { } else {
++it; ++it;
} }
} }
for (auto const& node : workspaces_) { return needReorder;
if (!config_["all-outputs"].asBool() }
&& bar_.output_name != node["output"].asString()) {
continue; auto Workspaces::update() -> void {
} std::lock_guard<std::mutex> lock(mutex_);
auto it = buttons_.find(node["name"].asString()); bool needReorder = filterButtons();
if (it == buttons_.end()) { for (auto it = workspaces_.begin(); it != workspaces_.end(); ++it) {
addWorkspace(node); auto bit = buttons_.find((*it)["name"].asString());
if (bit == buttons_.end()) {
needReorder = true; needReorder = true;
} else {
auto &button = it->second;
if (node["focused"].asBool()) {
button.get_style_context()->add_class("focused");
} else {
button.get_style_context()->remove_class("focused");
}
if (node["visible"].asBool()) {
button.get_style_context()->add_class("visible");
} else {
button.get_style_context()->remove_class("visible");
}
if (node["urgent"].asBool()) {
button.get_style_context()->add_class("urgent");
} else {
button.get_style_context()->remove_class("urgent");
}
if (needReorder) {
box_.reorder_child(button, node["num"].asInt());
}
auto icon = getIcon(node["name"].asString(), node);
if (config_["format"].isString()) {
auto format = config_["format"].asString();
button.set_label(fmt::format(format, fmt::arg("icon", icon),
fmt::arg("name", node["name"].asString()),
fmt::arg("index", node["num"].asString())));
} else {
button.set_label(icon);
}
button.show();
} }
} auto &button = bit == buttons_.end() ? addButton(*it) : bit->second;
if (scrolling_) { if ((*it)["focused"].asBool()) {
scrolling_ = false; button.get_style_context()->add_class("focused");
} else {
button.get_style_context()->remove_class("focused");
}
if ((*it)["visible"].asBool()) {
button.get_style_context()->add_class("visible");
} else {
button.get_style_context()->remove_class("visible");
}
if ((*it)["urgent"].asBool()) {
button.get_style_context()->add_class("urgent");
} else {
button.get_style_context()->remove_class("urgent");
}
if (needReorder) {
box_.reorder_child(button, it - workspaces_.begin());
}
std::string output = getIcon((*it)["name"].asString(), *it);
if (config_["format"].isString()) {
auto format = config_["format"].asString();
output = fmt::format(format,
fmt::arg("icon", output),
fmt::arg("name", trimWorkspaceName((*it)["name"].asString())),
fmt::arg("index", (*it)["num"].asString()));
}
if (!config_["disable-markup"].asBool()) {
static_cast<Gtk::Label *>(button.get_children()[0])->set_markup(output);
} else {
button.set_label(output);
}
onButtonReady(*it, button);
} }
} }
void waybar::modules::sway::Workspaces::addWorkspace(Json::Value node) Gtk::Button &Workspaces::addButton(const Json::Value &node) {
{ auto pair = buttons_.emplace(node["name"].asString(), node["name"].asString());
auto icon = getIcon(node["name"].asString(), node);
auto format = config_["format"].isString()
? fmt::format(config_["format"].asString(), fmt::arg("icon", icon),
fmt::arg("name", node["name"].asString()),
fmt::arg("index", node["num"].asString()))
: icon;
auto pair = buttons_.emplace(node["name"].asString(), format);
auto &button = pair.first->second; auto &button = pair.first->second;
box_.pack_start(button, false, false, 0); box_.pack_start(button, false, false, 0);
button.set_relief(Gtk::RELIEF_NONE); button.set_relief(Gtk::RELIEF_NONE);
button.signal_clicked().connect([this, pair] { button.signal_clicked().connect([this, pair] {
try { try {
std::lock_guard<std::mutex> lock(mutex_); ipc_.sendCmd(IPC_COMMAND, fmt::format("workspace \"{}\"", pair.first->first));
auto cmd = fmt::format("workspace \"{}\"", pair.first->first); } catch (const std::exception &e) {
ipc_.sendCmd(IPC_COMMAND, cmd);
} catch (const std::exception& e) {
std::cerr << e.what() << std::endl; std::cerr << e.what() << std::endl;
} }
}); });
button.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);
if (!config_["disable-scroll"].asBool()) { if (!config_["disable-scroll"].asBool()) {
button.signal_scroll_event() button.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);
.connect(sigc::mem_fun(*this, &Workspaces::handleScroll)); button.signal_scroll_event().connect(sigc::mem_fun(*this, &Workspaces::handleScroll));
} }
box_.reorder_child(button, node["num"].asInt()); return button;
if (node["focused"].asBool()) {
button.get_style_context()->add_class("focused");
}
if (node["visible"].asBool()) {
button.get_style_context()->add_class("visible");
}
if (node["urgent"].asBool()) {
button.get_style_context()->add_class("urgent");
}
button.show();
} }
std::string waybar::modules::sway::Workspaces::getIcon(std::string name, std::string Workspaces::getIcon(const std::string &name, const Json::Value &node) {
Json::Value node) std::vector<std::string> keys = {name, "urgent", "focused", "visible", "default"};
{ for (auto const &key : keys) {
std::vector<std::string> keys = {
name, "urgent", "focused", "visible", "default"};
for (auto const& key : keys) {
if (key == "focused" || key == "visible" || key == "urgent") { if (key == "focused" || key == "visible" || key == "urgent") {
if (config_["format-icons"][key].isString() && node[key].asBool()) { if (config_["format-icons"][key].isString() && node[key].asBool()) {
return config_["format-icons"][key].asString(); return config_["format-icons"][key].asString();
@ -153,82 +147,87 @@ std::string waybar::modules::sway::Workspaces::getIcon(std::string name,
return name; return name;
} }
bool waybar::modules::sway::Workspaces::handleScroll(GdkEventScroll *e) bool Workspaces::handleScroll(GdkEventScroll *e) {
{
// Avoid concurrent scroll event // Avoid concurrent scroll event
if (scrolling_) { if (scrolling_) {
return false; return false;
} }
scrolling_ = true;
std::string name; std::string name;
uint16_t idx = 0; scrolling_ = true;
{ {
std::lock_guard<std::mutex> lock(mutex_); std::lock_guard<std::mutex> lock(mutex_);
for (; idx < workspaces_.size(); idx += 1) { auto it = std::find_if(workspaces_.begin(), workspaces_.end(), [](const auto &workspace) {
if (workspaces_[idx]["focused"].asBool()) { return workspace["focused"].asBool();
name = workspaces_[idx]["name"].asString(); });
break; if (it == workspaces_.end()) {
} scrolling_ = false;
} return false;
} }
if (name.empty()) { switch (e->direction) {
scrolling_ = false; case GDK_SCROLL_DOWN:
return false; case GDK_SCROLL_RIGHT:
} name = getCycleWorkspace(it, false);
if (e->direction == GDK_SCROLL_UP) { break;
name = getNextWorkspace(); case GDK_SCROLL_UP:
} case GDK_SCROLL_LEFT:
if (e->direction == GDK_SCROLL_DOWN) { name = getCycleWorkspace(it, true);
name = getPrevWorkspace(); break;
} case GDK_SCROLL_SMOOTH:
if (e->direction == GDK_SCROLL_SMOOTH) { gdouble delta_x, delta_y;
gdouble delta_x, delta_y; gdk_event_get_scroll_deltas(reinterpret_cast<const GdkEvent *>(e), &delta_x, &delta_y);
gdk_event_get_scroll_deltas(reinterpret_cast<const GdkEvent *>(e), if (delta_y < 0) {
&delta_x, &delta_y); name = getCycleWorkspace(it, true);
if (delta_y < 0) { } else if (delta_y > 0) {
name = getNextWorkspace(); name = getCycleWorkspace(it, false);
} else if (delta_y > 0) { }
name = getPrevWorkspace(); break;
} default:
} break;
if (!name.empty()) { }
std::lock_guard<std::mutex> lock(mutex_); if (name.empty() || name == (*it)["name"].asString()) {
if (name == workspaces_[idx]["name"].asString()) {
scrolling_ = false; scrolling_ = false;
return false; return false;
} }
ipc_.sendCmd(IPC_COMMAND, fmt::format("workspace \"{}\"", name));
std::this_thread::sleep_for(std::chrono::milliseconds(150));
} }
ipc_.sendCmd(IPC_COMMAND, fmt::format("workspace \"{}\"", name));
return true; return true;
} }
std::string waybar::modules::sway::Workspaces::getPrevWorkspace() const std::string Workspaces::getCycleWorkspace(std::vector<Json::Value>::iterator it,
{ bool prev) const {
for (uint16_t i = 0; i != workspaces_.size(); i += 1) { if (prev && it == workspaces_.begin()) {
if (workspaces_[i]["focused"].asBool()) { return (*(--workspaces_.end()))["name"].asString();
if (i > 0) {
return workspaces_[i - 1]["name"].asString();
}
return workspaces_[workspaces_.size() - 1]["name"].asString();
}
} }
return ""; if (prev && it != workspaces_.begin())
--it;
else if (!prev && it != workspaces_.end())
++it;
if (!prev && it == workspaces_.end()) {
return (*(++workspaces_.begin()))["name"].asString();
}
return (*it)["name"].asString();
} }
std::string waybar::modules::sway::Workspaces::getNextWorkspace() std::string Workspaces::trimWorkspaceName(std::string name) {
{ std::size_t found = name.find(':');
for (uint16_t i = 0; i != workspaces_.size(); i += 1) { if (found != std::string::npos) {
if (workspaces_[i]["focused"].asBool()) { return name.substr(found + 1);
if (i + 1U < workspaces_.size()) {
return workspaces_[i + 1]["name"].asString();
}
return workspaces_[0]["String"].asString();
}
} }
return ""; return name;
} }
waybar::modules::sway::Workspaces::operator Gtk::Widget &() { void Workspaces::onButtonReady(const Json::Value &node, Gtk::Button &button) {
return box_; if (config_["current-only"].asBool()) {
if (node["focused"].asBool()) {
button.show();
} else {
button.hide();
}
} else {
button.show();
}
} }
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,10 +1,10 @@
[wrap-file] [wrap-file]
directory = fmt-5.2.1 directory = fmt-5.3.0
source_url = https://github.com/fmtlib/fmt/archive/5.2.1.tar.gz source_url = https://github.com/fmtlib/fmt/archive/5.3.0.tar.gz
source_filename = fmt-5.2.1.tar.gz source_filename = fmt-5.3.0.tar.gz
source_hash = 3c812a18e9f72a88631ab4732a97ce9ef5bcbefb3235e9fd465f059ba204359b source_hash = defa24a9af4c622a7134076602070b45721a43c51598c8456ec6f2c4dbb51c89
patch_url = https://wrapdb.mesonbuild.com/v1/projects/fmt/5.2.1/1/get_zip patch_url = https://wrapdb.mesonbuild.com/v1/projects/fmt/5.3.0/1/get_zip
patch_filename = fmt-5.2.1-1-wrap.zip patch_filename = fmt-5.3.0-1-wrap.zip
patch_hash = 7add08bb4e168c0809e88c6aa64ed5c3494b74deb6be12a93e1e4dc5bb3a1fc1 patch_hash = 18f21a3b8833949c35d4ac88a7059577d5fa24b98786e4b1b2d3d81bb811440f