Compare commits

..

205 Commits
0.1.3 ... 0.4.0

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

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

Signed-off-by: Harish Krupo <harishkrupo@gmail.com>
2018-10-30 20:52:23 +05:30
e93c5e7957 Merge pull request #70 from Robinhuett/mode_module
Add module to show sway binding mode
2018-10-30 14:26:43 +01:00
668b7b736c Added default config for sway binding mode 2018-10-30 13:44:44 +01:00
a042eea384 Add module to show sway binding mode 2018-10-30 13:39:30 +01:00
c9a8a07976 fix(window): title on new workspace 2018-10-29 21:52:53 +01:00
4307e4fd8e chore: upgrade fmt to 5.2.0 2018-10-28 14:40:25 +01:00
daf613f8ca feat: add debug about tray beta 2018-10-28 08:43:48 +01:00
69 changed files with 3675 additions and 927 deletions

View File

4
.gitignore vendored
View File

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

18
.travis.yml Normal file
View File

@ -0,0 +1,18 @@
sudo: false
services:
- docker
env:
- distro: debian
- distro: archlinux
# - distro: opensuse
before_install:
- docker pull alexays/waybar:${distro}
script:
- echo FROM alexays/waybar:${distro} > Dockerfile
- echo ADD . /root >> Dockerfile
- docker build -t waybar .
- docker run waybar /bin/sh -c "cd /root && git clone https://github.com/swaywm/wlroots subprojects/wlroots && meson build && ninja -C build"

4
Dockerfiles/archlinux Normal file
View File

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

5
Dockerfiles/debian Normal file
View File

@ -0,0 +1,5 @@
FROM debian:sid
RUN apt-get update && \
apt-get install -y build-essential meson ninja-build git pkg-config libinput10 libinput-dev wayland-protocols libwayland-client0 libwayland-cursor0 libwayland-dev libegl1-mesa-dev libgles2-mesa-dev libgbm-dev libxkbcommon-dev libudev-dev libpixman-1-dev libgtkmm-3.0-dev libjsoncpp-dev && \
apt-get clean

5
Dockerfiles/opensuse Normal file
View File

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

20
Makefile Normal file
View File

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

View File

@ -1,12 +1,11 @@
# 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** **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 on [AUR](https://aur.archlinux.org/packages/waybar-git/)
**Current features** **Current features**
- Sway Workspaces - Sway (Workspaces, Binding mode, Focused window name)
- Sway focused window name
- Tray (Beta) [#21](https://github.com/Alexays/Waybar/issues/21) - Tray (Beta) [#21](https://github.com/Alexays/Waybar/issues/21)
- Local time - Local time
- Battery - Battery
@ -15,9 +14,10 @@
- Memory - Memory
- Cpu load average - Cpu load average
- 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).
@ -25,9 +25,29 @@
```bash ```bash
$ git clone https://github.com/Alexays/Waybar $ git clone https://github.com/Alexays/Waybar
$ 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]
``` ```
Contributions welcome! - have fun :)<br> Contributions welcome! - have fun :)<br>

View File

@ -2,25 +2,35 @@
#include <json/json.h> #include <json/json.h>
#include "IModule.hpp" #include "IModule.hpp"
#include <glibmm/markup.h>
#include <gtkmm/eventbox.h>
#include <gtkmm/label.h>
namespace waybar { 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() = default;
virtual auto update() -> void; virtual auto update() -> void;
virtual std::string getIcon(uint16_t, const std::string& alt = ""); virtual std::string getIcon(uint16_t, const std::string &alt = "");
virtual operator Gtk::Widget &(); virtual operator Gtk::Widget &();
protected:
Gtk::EventBox event_box_; protected:
Gtk::Label label_; bool tooltipEnabled();
const Json::Value& config_;
std::string format_; Gtk::EventBox event_box_;
private: Gtk::Label label_;
bool handleToggle(GdkEventButton* const& ev); const Json::Value &config_;
bool alt = false; std::string format_;
const std::string default_format_; std::mutex mutex_;
const std::chrono::seconds interval_;
bool alt_ = false;
std::string default_format_;
private:
bool handleToggle(GdkEventButton *const &ev);
bool handleScroll(GdkEventScroll *);
}; };
} } // namespace waybar

View File

@ -1,6 +1,8 @@
#pragma once #pragma once
#include <gtkmm.h> #include <glibmm/dispatcher.h>
#include <gtkmm/box.h>
#include <gtkmm/widget.h>
namespace waybar { namespace waybar {

View File

@ -1,9 +1,13 @@
#pragma once #pragma once
#include <json/json.h> #include <json/json.h>
#include <gtkmm.h> #include <glibmm/refptr.h>
#include <gtkmm/main.h>
#include <gtkmm/cssprovider.h>
#include <gtkmm/window.h>
#include "wlr-layer-shell-unstable-v1-client-protocol.h" #include "wlr-layer-shell-unstable-v1-client-protocol.h"
#include "xdg-output-unstable-v1-client-protocol.h" #include "xdg-output-unstable-v1-client-protocol.h"
#include "idle-inhibit-unstable-v1-client-protocol.h"
#include "IModule.hpp" #include "IModule.hpp"
namespace waybar { namespace waybar {
@ -15,6 +19,7 @@ class Bar {
public: public:
Bar(const Client&, std::unique_ptr<struct wl_output *>&&, uint32_t); Bar(const Client&, std::unique_ptr<struct wl_output *>&&, uint32_t);
Bar(const Bar&) = delete; Bar(const Bar&) = delete;
~Bar() = default;
auto toggle() -> void; auto toggle() -> void;
@ -40,6 +45,9 @@ class Bar {
static void layerSurfaceHandleClosed(void *, static void layerSurfaceHandleClosed(void *,
struct zwlr_layer_surface_v1 *); struct zwlr_layer_surface_v1 *);
void initBar();
bool isValidOutput(const Json::Value &config);
void destroyOutput();
auto setupConfig() -> void; auto setupConfig() -> void;
auto setupWidgets() -> void; auto setupWidgets() -> void;
auto setupCss() -> void; auto setupCss() -> void;
@ -51,6 +59,10 @@ class Bar {
Glib::RefPtr<Gtk::StyleContext> style_context_; Glib::RefPtr<Gtk::StyleContext> style_context_;
Glib::RefPtr<Gtk::CssProvider> css_provider_; Glib::RefPtr<Gtk::CssProvider> css_provider_;
struct zxdg_output_v1 *xdg_output_; struct zxdg_output_v1 *xdg_output_;
Gtk::Box left_;
Gtk::Box center_;
Gtk::Box right_;
Gtk::Box box_;
std::vector<std::unique_ptr<waybar::IModule>> modules_left_; std::vector<std::unique_ptr<waybar::IModule>> modules_left_;
std::vector<std::unique_ptr<waybar::IModule>> modules_center_; std::vector<std::unique_ptr<waybar::IModule>> modules_center_;
std::vector<std::unique_ptr<waybar::IModule>> modules_right_; std::vector<std::unique_ptr<waybar::IModule>> modules_right_;

View File

@ -15,7 +15,7 @@ class Client {
Client(int argc, char *argv[]); Client(int argc, char *argv[]);
int main(int argc, char *argv[]); int main(int argc, char *argv[]);
Glib::RefPtr<Gtk::Application> gtk_app; Gtk::Main gtk_main;
std::string css_file; std::string css_file;
std::string config_file; std::string config_file;
Glib::RefPtr<Gdk::Display> gdk_display; Glib::RefPtr<Gdk::Display> gdk_display;
@ -24,11 +24,13 @@ class Client {
struct zwlr_layer_shell_v1 *layer_shell = nullptr; struct zwlr_layer_shell_v1 *layer_shell = nullptr;
struct zxdg_output_manager_v1 *xdg_output_manager = nullptr; struct zxdg_output_manager_v1 *xdg_output_manager = nullptr;
struct wl_seat *seat = nullptr; struct wl_seat *seat = nullptr;
struct zwp_idle_inhibit_manager_v1 *idle_inhibit_manager = nullptr;
std::vector<std::unique_ptr<Bar>> bars; std::vector<std::unique_ptr<Bar>> bars;
private: private:
void setupConfigs(const std::string& config, const std::string& style);
void bindInterfaces(); void bindInterfaces();
auto setupCss(); const std::string getValidPath(std::vector<std::string> paths);
static void handleGlobal(void *data, struct wl_registry *registry, static void handleGlobal(void *data, struct wl_registry *registry,
uint32_t name, const char *interface, uint32_t version); uint32_t name, const char *interface, uint32_t version);

View File

@ -3,9 +3,11 @@
#include <json/json.h> #include <json/json.h>
#include "modules/clock.hpp" #include "modules/clock.hpp"
#ifdef HAVE_SWAY #ifdef HAVE_SWAY
#include "modules/sway/mode.hpp"
#include "modules/sway/workspaces.hpp" #include "modules/sway/workspaces.hpp"
#include "modules/sway/window.hpp" #include "modules/sway/window.hpp"
#endif #endif
#include "modules/idle_inhibitor.hpp"
#include "modules/battery.hpp" #include "modules/battery.hpp"
#include "modules/memory.hpp" #include "modules/memory.hpp"
#include "modules/cpu.hpp" #include "modules/cpu.hpp"
@ -15,6 +17,9 @@
#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
@ -26,10 +31,10 @@ 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: private:
Bar& bar_; const Bar& bar_;
const Json::Value& config_; const Json::Value& config_;
}; };

View File

@ -0,0 +1,67 @@
#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,32 +1,47 @@
#pragma once #pragma once
#ifdef FILESYSTEM_EXPERIMENTAL
#include <experimental/filesystem>
#else
#include <filesystem> #include <filesystem>
#endif
#include <fstream> #include <fstream>
#include <iostream> #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 "util/sleeper_thread.hpp"
#include "ALabel.hpp" #include "ALabel.hpp"
namespace waybar::modules { namespace waybar::modules {
#ifdef FILESYSTEM_EXPERIMENTAL
namespace fs = std::experimental::filesystem;
#else
namespace fs = std::filesystem; namespace fs = std::filesystem;
#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: private:
static inline const fs::path data_dir_ = "/sys/class/power_supply/"; static inline const fs::path data_dir_ = "/sys/class/power_supply/";
void getBatteries();
void worker(); 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_;
util::SleeperThread threadTimer_; util::SleeperThread thread_timer_;
std::vector<fs::path> batteries_; std::vector<fs::path> batteries_;
fs::path adapter_;
int fd_; int fd_;
std::vector<int> wds_;
std::string old_status_;
}; };
} }

View File

@ -2,14 +2,15 @@
#include <fmt/format.h> #include <fmt/format.h>
#include "fmt/time.h" #include "fmt/time.h"
#include "util/chrono.hpp" #include "util/sleeper_thread.hpp"
#include "ALabel.hpp" #include "ALabel.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&);
~Clock() = default;
auto update() -> void; auto update() -> void;
private: private:
waybar::util::SleeperThread thread_; waybar::util::SleeperThread thread_;

View File

@ -2,16 +2,27 @@
#include <fmt/format.h> #include <fmt/format.h>
#include <sys/sysinfo.h> #include <sys/sysinfo.h>
#include "util/chrono.hpp" #include <fstream>
#include <vector>
#include <numeric>
#include <iostream>
#include "util/sleeper_thread.hpp"
#include "ALabel.hpp" #include "ALabel.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&);
~Cpu() = default;
auto update() -> void; auto update() -> void;
private: private:
static inline const std::string data_dir_ = "/proc/stat";
uint16_t getCpuLoad();
std::tuple<uint16_t, std::string> getCpuUsage();
std::vector<std::tuple<size_t, size_t>> parseCpuinfo();
std::vector<std::tuple<size_t, size_t>> prev_times_;
waybar::util::SleeperThread thread_; waybar::util::SleeperThread thread_;
}; };

View File

@ -2,23 +2,35 @@
#include <fmt/format.h> #include <fmt/format.h>
#include <iostream> #include <iostream>
#include "util/chrono.hpp" #include "util/sleeper_thread.hpp"
#include "util/command.hpp" #include "util/command.hpp"
#include "util/json.hpp"
#include "ALabel.hpp" #include "ALabel.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&);
~Custom();
auto update() -> void; auto update() -> void;
private: private:
void delayWorker(); void delayWorker();
void continuousWorker(); void continuousWorker();
void parseOutputRaw();
void parseOutputJson();
const std::string name_; const std::string name_;
std::string text_;
std::string alt_;
std::string tooltip_;
std::string class_;
std::string prevclass_;
int percentage_;
waybar::util::SleeperThread thread_; waybar::util::SleeperThread thread_;
waybar::util::command::res output_; waybar::util::command::res output_;
waybar::util::JsonParser parser_;
FILE* fp_;
}; };
} }

View File

@ -0,0 +1,23 @@
#pragma once
#include <fmt/format.h>
#include "bar.hpp"
#include "client.hpp"
#include "ALabel.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 onClick(GdkEventButton* const& ev);
const Bar& bar_;
std::string status_;
struct zwp_idle_inhibitor_v1 *idle_inhibitor_;
};
}

View File

@ -1,17 +1,22 @@
#pragma once #pragma once
#include <fmt/format.h> #include <fmt/format.h>
#include <sys/sysinfo.h> #include <fstream>
#include "util/chrono.hpp" #include "util/sleeper_thread.hpp"
#include "ALabel.hpp" #include "ALabel.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&);
~Memory() = default;
auto update() -> void; auto update() -> void;
private: private:
static inline const std::string data_dir_ = "/proc/meminfo";
unsigned long memtotal_;
unsigned long memfree_;
void parseMeminfo();
waybar::util::SleeperThread thread_; waybar::util::SleeperThread thread_;
}; };

View File

@ -1,46 +1,61 @@
#pragma once #pragma once
#include <net/if.h> #include <net/if.h>
#include <arpa/inet.h>
#include <ifaddrs.h>
#include <netlink/netlink.h> #include <netlink/netlink.h>
#include <netlink/genl/genl.h> #include <netlink/genl/genl.h>
#include <netlink/genl/ctrl.h> #include <netlink/genl/ctrl.h>
#include <linux/nl80211.h> #include <linux/nl80211.h>
#include <sys/epoll.h>
#include <fmt/format.h> #include <fmt/format.h>
#include "util/chrono.hpp" #include "util/sleeper_thread.hpp"
#include "ALabel.hpp" #include "ALabel.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: private:
static int netlinkRequest(int, void*, uint32_t, uint32_t groups = 0); static const uint8_t MAX_RETRY = 5;
static int netlinkResponse(int, void*, uint32_t, uint32_t groups = 0);
static int scanCb(struct nl_msg*, void*);
static int handleEvents(struct nl_msg*, void*);
static int handleScan(struct nl_msg*, void*);
void worker();
void disconnected(); void disconnected();
void initNL80211(); void createInfoSocket();
void createEventSocket();
int getExternalInterface(); int getExternalInterface();
void getInterfaceAddress();
int netlinkRequest(void*, uint32_t, uint32_t groups = 0);
int netlinkResponse(void*, uint32_t, uint32_t groups = 0);
void parseEssid(struct nlattr**); void parseEssid(struct nlattr**);
void parseSignal(struct nlattr**); void parseSignal(struct nlattr**);
bool associatedOrJoined(struct nlattr**); bool associatedOrJoined(struct nlattr**);
auto getInfo() -> void; auto getInfo() -> void;
waybar::util::SleeperThread thread_; waybar::util::SleeperThread thread_;
waybar::util::SleeperThread thread_timer_;
int ifid_; int ifid_;
sa_family_t family_; sa_family_t family_;
int sock_fd_; struct sockaddr_nl nladdr_ = {0};
struct sockaddr_nl nladdr_ = {};
struct nl_sock* sk_ = nullptr; struct nl_sock* sk_ = nullptr;
struct nl_sock* info_sock_ = nullptr;
int efd_;
int ev_fd_;
int nl80211_id_; int nl80211_id_;
std::string essid_; std::string essid_;
std::string ifname_; std::string ifname_;
int signal_strength_dbm_; std::string ipaddr_;
uint16_t signal_strength_; std::string netmask_;
int cidr_;
int32_t signal_strength_dbm_;
uint8_t signal_strength_;
}; };
} }

View File

@ -1,7 +1,8 @@
#pragma once #pragma once
#include <pulse/pulseaudio.h>
#include <fmt/format.h> #include <fmt/format.h>
#include <pulse/pulseaudio.h>
#include <pulse/volume.h>
#include <algorithm> #include <algorithm>
#include "ALabel.hpp" #include "ALabel.hpp"
@ -9,7 +10,7 @@ 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: private:
@ -18,6 +19,8 @@ class Pulseaudio : public ALabel {
static void contextStateCb(pa_context*, void*); static void contextStateCb(pa_context*, void*);
static void sinkInfoCb(pa_context*, const pa_sink_info*, int, void*); static void sinkInfoCb(pa_context*, const pa_sink_info*, int, void*);
static void serverInfoCb(pa_context*, const pa_server_info*, 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; const std::string getPortIcon() const;
@ -26,9 +29,11 @@ class Pulseaudio : public ALabel {
pa_context* context_; pa_context* context_;
uint32_t sink_idx_{0}; uint32_t sink_idx_{0};
uint16_t volume_; uint16_t volume_;
pa_cvolume pa_volume_;
bool muted_; bool muted_;
std::string port_name_; std::string port_name_;
std::string desc_; std::string desc_;
bool scrolling_;
}; };
} } // namespace waybar::modules

View File

@ -0,0 +1,42 @@
#pragma once
#include <glibmm/refptr.h>
#include <giomm.h>
#include <json/json.h>
#include <tuple>
#include <dbus-status-notifier-watcher.h>
#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_;
};
}

View File

@ -1,24 +1,33 @@
#pragma once #pragma once
#include <dbus-status-notifier-item.h> #include <dbus-status-notifier-item.h>
#include <gtkmm.h> #include <glibmm/refptr.h>
#include <gtkmm/eventbox.h>
#include <gtkmm/image.h>
#include <gtkmm/icontheme.h>
#include <gtkmm/menu.h>
#include <json/json.h> #include <json/json.h>
#include <libdbusmenu-gtk/dbusmenu-gtk.h>
#ifdef FILESYSTEM_EXPERIMENTAL
#include <experimental/filesystem>
#else
#include <filesystem> #include <filesystem>
#endif
namespace waybar::modules::SNI { namespace waybar::modules::SNI {
class Item { class Item {
public: public:
Item(std::string, std::string, Glib::Dispatcher*, Json::Value); Item(std::string, std::string, const Json::Value&);
~Item() = default;
std::string bus_name; std::string bus_name;
std::string object_path; std::string object_path;
Gtk::EventBox event_box;
int icon_size; int icon_size;
int effective_icon_size; int effective_icon_size;
Gtk::Image *image; Gtk::Image image;
Gtk::Menu *gtk_menu = nullptr; Gtk::EventBox event_box;
std::string category; std::string category;
std::string id; std::string id;
std::string status; std::string status;
@ -32,24 +41,23 @@ public:
std::string attention_movie_name; std::string attention_movie_name;
std::string icon_theme_path; std::string icon_theme_path;
std::string menu; std::string menu;
DbusmenuGtkMenu *dbus_menu = nullptr;
Gtk::Menu *gtk_menu = nullptr;
bool item_is_menu; bool item_is_menu;
private: private:
static void proxyReady(GObject *obj, GAsyncResult *res, gpointer data); static void proxyReady(GObject *obj, GAsyncResult *res, gpointer data);
static void getAll(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 updateImage();
void updateMenu();
Glib::RefPtr<Gdk::Pixbuf> extractPixBuf(GVariant *variant); Glib::RefPtr<Gdk::Pixbuf> extractPixBuf(GVariant *variant);
Glib::RefPtr<Gdk::Pixbuf> getIconByName(std::string name, int size); Glib::RefPtr<Gdk::Pixbuf> getIconByName(std::string name, int size);
static void onMenuDestroyed(Item *self);
bool makeMenu(GdkEventButton *const &ev);
bool handleClick(GdkEventButton *const & /*ev*/); bool handleClick(GdkEventButton *const & /*ev*/);
Glib::Dispatcher *dp_;
GCancellable *cancellable_ = nullptr; GCancellable *cancellable_ = nullptr;
SnItem *proxy_ = nullptr; SnItem *proxy_ = nullptr;
Json::Value config_;
}; };
} // namespace waybar::modules::SNI } // 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,21 +1,25 @@
#pragma once #pragma once
#include <fmt/format.h> #include <fmt/format.h>
#include <thread> #include "bar.hpp"
#include "util/json.hpp" #include "util/json.hpp"
#include "IModule.hpp" #include "IModule.hpp"
#include "modules/sni/snw.hpp" #include "modules/sni/watcher.hpp"
#include "modules/sni/snh.hpp" #include "modules/sni/host.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 Json::Value&);
~Tray() = default;
auto update() -> void; auto update() -> void;
operator Gtk::Widget &(); operator Gtk::Widget &();
private: private:
std::thread thread_; void onAdd(std::unique_ptr<Item>& item);
void onRemove(std::unique_ptr<Item>& item);
static inline std::size_t nb_hosts_ = 0;
const Json::Value& config_; const Json::Value& config_;
Gtk::Box box_; Gtk::Box box_;
SNI::Watcher watcher_ ; SNI::Watcher watcher_ ;

View File

@ -1,6 +1,7 @@
#pragma once #pragma once
#include <gtkmm.h> #include <giomm.h>
#include <glibmm/refptr.h>
#include <dbus-status-notifier-watcher.h> #include <dbus-status-notifier-watcher.h>
namespace waybar::modules::SNI { namespace waybar::modules::SNI {
@ -8,7 +9,7 @@ namespace waybar::modules::SNI {
class Watcher { class Watcher {
public: public:
Watcher(); Watcher();
~Watcher(); ~Watcher() = default;
private: private:
typedef enum { GF_WATCH_TYPE_HOST, GF_WATCH_TYPE_ITEM } GfWatchType; typedef enum { GF_WATCH_TYPE_HOST, GF_WATCH_TYPE_ITEM } GfWatchType;
@ -22,7 +23,7 @@ private:
guint watch_id; guint watch_id;
} GfWatch; } GfWatch;
static void busAcquired(GDBusConnection *, const gchar *, gpointer); void busAcquired(const Glib::RefPtr<Gio::DBus::Connection>&, Glib::ustring);
static gboolean handleRegisterHost(Watcher *, GDBusMethodInvocation *, static gboolean handleRegisterHost(Watcher *, GDBusMethodInvocation *,
const gchar *); const gchar *);
static gboolean handleRegisterItem(Watcher *, GDBusMethodInvocation *, static gboolean handleRegisterItem(Watcher *, GDBusMethodInvocation *,

View File

@ -10,33 +10,31 @@
namespace waybar::modules::sway { namespace waybar::modules::sway {
class Ipc { class Ipc {
public: public:
Ipc(); Ipc();
~Ipc(); ~Ipc();
struct ipc_response { struct ipc_response {
uint32_t size; uint32_t size;
uint32_t type; uint32_t type;
std::string payload; std::string payload;
}; };
void connect(); struct ipc_response sendCmd(uint32_t type, const std::string &payload = "") const;
struct ipc_response sendCmd(uint32_t type, void subscribe(const std::string &payload) const;
const std::string& payload = "") const; struct ipc_response handleEvent() 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; protected:
int open(const std::string&) const; static inline const std::string ipc_magic_ = "i3-ipc";
struct ipc_response send(int fd, uint32_t type, static inline const size_t ipc_header_size_ = ipc_magic_.size() + 8;
const std::string& payload = "") const;
struct ipc_response recv(int fd) const;
int fd_; const std::string getSocketPath() const;
int fd_event_; int open(const std::string &) const;
struct ipc_response send(int fd, uint32_t type, const std::string &payload = "") const;
struct ipc_response recv(int fd) const;
int fd_;
int fd_event_;
}; };
} } // 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

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

View File

@ -4,7 +4,7 @@
#include <tuple> #include <tuple>
#include "bar.hpp" #include "bar.hpp"
#include "client.hpp" #include "client.hpp"
#include "util/chrono.hpp" #include "util/sleeper_thread.hpp"
#include "util/json.hpp" #include "util/json.hpp"
#include "ALabel.hpp" #include "ALabel.hpp"
#include "modules/sway/ipc/client.hpp" #include "modules/sway/ipc/client.hpp"
@ -13,14 +13,15 @@ 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&);
~Window() = default;
auto update() -> void; auto update() -> void;
private: private:
void worker(); void worker();
std::tuple<int, std::string> getFocusedNode(Json::Value nodes); std::tuple<int, std::string> getFocusedNode(Json::Value nodes);
void getFocusedWindow(); void getFocusedWindow();
Bar& bar_; const Bar& bar_;
waybar::util::SleeperThread thread_; waybar::util::SleeperThread thread_;
util::JsonParser parser_; util::JsonParser parser_;
Ipc ipc_; Ipc ipc_;

View File

@ -3,36 +3,40 @@
#include <fmt/format.h> #include <fmt/format.h>
#include "bar.hpp" #include "bar.hpp"
#include "client.hpp" #include "client.hpp"
#include "util/chrono.hpp" #include "util/sleeper_thread.hpp"
#include "util/json.hpp" #include "util/json.hpp"
#include "IModule.hpp" #include "IModule.hpp"
#include "modules/sway/ipc/client.hpp" #include "modules/sway/ipc/client.hpp"
#include <gtkmm/button.h>
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&);
~Workspaces() = default;
auto update() -> void; auto update() -> void;
operator Gtk::Widget &(); operator Gtk::Widget &();
private: private:
void worker(); void worker();
void addWorkspace(Json::Value); void addWorkspace(const Json::Value&);
std::string getIcon(std::string, Json::Value); std::string getIcon(const std::string&, const Json::Value&);
bool handleScroll(GdkEventScroll*); bool handleScroll(GdkEventScroll*);
int getPrevWorkspace(); std::string getPrevWorkspace();
int getNextWorkspace(); std::string getNextWorkspace();
uint16_t getWorkspaceIndex(const std::string &name);
std::string trimWorkspaceName(std::string);
Bar& bar_; const Bar& bar_;
const Json::Value& config_; const Json::Value& config_;
Json::Value workspaces_;
waybar::util::SleeperThread thread_; waybar::util::SleeperThread thread_;
Gtk::Box box_; Gtk::Box box_;
util::JsonParser parser_; util::JsonParser parser_;
Ipc ipc_;
std::mutex mutex_; std::mutex mutex_;
bool scrolling_; bool scrolling_;
std::unordered_map<int, Gtk::Button> buttons_; std::unordered_map<std::string, Gtk::Button> buttons_;
Json::Value workspaces_;
Ipc ipc_;
}; };
} }

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,6 +1,8 @@
#pragma once #pragma once
#include <sys/wait.h> #include <sys/wait.h>
#include <giomm.h>
#include <unistd.h>
namespace waybar::util::command { namespace waybar::util::command {
@ -29,7 +31,23 @@ inline struct res exec(const std::string cmd)
output.erase(output.length()-1); output.erase(output.length()-1);
} }
int exit_code = WEXITSTATUS(pclose(fp)); int exit_code = WEXITSTATUS(pclose(fp));
return { exit_code, output }; return {exit_code, output};
} }
inline bool forkExec(std::string cmd) {
if (cmd == "") return true;
int32_t pid = fork();
if (pid < 0) {
printf("Unable to exec cmd %s, error %s", cmd.c_str(), strerror(errno));
return false;
}
// Child executes the command
if (!pid) execl("/bin/sh", "sh", "-c", cmd.c_str(), (char*)0);
return true;
} }
} // namespace waybar::util::command

View File

@ -10,7 +10,7 @@ struct JsonParser {
: reader_(builder_.newCharReader()) : reader_(builder_.newCharReader())
{} {}
const Json::Value parse(const std::string data) const const Json::Value parse(const std::string& data) const
{ {
Json::Value root; Json::Value root;
std::string err; std::string err;

View File

@ -0,0 +1,76 @@
#pragma once
#include <chrono>
#include <ctime>
#include <functional>
#include <condition_variable>
#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_) 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 !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 !do_run_; });
}
auto wake_up()
{
condvar_.notify_all();
}
auto stop()
{
{
std::lock_guard<std::mutex> lck(mutex_);
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;
};
}

View File

@ -1,10 +1,11 @@
project( project(
'waybar', 'cpp', 'c', 'waybar', 'cpp', 'c',
version: '0.1.3', version: '0.4.0',
license: 'MIT', license: 'MIT',
default_options : [ default_options : [
'cpp_std=c++17', 'cpp_std=c++17',
'buildtype=release' 'buildtype=release',
'default_library=static'
], ],
) )
@ -17,16 +18,31 @@ if false # libc++
cpp_link_args += ['-lc++fs'] cpp_link_args += ['-lc++fs']
else else
# TODO: For std::filesystem in libstdc++. Still unstable? Or why is it not in libstdc++ proper yet?
cpp_link_args += ['-lstdc++fs'] cpp_link_args += ['-lstdc++fs']
endif endif
compiler = meson.get_compiler('cpp')
git = find_program('git', required: false)
if not git.found()
add_project_arguments('-DVERSION="@0@"'.format(meson.project_version()), language: 'cpp')
else
git_commit_hash = run_command([git.path(), 'describe', '--always', '--tags']).stdout().strip()
git_branch = run_command([git.path(), 'rev-parse', '--abbrev-ref', 'HEAD']).stdout().strip()
version = '"@0@ (" __DATE__ ", branch \'@1@\')"'.format(git_commit_hash, git_branch)
add_project_arguments('-DVERSION=@0@'.format(version), language: 'cpp')
endif
if not compiler.has_header('filesystem')
add_project_arguments('-DFILESYSTEM_EXPERIMENTAL', language: 'cpp')
endif
add_global_arguments(cpp_args, language : 'cpp') add_global_arguments(cpp_args, language : 'cpp')
add_global_link_arguments(cpp_link_args, language : 'cpp') 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', fallback: ['fmtlib', 'fmt_dep']) fmt = dependency('fmt', version : ['>=5.2.1'], 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')
@ -39,6 +55,7 @@ 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'))
src_files = files( src_files = files(
'src/factory.cpp', 'src/factory.cpp',
@ -48,6 +65,7 @@ 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/main.cpp', 'src/main.cpp',
'src/bar.cpp', 'src/bar.cpp',
'src/client.cpp' 'src/client.cpp'
@ -57,6 +75,7 @@ if 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',
'src/modules/sway/mode.cpp',
'src/modules/sway/window.cpp', 'src/modules/sway/window.cpp',
'src/modules/sway/workspaces.cpp' 'src/modules/sway/workspaces.cpp'
] ]
@ -76,12 +95,17 @@ 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
subdir('protocol') subdir('protocol')
executable( executable(
@ -102,7 +126,8 @@ executable(
giounix, giounix,
libnl, libnl,
libnlgen, libnlgen,
libpulse libpulse,
libudev
], ],
include_directories: [include_directories('include')], include_directories: [include_directories('include')],
install: true, install: true,

View File

@ -1,4 +1,5 @@
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('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'],
] ]
@ -38,26 +39,58 @@ endforeach
gdbus_codegen = find_program('gdbus-codegen') gdbus_codegen = find_program('gdbus-codegen')
gdbus_code = generator( r = run_command(gdbus_codegen, '--body', '--output', '/dev/null')
gdbus_codegen, if r.returncode() != 0
output: '@BASENAME@.c', gdbus_code_dsnw = custom_target(
arguments: ['--c-namespace', 'Sn', '--body', '--output', '@OUTPUT@', '@INPUT@'] 'dbus-status-notifier-watcher.[ch]',
) output: ['@BASENAME@.c','@BASENAME@.h'],
input: './dbus-status-notifier-watcher.xml',
command: [gdbus_codegen,'--c-namespace', 'Sn', '--generate-c-code', 'protocol/@BASENAME@', '@INPUT@'],
)
gdbus_header = generator( gdbus_code_dsni = custom_target(
gdbus_codegen, 'dbus-status-notifier-item.[ch]',
output: '@BASENAME@.h', output: ['@BASENAME@.c','@BASENAME@.h'],
arguments: ['--c-namespace', 'Sn', '--header', '--output', '@OUTPUT@', '@INPUT@'] input: './dbus-status-notifier-item.xml',
) command: [gdbus_codegen,'--c-namespace', 'Sn', '--generate-c-code', 'protocol/@BASENAME@', '@INPUT@'],
)
client_protos_src += gdbus_code.process('./dbus-status-notifier-watcher.xml') gdbus_code_dm = custom_target(
client_protos_headers += gdbus_header.process('./dbus-status-notifier-watcher.xml') 'dbus-menu.[ch]',
output: ['@BASENAME@.c','@BASENAME@.h'],
input: './dbus-menu.xml',
command: [gdbus_codegen,'--c-namespace', 'Sn', '--generate-c-code', 'protocol/@BASENAME@', '@INPUT@'],
)
client_protos_src += gdbus_code.process('./dbus-status-notifier-item.xml') client_protos_src += gdbus_code_dsnw[0]
client_protos_headers += gdbus_header.process('./dbus-status-notifier-item.xml') client_protos_headers += gdbus_code_dsnw[1]
client_protos_src += gdbus_code_dsni[0]
client_protos_headers += gdbus_code_dsni[1]
client_protos_src += gdbus_code_dm[0]
client_protos_headers += gdbus_code_dm[1]
else
gdbus_code = generator(
gdbus_codegen,
output: '@BASENAME@.c',
arguments: ['--c-namespace', 'Sn', '--body', '--output', '@OUTPUT@', '@INPUT@']
)
gdbus_header = generator(
gdbus_codegen,
output: '@BASENAME@.h',
arguments: ['--c-namespace', 'Sn', '--header', '--output', '@OUTPUT@', '@INPUT@']
)
client_protos_src += gdbus_code.process('./dbus-status-notifier-watcher.xml')
client_protos_headers += gdbus_header.process('./dbus-status-notifier-watcher.xml')
client_protos_src += gdbus_code.process('./dbus-status-notifier-item.xml')
client_protos_headers += gdbus_header.process('./dbus-status-notifier-item.xml')
client_protos_src += gdbus_code.process('./dbus-menu.xml')
client_protos_headers += gdbus_header.process('./dbus-menu.xml')
endif
client_protos_src += gdbus_code.process('./dbus-menu.xml')
client_protos_headers += gdbus_header.process('./dbus-menu.xml')
lib_client_protos = static_library( lib_client_protos = static_library(
'client_protos', 'client_protos',

View File

@ -4,9 +4,9 @@
// "height": 30, // Waybar height // "height": 30, // Waybar 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", "custom/spotify"], "modules-left": ["sway/workspaces", "sway/mode", "custom/spotify"],
"modules-center": ["sway/window"], "modules-center": ["sway/window"],
"modules-right": ["pulseaudio", "network", "cpu", "memory", "battery", "battery#bat2", "clock", "tray"], "modules-right": ["idle_inhibitor", "pulseaudio", "network", "cpu", "memory", "backlight", "battery", "battery#bat2", "clock", "tray"],
// Modules configuration // Modules configuration
// "sway/workspaces": { // "sway/workspaces": {
// "disable-scroll": true, // "disable-scroll": true,
@ -23,24 +23,44 @@
// "default": "" // "default": ""
// } // }
// }, // },
"sway/window": { "sway/mode": {
"max-length": 50 "format": "<span style=\"italic\">{}</span>"
},
"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": "{}% " "format": "{usage}% "
}, },
"memory": { "memory": {
"format": "{}% " "format": "{}% "
}, },
"backlight": {
// "device": "acpi_video1",
"format": "{percent}% {icon}",
"format-icons": ["", ""]
},
"battery": { "battery": {
"states": {
// "good": 95,
"warning": 30,
"critical": 15
},
"format": "{capacity}% {icon}", "format": "{capacity}% {icon}",
// "format-good": "", // An empty format will hide the module
// "format-full": "",
"format-icons": ["", "", "", "", ""] "format-icons": ["", "", "", "", ""]
}, },
"battery#bat2": { "battery#bat2": {
@ -49,10 +69,11 @@
"network": { "network": {
// "interface": "wlp2s0", // (Optional) To force the use of this interface // "interface": "wlp2s0", // (Optional) To force the use of this interface
"format-wifi": "{essid} ({signalStrength}%) ", "format-wifi": "{essid} ({signalStrength}%) ",
"format-ethernet": "{ifname} ", "format-ethernet": "{ifname}: {ipaddr}/{cidr} ",
"format-disconnected": "Disconnected ⚠" "format-disconnected": "Disconnected ⚠"
}, },
"pulseaudio": { "pulseaudio": {
//"scroll-step": 1,
"format": "{volume}% {icon}", "format": "{volume}% {icon}",
"format-bluetooth": "{volume}% {icon}", "format-bluetooth": "{volume}% {icon}",
"format-muted": "", "format-muted": "",
@ -64,13 +85,12 @@
"portable": "", "portable": "",
"car": "", "car": "",
"default": ["", ""] "default": ["", ""]
} },
"on-click": "pavucontrol"
}, },
"custom/spotify": { "custom/spotify": {
"format": " {}", "format": " {}",
"max-length": 40, "max-length": 40,
"interval": 30, // Remove this if your script is endless and write in loop "exec": "$HOME/.config/waybar/mediaplayer.py 2> /dev/null" // Script in resources folder
"exec": "$HOME/.config/waybar/mediaplayer.sh", // Script in resources folder
"exec-if": "pgrep spotify"
} }
} }

View File

@ -0,0 +1,71 @@
#!/usr/bin/env python3
import sys
import signal
import gi
gi.require_version('Playerctl', '2.0')
from gi.repository import Playerctl, GLib
manager = Playerctl.PlayerManager()
loop = GLib.MainLoop()
def on_play(player, status, manager):
on_metadata(player, player.props.metadata, manager)
def on_metadata(player, metadata, manager):
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())
else:
sys.stdout.write('\n')
sys.stdout.flush()
return
if player.props.status == 'Playing':
sys.stdout.write(track_info + '\n')
else:
sys.stdout.write('' + track_info + '\n')
sys.stdout.flush()
def on_name_appeared(manager, name):
init_player(name)
def on_player_vanished(manager, player):
sys.stdout.write("\n")
sys.stdout.flush()
def init_player(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):
sys.stdout.write("\n")
sys.stdout.flush()
loop.quit()
sys.exit(0)
manager.connect('name-appeared', on_name_appeared)
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:
init_player(player)
loop.run()

View File

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

View File

@ -3,6 +3,7 @@
border-radius: 0; border-radius: 0;
font-family: Roboto, Helvetica, Arial, sans-serif; font-family: Roboto, Helvetica, Arial, sans-serif;
font-size: 13px; font-size: 13px;
min-height: 0;
} }
window#waybar { window#waybar {
@ -11,6 +12,7 @@ window#waybar {
color: white; color: white;
} }
/* 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;
@ -23,7 +25,12 @@ window#waybar {
border-bottom: 3px solid white; border-bottom: 3px solid white;
} }
#clock, #battery, #cpu, #memory, #network, #pulseaudio, #custom-spotify, #tray { #mode {
background: #64727D;
border-bottom: 3px solid white;
}
#clock, #battery, #cpu, #memory, #backlight, #network, #pulseaudio, #custom-spotify, #tray, #mode, #idle_inhibitor {
padding: 0 10px; padding: 0 10px;
margin: 0 5px; margin: 0 5px;
} }
@ -49,7 +56,7 @@ window#waybar {
} }
} }
#battery.warning { #battery.critical:not(.charging) {
background: #f53c3c; background: #f53c3c;
color: white; color: white;
animation-name: blink; animation-name: blink;
@ -68,6 +75,10 @@ window#waybar {
background: #9b59b6; background: #9b59b6;
} }
#backlight {
background: #90b1b1;
}
#network { #network {
background: #2980b9; background: #2980b9;
} }
@ -93,4 +104,8 @@ window#waybar {
#tray { #tray {
background-color: #2980b9; background-color: #2980b9;
} }
#idle_inhibitor {
background-color: #2d3436;
}

View File

@ -1,11 +1,13 @@
#include "ALabel.hpp" #include "ALabel.hpp"
#include <util/command.hpp>
#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_(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()) {
@ -14,23 +16,71 @@ waybar::ALabel::ALabel(const Json::Value& config, const std::string format)
} }
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() event_box_.signal_button_press_event().connect(
.connect(sigc::mem_fun(*this, &ALabel::handleToggle)); sigc::mem_fun(*this, &ALabel::handleToggle));
}
// configure events' user commands
if (config_["on-click"].isString() || config_["on-click-right"].isString()) {
event_box_.add_events(Gdk::BUTTON_PRESS_MASK);
event_box_.signal_button_press_event().connect(
sigc::mem_fun(*this, &ALabel::handleToggle));
}
if (config_["on-scroll-up"].isString() || config_["on-scroll-down"].isString()) {
event_box_.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);
event_box_.signal_scroll_event().connect(
sigc::mem_fun(*this, &ALabel::handleScroll));
} }
} }
auto waybar::ALabel::update() -> void auto waybar::ALabel::update() -> void {
{
// Nothing here // Nothing here
} }
bool waybar::ALabel::handleToggle(GdkEventButton* const& /*ev*/) bool waybar::ALabel::handleToggle(GdkEventButton* const& e) {
{ if (config_["on-click"].isString() && e->button == 1) {
alt = !alt; waybar::util::command::forkExec(config_["on-click"].asString());
if (alt) { } else if (config_["on-click-right"].isString() && e->button == 3) {
format_ = config_["format-alt"].asString(); waybar::util::command::forkExec(config_["on-click-right"].asString());
} else { } else {
format_ = default_format_; alt_ = !alt_;
if (alt_) {
format_ = config_["format-alt"].asString();
} else {
format_ = default_format_;
}
}
dp.emit();
return true;
}
bool waybar::ALabel::handleScroll(GdkEventScroll* e) {
// Avoid concurrent scroll event
std::lock_guard<std::mutex> lock(mutex_);
bool direction_up = false;
if (e->direction == GDK_SCROLL_UP) {
direction_up = true;
}
if (e->direction == GDK_SCROLL_DOWN) {
direction_up = false;
}
if (e->direction == GDK_SCROLL_SMOOTH) {
gdouble delta_x, delta_y;
gdk_event_get_scroll_deltas(reinterpret_cast<const GdkEvent*>(e),
&delta_x, &delta_y);
if (delta_y < 0) {
direction_up = true;
} else if (delta_y > 0) {
direction_up = false;
}
}
if (direction_up && config_["on-scroll-up"].isString()) {
waybar::util::command::forkExec(config_["on-scroll-up"].asString());
} else if (config_["on-scroll-down"].isString()) {
waybar::util::command::forkExec(config_["on-scroll-down"].asString());
} }
dp.emit(); dp.emit();
return true; return true;
@ -40,7 +90,7 @@ std::string waybar::ALabel::getIcon(uint16_t percentage, const std::string& alt)
{ {
auto format_icons = config_["format-icons"]; auto format_icons = config_["format-icons"];
if (format_icons.isObject()) { if (format_icons.isObject()) {
if (!alt.empty() && format_icons[alt].isString()) { 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"];
@ -57,6 +107,9 @@ std::string waybar::ALabel::getIcon(uint16_t percentage, const std::string& alt)
return ""; return "";
} }
waybar::ALabel::operator Gtk::Widget &() { bool waybar::ALabel::tooltipEnabled()
return event_box_; {
return config_["tooltip"].isBool() ? config_["tooltip"].asBool() : true;
} }
waybar::ALabel::operator Gtk::Widget&() { return event_box_; }

View File

@ -7,7 +7,9 @@ waybar::Bar::Bar(const Client& client,
std::unique_ptr<struct wl_output *> &&p_output, uint32_t p_wl_name) std::unique_ptr<struct wl_output *> &&p_output, uint32_t p_wl_name)
: client(client), window{Gtk::WindowType::WINDOW_TOPLEVEL}, : client(client), window{Gtk::WindowType::WINDOW_TOPLEVEL},
surface(nullptr), layer_surface(nullptr), surface(nullptr), layer_surface(nullptr),
output(std::move(p_output)), wl_name(p_wl_name) output(std::move(p_output)), wl_name(p_wl_name),
left_(Gtk::ORIENTATION_HORIZONTAL, 0), center_(Gtk::ORIENTATION_HORIZONTAL, 0),
right_(Gtk::ORIENTATION_HORIZONTAL, 0), box_(Gtk::ORIENTATION_HORIZONTAL, 0)
{ {
static const struct zxdg_output_v1_listener xdgOutputListener = { static const struct zxdg_output_v1_listener xdgOutputListener = {
.logical_position = handleLogicalPosition, .logical_position = handleLogicalPosition,
@ -20,20 +22,22 @@ waybar::Bar::Bar(const Client& client,
zxdg_output_manager_v1_get_xdg_output(client.xdg_output_manager, *output); zxdg_output_manager_v1_get_xdg_output(client.xdg_output_manager, *output);
zxdg_output_v1_add_listener(xdg_output_, &xdgOutputListener, this); zxdg_output_v1_add_listener(xdg_output_, &xdgOutputListener, this);
window.set_title("waybar"); window.set_title("waybar");
window.set_decorated(false);
window.set_name("waybar"); window.set_name("waybar");
window.set_decorated(false);
window.set_resizable(false); window.set_resizable(false);
setupConfig(); setupConfig();
setupCss(); setupCss();
setupWidgets();
Gtk::Widget& wrap(window); auto wrap = reinterpret_cast<GtkWidget*>(window.gobj());
gtk_widget_realize(wrap.gobj()); gtk_widget_realize(wrap);
GdkWindow *gdk_window = gtk_widget_get_window(wrap.gobj()); GdkWindow *gdk_window = gtk_widget_get_window(wrap);
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" void waybar::Bar::initBar()
{
std::size_t layer_top = config_["layer"] == "top"
? ZWLR_LAYER_SHELL_V1_LAYER_TOP : ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM; ? ZWLR_LAYER_SHELL_V1_LAYER_TOP : ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM;
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, layer_top, "waybar");
@ -42,8 +46,7 @@ waybar::Bar::Bar(const Client& client,
.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 std::size_t anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT
| ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
@ -60,6 +63,8 @@ waybar::Bar::Bar(const Client& client,
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);
setupWidgets();
} }
void waybar::Bar::handleLogicalPosition(void* /*data*/, void waybar::Bar::handleLogicalPosition(void* /*data*/,
@ -81,11 +86,50 @@ void waybar::Bar::handleDone(void* /*data*/,
// Nothing here // Nothing here
} }
bool waybar::Bar::isValidOutput(const Json::Value &config)
{
bool found = true;
if (config["output"].isArray()) {
bool in_array = false;
for (auto const &output : config["output"]) {
if (output.isString() && output.asString() == output_name) {
in_array = true;
break;
}
}
found = in_array;
}
if (config["output"].isString() && config["output"].asString() != output_name) {
found = false;
}
return found;
}
void waybar::Bar::handleName(void* data, struct zxdg_output_v1* /*xdg_output*/, void waybar::Bar::handleName(void* data, struct zxdg_output_v1* /*xdg_output*/,
const char* name) const char* name)
{ {
auto o = static_cast<waybar::Bar *>(data); auto o = static_cast<waybar::Bar *>(data);
o->output_name = name; o->output_name = name;
bool found = true;
if (o->config_.isArray()) {
bool in_array = false;
for (auto const &config : o->config_) {
if (config.isObject() && o->isValidOutput(config)) {
in_array = true;
o->config_ = config;
break;
}
}
found = in_array;
} else {
found = o->isValidOutput(o->config_);
}
if (!found) {
wl_output_destroy(*o->output);
zxdg_output_v1_destroy(o->xdg_output_);
} else {
o->initBar();
}
} }
void waybar::Bar::handleDescription(void* /*data*/, void waybar::Bar::handleDescription(void* /*data*/,
@ -99,18 +143,24 @@ void waybar::Bar::layerSurfaceHandleConfigure(void* data,
uint32_t height) uint32_t height)
{ {
auto o = static_cast<waybar::Bar *>(data); auto o = static_cast<waybar::Bar *>(data);
o->window.show_all();
zwlr_layer_surface_v1_ack_configure(surface, serial); 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.resize(o->width_, o->height_);
int dummy_width, min_height;
o->window.get_size(dummy_width, min_height);
if (o->height_ < static_cast<uint32_t>(min_height)) {
std::cout << fmt::format("Requested height: {} exceeds the minimum \
height: {} required by the modules", o->height_, min_height) << std::endl;
o->height_ = min_height;
}
std::cout << fmt::format( std::cout << fmt::format(
"Bar configured (width: {}, height: {}) for output: {}", "Bar configured (width: {}, height: {}) for output: {}",
o->width_, o->height_, o->output_name) << std::endl; o->width_, o->height_, o->output_name) << std::endl;
o->window.set_size_request(o->width_, o->height_);
o->window.resize(o->width_, o->height_);
zwlr_layer_surface_v1_set_exclusive_zone(surface, o->height_);
zwlr_layer_surface_v1_set_size(surface, o->width_, o->height_);
wl_surface_commit(o->surface); wl_surface_commit(o->surface);
} }
} }
@ -176,7 +226,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;
} }
@ -186,28 +242,24 @@ void waybar::Bar::getModules(const Factory& factory, const std::string& pos)
auto waybar::Bar::setupWidgets() -> void auto waybar::Bar::setupWidgets() -> void
{ {
auto &left = *Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 0)); window.add(box_);
auto &center = *Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 0)); box_.pack_start(left_, true, true);
auto &right = *Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 0)); box_.set_center_widget(center_);
box_.pack_end(right_, true, true);
auto &box = *Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 0));
window.add(box);
box.pack_start(left, true, true);
box.set_center_widget(center);
box.pack_end(right, true, true);
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,7 +1,9 @@
#include "client.hpp" #include "client.hpp"
#include "util/clara.hpp"
#include <iostream>
waybar::Client::Client(int argc, char* argv[]) waybar::Client::Client(int argc, char* argv[])
: gtk_app(Gtk::Application::create(argc, argv, "fr.arouillard.waybar")), : gtk_main(argc, argv),
gdk_display(Gdk::Display::get_default()) gdk_display(Gdk::Display::get_default())
{ {
if (!gdk_display) { if (!gdk_display) {
@ -11,36 +13,24 @@ 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) { }
wordexp_t p;
for (const std::string &path: possiblePaths) { const std::string waybar::Client::getValidPath(std::vector<std::string> paths)
if (wordexp(path.c_str(), &p, 0) == 0) { {
if (access(*p.we_wordv, F_OK) == 0) { wordexp_t p;
std::string result = *p.we_wordv;
wordfree(&p); for (const std::string &path: paths) {
return result; if (wordexp(path.c_str(), &p, 0) == 0) {
} if (access(*p.we_wordv, F_OK) == 0) {
std::string result = *p.we_wordv;
wordfree(&p); wordfree(&p);
return result;
} }
wordfree(&p);
} }
}
return std::string(); 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",
});
} }
void waybar::Client::handleGlobal(void *data, struct wl_registry *registry, void waybar::Client::handleGlobal(void *data, struct wl_registry *registry,
@ -65,6 +55,10 @@ void waybar::Client::handleGlobal(void *data, struct wl_registry *registry,
o->xdg_output_manager = static_cast<struct zxdg_output_manager_v1 *>( o->xdg_output_manager = static_cast<struct zxdg_output_manager_v1 *>(
wl_registry_bind(registry, name, wl_registry_bind(registry, name,
&zxdg_output_manager_v1_interface, ZXDG_OUTPUT_V1_NAME_SINCE_VERSION)); &zxdg_output_manager_v1_interface, ZXDG_OUTPUT_V1_NAME_SINCE_VERSION));
} else if (strcmp(interface, zwp_idle_inhibit_manager_v1_interface.name) == 0) {
o->idle_inhibit_manager = static_cast<struct zwp_idle_inhibit_manager_v1 *>(
wl_registry_bind(registry, name,
&zwp_idle_inhibit_manager_v1_interface, 1));
} }
} }
@ -80,6 +74,28 @@ void waybar::Client::handleGlobalRemove(void* data,
} }
} }
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;
}
void waybar::Client::bindInterfaces() void waybar::Client::bindInterfaces()
{ {
registry = wl_display_get_registry(wl_display); registry = wl_display_get_registry(wl_display);
@ -89,16 +105,44 @@ void waybar::Client::bindInterfaces()
}; };
wl_registry_add_listener(registry, &registry_listener, this); wl_registry_add_listener(registry, &registry_listener, this);
wl_display_roundtrip(wl_display); wl_display_roundtrip(wl_display);
if (!layer_shell || !seat || !xdg_output_manager) {
throw std::runtime_error("Failed to acquire required resources.");
}
wl_display_roundtrip(wl_display);
} }
int waybar::Client::main(int /*argc*/, char* /*argv*/[]) int waybar::Client::main(int argc, char* argv[])
{ {
bool show_help = false;
bool show_version = false;
std::string config;
std::string style;
std::string bar_id;
auto cli = clara::detail::Help(show_help)
| clara::detail::Opt(show_version)["-v"]["--version"]("Show version")
| clara::detail::Opt(config, "config")["-c"]["--config"]("Config path")
| clara::detail::Opt(style, "style")["-s"]["--style"]("Style path")
| clara::detail::Opt(bar_id, "id")["-b"]["--bar"]("Bar id");
auto res = cli.parse(clara::detail::Args(argc, argv));
if (!res) {
std::cerr << "Error in command line: " << res.errorMessage() << std::endl;
return 1;
}
if (show_help) {
std::cout << cli << std::endl;
return 0;
}
if (show_version) {
std::cout << "Waybar v" << VERSION << std::endl;
return 0;
}
setupConfigs(config, style);
bindInterfaces(); bindInterfaces();
gtk_app->hold(); gtk_main.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_seat_destroy(seat);
wl_display_disconnect(wl_display); wl_display_disconnect(wl_display);

View File

@ -1,46 +1,59 @@
#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") {
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 #endif
if (ref == "idle_inhibitor") {
return new waybar::modules::IdleInhibitor(id, bar_, config_[name]);
}
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, 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
#ifdef HAVE_LIBUDEV
if (ref == "backlight") {
return new waybar::modules::Backlight(id, config_[name]);
} }
#endif #endif
#ifdef HAVE_LIBPULSE #ifdef HAVE_LIBPULSE
if (ref == "pulseaudio") { if (ref == "pulseaudio") {
return new waybar::modules::Pulseaudio(config_[name]); return new waybar::modules::Pulseaudio(id, config_[name]);
} }
#endif #endif
if (ref.compare(0, 7, "custom/") == 0 && ref.size() > 7) { if (ref.compare(0, 7, "custom/") == 0 && ref.size() > 7) {

View File

@ -15,7 +15,7 @@ int main(int argc, char* argv[])
waybar::client = &c; waybar::client = &c;
std::signal(SIGUSR1, [] (int /*signal*/) { std::signal(SIGUSR1, [] (int /*signal*/) {
for (auto& bar : waybar::client->bars) { for (auto& bar : waybar::client->bars) {
(*bar).toggle(); bar->toggle();
} }
}); });

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

@ -0,0 +1,278 @@
#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(0)};
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,21 +1,71 @@
#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");
if (!id.empty()) {
label_.get_style_context()->add_class(id);
}
getBatteries();
fd_ = inotify_init1(IN_CLOEXEC);
if (fd_ == -1) {
throw std::runtime_error("Unable to listen batteries.");
}
for (auto const& bat : batteries_) {
auto wd = inotify_add_watch(fd_, (bat / "uevent").c_str(), IN_ACCESS);
if (wd != -1) {
wds_.push_back(wd);
}
}
worker();
}
waybar::modules::Battery::~Battery()
{
for (auto wd : wds_) {
inotify_rm_watch(fd_, wd);
}
close(fd_);
}
void waybar::modules::Battery::worker()
{
thread_timer_ = [this] {
dp.emit();
thread_timer_.sleep_for(interval_);
};
thread_ = [this] {
struct inotify_event event = {0};
int nbytes = read(fd_, &event, sizeof(event));
if (nbytes != sizeof(event) || event.mask & IN_IGNORED) {
thread_.stop();
return;
}
// TODO: don't stop timer for now since there is some bugs :?
// thread_timer_.stop();
dp.emit();
};
}
void waybar::modules::Battery::getBatteries()
{ {
try { try {
if (config_["bat"].isString()) { for (auto const& node : fs::directory_iterator(data_dir_)) {
auto dir = data_dir_ / config_["bat"].asString(); if (!fs::is_directory(node)) {
if (fs::is_directory(dir) && fs::exists(dir / "capacity") continue;
&& fs::exists(dir / "status") && fs::exists(dir / "uevent")) {
batteries_.push_back(dir);
} }
} else { auto dir_name = node.path().filename();
for (auto const& node : fs::directory_iterator(data_dir_)) { auto bat_defined = config_["bat"].isString();
if (fs::is_directory(node) && fs::exists(node / "capacity") if (((bat_defined && dir_name == config_["bat"].asString())
&& fs::exists(node / "status") && fs::exists(node / "uevent")) { || !bat_defined) && fs::exists(node / "capacity")
&& fs::exists(node / "uevent") && fs::exists(node / "status")) {
batteries_.push_back(node); 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) { } catch (fs::filesystem_error &e) {
@ -27,47 +77,13 @@ waybar::modules::Battery::Battery(const Json::Value& config)
} }
throw std::runtime_error("No batteries."); throw std::runtime_error("No batteries.");
} }
fd_ = inotify_init1(IN_CLOEXEC);
if (fd_ == -1) {
throw std::runtime_error("Unable to listen batteries.");
}
for (auto const& bat : batteries_) {
inotify_add_watch(fd_, (bat / "uevent").c_str(), IN_ACCESS);
}
label_.set_name("battery");
worker();
} }
waybar::modules::Battery::~Battery() const std::tuple<uint8_t, std::string> waybar::modules::Battery::getInfos() const
{
close(fd_);
}
void waybar::modules::Battery::worker()
{
// Trigger first values
update();
uint32_t interval = config_["interval"].isUInt() ? config_["interval"].asUInt() : 60;
threadTimer_ = [this, interval] {
thread_.sleep_for(chrono::seconds(interval));
dp.emit();
};
thread_ = [this] {
struct inotify_event event = {0};
int nbytes = read(fd_, &event, sizeof(event));
if (nbytes != sizeof(event)) {
return;
}
threadTimer_.stop();
dp.emit();
};
}
auto waybar::modules::Battery::update() -> void
{ {
try { try {
uint16_t total = 0; uint16_t total = 0;
std::string status; 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;
@ -79,22 +95,80 @@ auto waybar::modules::Battery::update() -> void
total += capacity; total += capacity;
} }
uint16_t capacity = total / batteries_.size(); uint16_t capacity = total / batteries_.size();
label_.set_text(fmt::format(format_, fmt::arg("capacity", capacity), return {capacity, status};
fmt::arg("icon", getIcon(capacity))));
label_.set_tooltip_text(status);
bool charging = status == "Charging";
if (charging) {
label_.get_style_context()->add_class("charging");
} else {
label_.get_style_context()->remove_class("charging");
}
auto critical = config_["critical"].isUInt() ? config_["critical"].asUInt() : 15;
if (capacity <= critical && !charging) {
label_.get_style_context()->add_class("warning");
} else {
label_.get_style_context()->remove_class("warning");
}
} catch (const std::exception& e) { } catch (const std::exception& e) {
std::cerr << e.what() << std::endl; std::cerr << e.what() << std::endl;
return {0, "Unknown"};
}
}
const std::string waybar::modules::Battery::getAdapterStatus(uint8_t capacity) const
{
if (!adapter_.empty()) {
bool online;
std::ifstream(adapter_ / "online") >> online;
if (capacity == 100) {
return "Full";
}
return online ? "Charging" : "Discharging";
}
return "Unknown";
}
const std::string waybar::modules::Battery::getState(uint8_t capacity) const
{
// Get current state
std::vector<std::pair<std::string, uint8_t>> states;
if (config_["states"].isObject()) {
for (auto it = config_["states"].begin(); it != config_["states"].end(); ++it) {
if (it->isUInt() && it.key().isString()) {
states.push_back({it.key().asString(), it->asUInt()});
}
}
}
// Sort states
std::sort(states.begin(), states.end(), [](auto &a, auto &b) {
return a.second < b.second;
});
std::string valid_state;
for (auto const& state : states) {
if (capacity <= state.second && valid_state.empty()) {
label_.get_style_context()->add_class(state.first);
valid_state = state.first;
} else {
label_.get_style_context()->remove_class(state.first);
}
}
return valid_state;
}
auto waybar::modules::Battery::update() -> void
{
auto [capacity, status] = getInfos();
if (status == "Unknown") {
status = getAdapterStatus(capacity);
}
if (tooltipEnabled()) {
label_.set_tooltip_text(status);
}
std::transform(status.begin(), status.end(), status.begin(), ::tolower);
auto format = format_;
auto state = getState(capacity);
label_.get_style_context()->remove_class(old_status_);
label_.get_style_context()->add_class(status);
old_status_ = status;
if (!state.empty() && config_["format-" + status + "-" + state].isString()) {
format = config_["format-" + status + "-" + state].asString();
} else if (config_["format-" + status].isString()) {
format = config_["format-" + status].asString();
} else if (!state.empty() && config_["format-" + state].isString()) {
format = config_["format-" + state].asString();
}
if (format.empty()) {
event_box_.hide();
} else {
event_box_.show();
label_.set_markup(fmt::format(format, fmt::arg("capacity", capacity),
fmt::arg("icon", getIcon(capacity))));
} }
} }

View File

@ -1,21 +1,36 @@
#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] {
auto now = std::chrono::system_clock::now();
dp.emit(); dp.emit();
auto timeout = std::chrono::floor<std::chrono::seconds>(now auto timeout = std::chrono::floor<std::chrono::seconds>(now + interval_);
+ std::chrono::seconds(interval)); auto time_s = std::chrono::time_point_cast<std::chrono::seconds>(timeout);
thread_.sleep_until(timeout); auto sub_m =
std::chrono::duration_cast<std::chrono::seconds>(time_s.time_since_epoch()).count() % interval_.count();
thread_.sleep_until(timeout - std::chrono::seconds(sub_m - 1));
}; };
} }
auto waybar::modules::Clock::update() -> void auto waybar::modules::Clock::update() -> void
{ {
auto 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,22 +1,90 @@
#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, "{}%") : 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
{ {
struct sysinfo info = {}; // TODO: as creating dynamic fmt::arg arrays is buggy we have to calc both
auto cpu_load = getCpuLoad();
auto [cpu_usage, tooltip] = getCpuUsage();
if (tooltipEnabled()) {
label_.set_tooltip_text(tooltip);
}
label_.set_markup(fmt::format(format_,
fmt::arg("load", cpu_load), fmt::arg("usage", cpu_usage)));
}
uint16_t waybar::modules::Cpu::getCpuLoad()
{
struct sysinfo info = {0};
if (sysinfo(&info) == 0) { 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();
label_.set_text(fmt::format(format_, load)); return load;
} }
throw std::runtime_error("Can't get Cpu load");
}
std::tuple<uint16_t, std::string> waybar::modules::Cpu::getCpuUsage()
{
if (prev_times_.empty()) {
prev_times_ = parseCpuinfo();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
std::vector<std::tuple<size_t, size_t>> curr_times = parseCpuinfo();
std::string tooltip;
uint16_t usage = 0;
for (size_t i = 0; i < curr_times.size(); ++i) {
auto [curr_idle, curr_total] = curr_times[i];
auto [prev_idle, prev_total] = prev_times_[i];
const float delta_idle = curr_idle - prev_idle;
const float delta_total = curr_total - prev_total;
uint16_t tmp = 100 * (1 - delta_idle / delta_total);
if (i == 0) {
usage = tmp;
tooltip = fmt::format("Total: {}%", tmp);
} else {
tooltip = tooltip + fmt::format("\nCore{}: {}%", i - 1, tmp);
}
}
prev_times_ = curr_times;
return {usage, tooltip};
}
std::vector<std::tuple<size_t, size_t>> waybar::modules::Cpu::parseCpuinfo()
{
std::ifstream info(data_dir_);
if (!info.is_open()) {
throw std::runtime_error("Can't open " + data_dir_);
}
std::vector< std::tuple<size_t, size_t> > cpuinfo;
std::string line;
while (getline(info, line)) {
if (line.substr(0,3).compare("cpu") != 0) {
break;
}
std::stringstream sline(line.substr(5));
std::vector<size_t> times;
for (size_t time; sline >> time; times.push_back(time));
size_t idle_time = 0;
size_t total_time = 0;
if (times.size() >= 4) {
idle_time = times[3];
total_time = std::accumulate(times.begin(), times.end(), 0);
}
cpuinfo.push_back({idle_time, total_time});
}
return cpuinfo;
} }

View File

@ -1,55 +1,69 @@
#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) : ALabel(config, "{}"), name_(name), fp_(nullptr)
{ {
if (!config_["exec"].isString()) { label_.set_name("custom-" + name_);
throw std::runtime_error(name_ + " has no exec path."); if (config_["exec"].isString()) {
if (interval_.count() > 0) {
delayWorker();
} else {
continuousWorker();
}
} }
if (config_["interval"].isUInt()) { dp.emit();
delayWorker(); }
} else {
continuousWorker(); waybar::modules::Custom::~Custom()
{
if (fp_) {
pclose(fp_);
fp_ = nullptr;
} }
} }
void waybar::modules::Custom::delayWorker() void waybar::modules::Custom::delayWorker()
{ {
auto interval = config_["interval"].asUInt(); thread_ = [this] {
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")); fp_ = popen(cmd.c_str(), "r");
if (!fp) { if (!fp_) {
throw std::runtime_error("Unable to open " + cmd); throw std::runtime_error("Unable to open " + cmd);
} }
thread_ = [this, fp] { thread_ = [this] {
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(pclose(fp_));
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;
} }
@ -67,14 +81,90 @@ void waybar::modules::Custom::continuousWorker()
auto waybar::modules::Custom::update() -> void auto waybar::modules::Custom::update() -> void
{ {
// Hide label if output is empty // Hide label if output is empty
if (output_.out.empty() || output_.exit_code != 0) { if (config_["exec"].isString() && (output_.out.empty() || output_.exit_code != 0)) {
label_.hide(); event_box_.hide();
label_.set_name("");
} else { } else {
label_.set_name("custom-" + name_); if (config_["return-type"].asString() == "json") {
auto str = fmt::format(format_, output_.out); parseOutputJson();
label_.set_text(str); } else {
label_.set_tooltip_text(str); parseOutputRaw();
label_.show(); }
auto str = fmt::format(format_, text_,
fmt::arg("alt", alt_),
fmt::arg("icon", getIcon(percentage_)),
fmt::arg("percentage", percentage_));
label_.set_markup(str);
if (tooltipEnabled()) {
if (text_ == tooltip_) {
label_.set_tooltip_text(str);
} else {
label_.set_tooltip_text(tooltip_);
}
}
if (class_ != "") {
if (prevclass_ != "") {
label_.get_style_context()->remove_class(prevclass_);
}
label_.get_style_context()->add_class(class_);
prevclass_ = class_;
} else {
label_.get_style_context()->remove_class(prevclass_);
prevclass_ = "";
}
event_box_.show();
} }
} }
void waybar::modules::Custom::parseOutputRaw()
{
std::istringstream output(output_.out);
std::string line;
int i = 0;
while (getline(output, line)) {
if (i == 0) {
if (config_["escape"].isBool() && config_["escape"].asBool()) {
text_ = Glib::Markup::escape_text(line);
} else {
text_ = line;
}
tooltip_ = line;
class_ = "";
} else if (i == 1) {
tooltip_ = line;
} else if (i == 2) {
class_ = line;
} else {
break;
}
i++;
}
}
void waybar::modules::Custom::parseOutputJson()
{
std::istringstream output(output_.out);
std::string line;
while (getline(output, line)) {
auto parsed = parser_.parse(line);
if (config_["escape"].isBool() && config_["escape"].asBool()) {
text_ = Glib::Markup::escape_text(parsed["text"].asString());
} else {
text_ = parsed["text"].asString();
}
if (config_["escape"].isBool() && config_["escape"].asBool()) {
alt_ = Glib::Markup::escape_text(parsed["alt"].asString());
} else {
alt_ = parsed["alt"].asString();
}
tooltip_ = parsed["tooltip"].asString();
class_ = parsed["class"].asString();
if (!parsed["percentage"].asString().empty() && parsed["percentage"].isUInt()) {
percentage_ = parsed["percentage"].asUInt();
} else {
percentage_ = 0;
}
break;
}
}

View File

@ -0,0 +1,50 @@
#include "modules/idle_inhibitor.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)
{
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::onClick));
dp.emit();
}
waybar::modules::IdleInhibitor::~IdleInhibitor()
{
if(idle_inhibitor_) {
zwp_idle_inhibitor_v1_destroy(idle_inhibitor_);
idle_inhibitor_ = nullptr;
}
}
auto waybar::modules::IdleInhibitor::update() -> void
{
label_.set_markup(
fmt::format(format_, fmt::arg("status", status_),
fmt::arg("icon", getIcon(0, status_))));
if(tooltipEnabled()) {
label_.set_tooltip_text(status_);
}
}
bool waybar::modules::IdleInhibitor::onClick(GdkEventButton* const& e)
{
if(e->button == 1) {
if (idle_inhibitor_) {
zwp_idle_inhibitor_v1_destroy(idle_inhibitor_);
idle_inhibitor_ = nullptr;
status_ = "deactivated";
} else {
idle_inhibitor_ = zwp_idle_inhibit_manager_v1_create_inhibitor(
bar_.client.idle_inhibit_manager, bar_.surface);
status_ = "activated";
}
}
dp.emit();
return true;
}

View File

@ -1,25 +1,65 @@
#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"); 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
{ {
struct sysinfo info = {}; parseMeminfo();
if (sysinfo(&info) == 0) { if (memtotal_ > 0 && memfree_ >= 0) {
auto total = info.totalram * info.mem_unit; int used_ram_percentage = 100 * (memtotal_ - memfree_) / memtotal_;
auto freeram = info.freeram * info.mem_unit; label_.set_markup(fmt::format(format_, used_ram_percentage));
int used_ram_percentage = 100 * (total - freeram) / total; auto used_ram_gigabytes = (memtotal_ - memfree_) / std::pow(1024, 2);
label_.set_text(fmt::format(format_, used_ram_percentage)); if (tooltipEnabled()) {
auto used_ram_gigabytes = (total - freeram) / std::pow(1024, 3); label_.set_tooltip_text(fmt::format("{:.{}f}Gb used", used_ram_gigabytes, 1));
label_.set_tooltip_text(fmt::format("{:.{}f}Gb used", used_ram_gigabytes, 1)); }
event_box_.show();
} else {
event_box_.hide();
} }
} }
void waybar::modules::Memory::parseMeminfo()
{
long memfree = -1, membuffer = -1, memcache = -1, memavail = -1;
std::ifstream info(data_dir_);
if (!info.is_open()) {
throw std::runtime_error("Can't open " + data_dir_);
}
std::string line;
while (getline(info, line)) {
auto posDelim = line.find(":");
if (posDelim == std::string::npos) {
continue;
}
std::string name = line.substr(0, posDelim);
long value = std::stol(line.substr(posDelim + 1));
if (name.compare("MemTotal") == 0) {
memtotal_ = value;
} else if (name.compare("MemAvailable") == 0) {
memavail = value;
} else if (name.compare("MemFree") == 0) {
memfree = value;
} else if (name.compare("Buffers") == 0) {
membuffer = value;
} else if (name.compare("Cached") == 0) {
memcache = value;
}
if (memtotal_ > 0 &&
(memavail >= 0 || (memfree > -1 && membuffer > -1 && memcache > -1))) {
break;
}
}
memfree_ = memavail >= 0 ? memavail : memfree + membuffer + memcache;
}

View File

@ -1,19 +1,16 @@
#include <sys/eventfd.h>
#include "modules/network.hpp" #include "modules/network.hpp"
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), family_(AF_INET), efd_(-1), ev_fd_(-1),
signal_strength_dbm_(0), signal_strength_(0) cidr_(-1), signal_strength_dbm_(0), signal_strength_(0)
{ {
sock_fd_ = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); label_.set_name("network");
if (sock_fd_ < 0) { if (!id.empty()) {
throw std::runtime_error("Can't open network socket"); label_.get_style_context()->add_class(id);
}
nladdr_.nl_family = AF_NETLINK;
nladdr_.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR;
if (bind(sock_fd_, reinterpret_cast<struct sockaddr *>(&nladdr_),
sizeof(nladdr_)) != 0) {
throw std::runtime_error("Can't bind network socket");
} }
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();
@ -28,80 +25,148 @@ waybar::modules::Network::Network(const Json::Value& config)
ifname_ = ifname; ifname_ = ifname;
} }
} }
initNL80211(); dp.emit();
label_.set_name("network"); worker();
// Trigger first values
getInfo();
update();
thread_ = [this] {
char buf[4096];
uint64_t len = netlinkResponse(sock_fd_, buf, sizeof(buf),
RTMGRP_LINK | RTMGRP_IPV4_IFADDR);
bool need_update = false;
for (auto nh = reinterpret_cast<struct nlmsghdr *>(buf); NLMSG_OK(nh, len);
nh = NLMSG_NEXT(nh, len)) {
if (nh->nlmsg_type == NLMSG_DONE) {
break;
}
if (nh->nlmsg_type == NLMSG_ERROR) {
continue;
}
if (nh->nlmsg_type < RTM_NEWADDR) {
auto rtif = static_cast<struct ifinfomsg *>(NLMSG_DATA(nh));
if (rtif->ifi_index == static_cast<int>(ifid_)) {
need_update = true;
if (!(rtif->ifi_flags & IFF_RUNNING)) {
disconnected();
}
}
}
}
if (ifid_ <= 0 && !config_["interface"].isString()) {
// Need to wait before get external interface
thread_.sleep_for(std::chrono::seconds(1));
ifid_ = getExternalInterface();
if (ifid_ > 0) {
char ifname[IF_NAMESIZE];
if_indextoname(ifid_, ifname);
ifname_ = ifname;
need_update = true;
}
}
if (need_update) {
getInfo();
dp.emit();
}
};
} }
waybar::modules::Network::~Network() waybar::modules::Network::~Network()
{ {
close(sock_fd_); if (ev_fd_ > -1) {
nl_socket_free(sk_); eventfd_write(ev_fd_, 1);
std::this_thread::sleep_for(std::chrono::milliseconds(150));
close(ev_fd_);
}
if (efd_ > -1) {
close(efd_);
}
if (info_sock_ != nullptr) {
nl_socket_drop_membership(info_sock_, RTMGRP_LINK);
nl_socket_drop_membership(info_sock_, RTMGRP_IPV4_IFADDR);
nl_close(info_sock_);
nl_socket_free(info_sock_);
}
if (sk_ != nullptr) {
nl_close(sk_);
nl_socket_free(sk_);
}
}
void waybar::modules::Network::createInfoSocket()
{
info_sock_ = nl_socket_alloc();
if (nl_connect(info_sock_, NETLINK_ROUTE) != 0) {
throw std::runtime_error("Can't connect network socket");
}
if (nl_socket_add_membership(info_sock_, RTMGRP_LINK) != 0) {
throw std::runtime_error("Can't add membership");
}
if (nl_socket_add_membership(info_sock_, RTMGRP_IPV4_IFADDR) != 0) {
throw std::runtime_error("Can't add membership");
}
nl_socket_disable_seq_check(info_sock_);
nl_socket_set_nonblocking(info_sock_);
nl_socket_modify_cb(info_sock_, NL_CB_VALID, NL_CB_CUSTOM, handleEvents, this);
efd_ = epoll_create1(0);
if (efd_ < 0) {
throw std::runtime_error("Can't create epoll");
}
{
ev_fd_ = eventfd(0, EFD_NONBLOCK);
struct epoll_event event;
event.events = EPOLLIN | EPOLLET;
event.data.fd = ev_fd_;
if (epoll_ctl(efd_, EPOLL_CTL_ADD, ev_fd_, &event) == -1) {
throw std::runtime_error("Can't add epoll event");
}
}
{
auto fd = nl_socket_get_fd(info_sock_);
struct epoll_event event;
event.events = EPOLLIN | EPOLLET | EPOLLRDHUP;
event.data.fd = fd;
if (epoll_ctl(efd_, EPOLL_CTL_ADD, fd, &event) == -1) {
throw std::runtime_error("Can't add epoll event");
}
}
}
void waybar::modules::Network::createEventSocket()
{
sk_ = nl_socket_alloc();
if (genl_connect(sk_) != 0) {
throw std::runtime_error("Can't connect to netlink socket");
}
if (nl_socket_modify_cb(sk_, NL_CB_VALID, NL_CB_CUSTOM, handleScan, this) < 0) {
throw std::runtime_error("Can't set callback");
}
nl80211_id_ = genl_ctrl_resolve(sk_, "nl80211");
if (nl80211_id_ < 0) {
throw std::runtime_error("Can't resolve nl80211 interface");
}
}
void waybar::modules::Network::worker()
{
thread_timer_ = [this] {
if (ifid_ > 0) {
getInfo();
dp.emit();
}
thread_timer_.sleep_for(interval_);
};
thread_ = [this] {
struct epoll_event events[16];
int ec = epoll_wait(efd_, events, 16, -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
{ {
auto format = format_; std::string connectiontype;
if (ifid_ <= 0) { if (ifid_ <= 0 || ipaddr_.empty()) {
format = config_["format-disconnected"].isString() if (config_["format-disconnected"].isString()) {
? config_["format-disconnected"].asString() : format; default_format_ = config_["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();
}
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();
}
connectiontype = "wifi";
} }
label_.get_style_context()->remove_class("disconnected"); label_.get_style_context()->remove_class("disconnected");
} }
label_.set_text(fmt::format(format, if (!alt_) {
format_ = default_format_;
}
label_.set_markup(fmt::format(format_,
fmt::arg("essid", essid_), fmt::arg("essid", essid_),
fmt::arg("signaldBm", signal_strength_dbm_), fmt::arg("signaldBm", signal_strength_dbm_),
fmt::arg("signalStrength", signal_strength_), fmt::arg("signalStrength", signal_strength_),
fmt::arg("ifname", ifname_) fmt::arg("ifname", ifname_),
fmt::arg("netmask", netmask_),
fmt::arg("ipaddr", ipaddr_),
fmt::arg("cidr", cidr_),
fmt::arg("icon", getIcon(signal_strength_, connectiontype))
)); ));
} }
@ -110,26 +175,15 @@ void waybar::modules::Network::disconnected()
essid_.clear(); essid_.clear();
signal_strength_dbm_ = 0; signal_strength_dbm_ = 0;
signal_strength_ = 0; signal_strength_ = 0;
ifname_.clear(); ipaddr_.clear();
ifid_ = -1; netmask_.clear();
} cidr_ = 0;
if (!config_["interface"].isString()) {
void waybar::modules::Network::initNL80211() ifname_.clear();
{ ifid_ = -1;
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
@ -155,7 +209,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;
} }
@ -165,7 +219,7 @@ 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;
} }
@ -255,7 +309,36 @@ out:
return ifidx; return ifidx;
} }
int waybar::modules::Network::netlinkRequest(int fd, void *req, void waybar::modules::Network::getInterfaceAddress() {
unsigned int cidrRaw;
struct ifaddrs *ifaddr, *ifa;
ipaddr_.clear();
netmask_.clear();
cidr_ = 0;
int success = getifaddrs(&ifaddr);
if (success == 0) {
ifa = ifaddr;
while (ifa != nullptr && ipaddr_.empty() && netmask_.empty()) {
if (ifa->ifa_addr != nullptr && ifa->ifa_addr->sa_family == family_) {
if (strcmp(ifa->ifa_name, ifname_.c_str()) == 0) {
ipaddr_ = inet_ntoa(((struct sockaddr_in*)ifa->ifa_addr)->sin_addr);
netmask_ = inet_ntoa(((struct sockaddr_in*)ifa->ifa_netmask)->sin_addr);
cidrRaw = ((struct sockaddr_in *)(ifa->ifa_netmask))->sin_addr.s_addr;
unsigned int cidr = 0;
while (cidrRaw) {
cidr += cidrRaw & 1;
cidrRaw >>= 1;
}
cidr_ = cidr;
}
}
ifa = ifa->ifa_next;
}
freeifaddrs(ifaddr);
}
}
int waybar::modules::Network::netlinkRequest(void *req,
uint32_t reqlen, uint32_t groups) uint32_t reqlen, uint32_t groups)
{ {
struct sockaddr_nl sa = {}; struct sockaddr_nl sa = {};
@ -263,26 +346,67 @@ int waybar::modules::Network::netlinkRequest(int fd, void *req,
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) {
auto net = static_cast<waybar::modules::Network *>(data);
bool need_update = false;
nlmsghdr *nh = nlmsg_hdr(msg);
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();
}
}
}
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 net = static_cast<waybar::modules::Network *>(data);
auto gnlh = static_cast<genlmsghdr *>(nlmsg_data(nlmsg_hdr(msg))); auto gnlh = static_cast<genlmsghdr *>(nlmsg_data(nlmsg_hdr(msg)));
struct nlattr* tb[NL80211_ATTR_MAX + 1]; struct nlattr* tb[NL80211_ATTR_MAX + 1];
@ -338,9 +462,8 @@ void waybar::modules::Network::parseEssid(struct nlattr **bss)
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;
@ -348,6 +471,9 @@ void waybar::modules::Network::parseSignal(struct nlattr **bss) {
signal_strength_ = ((signal_strength_dbm_ - hardwareMin) signal_strength_ = ((signal_strength_dbm_ - hardwareMin)
/ double{hardwareMax - hardwareMin}) * 100; / 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)
@ -368,9 +494,9 @@ 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(nl_msg, NL_AUTO_PORT, NL_AUTO_SEQ, nl80211_id_, 0, NLM_F_DUMP,

View File

@ -1,10 +1,18 @@
#include "modules/pulseaudio.hpp" #include "modules/pulseaudio.hpp"
waybar::modules::Pulseaudio::Pulseaudio(const Json::Value& config) waybar::modules::Pulseaudio::Pulseaudio(const std::string& id, const Json::Value &config)
: ALabel(config, "{volume}%"), mainloop_(nullptr), mainloop_api_(nullptr), : ALabel(config, "{volume}%"),
context_(nullptr), sink_idx_(0), volume_(0), muted_(false) mainloop_(nullptr),
{ mainloop_api_(nullptr),
context_(nullptr),
sink_idx_(0),
volume_(0),
muted_(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.");
@ -26,10 +34,18 @@ waybar::modules::Pulseaudio::Pulseaudio(const Json::Value& config)
throw std::runtime_error("pa_mainloop_run() failed."); throw std::runtime_error("pa_mainloop_run() failed.");
} }
pa_threaded_mainloop_unlock(mainloop_); pa_threaded_mainloop_unlock(mainloop_);
// define the pulse scroll events only when no user provided
// events are configured
if (!config["on-scroll-up"].isString() &&
!config["on-scroll-down"].isString()) {
event_box_.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);
event_box_.signal_scroll_event().connect(
sigc::mem_fun(*this, &Pulseaudio::handleScroll));
}
} }
waybar::modules::Pulseaudio::~Pulseaudio() waybar::modules::Pulseaudio::~Pulseaudio() {
{
mainloop_api_->quit(mainloop_api_, 0); mainloop_api_->quit(mainloop_api_, 0);
pa_threaded_mainloop_stop(mainloop_); pa_threaded_mainloop_stop(mainloop_);
pa_threaded_mainloop_free(mainloop_); pa_threaded_mainloop_free(mainloop_);
@ -58,6 +74,46 @@ void waybar::modules::Pulseaudio::contextStateCb(pa_context *c, void *data)
} }
} }
bool waybar::modules::Pulseaudio::handleScroll(GdkEventScroll *e) {
// Avoid concurrent scroll event
bool direction_up = false;
uint16_t change = config_["scroll-step"].isUInt() ? config_["scroll-step"].asUInt() * 100 : 100;
pa_cvolume pa_volume = pa_volume_;
if (scrolling_) {
return false;
}
scrolling_ = true;
if (e->direction == GDK_SCROLL_UP) {
direction_up = true;
}
if (e->direction == GDK_SCROLL_DOWN) {
direction_up = false;
}
if (e->direction == GDK_SCROLL_SMOOTH) {
gdouble delta_x, delta_y;
gdk_event_get_scroll_deltas(reinterpret_cast<const GdkEvent *>(e), &delta_x,
&delta_y);
if (delta_y < 0) {
direction_up = true;
} else if (delta_y > 0) {
direction_up = false;
}
}
if (direction_up) {
if (volume_ + 1 < 100) pa_cvolume_inc(&pa_volume, change);
} else {
if (volume_ - 1 > 0) pa_cvolume_dec(&pa_volume, change);
}
pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume,
volumeModifyCb, this);
return true;
}
/* /*
* Called when an event we subscribed to occurs. * Called when an event we subscribed to occurs.
*/ */
@ -75,16 +131,29 @@ void waybar::modules::Pulseaudio::subscribeCb(pa_context* context,
} }
} }
/*
* Called in response to a volume change request
*/
void waybar::modules::Pulseaudio::volumeModifyCb(pa_context *c, int success,
void *data) {
auto pa = static_cast<waybar::modules::Pulseaudio *>(data);
if (success) {
pa_context_get_sink_info_by_index(pa->context_, pa->sink_idx_, sinkInfoCb,
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, int /*eol*/, void* data) const pa_sink_info *i, int /*eol*/,
{ void *data) {
if (i != nullptr) { if (i != nullptr) {
auto pa = static_cast<waybar::modules::Pulseaudio *>(data); auto pa = static_cast<waybar::modules::Pulseaudio *>(data);
float volume = static_cast<float>(pa_cvolume_avg(&(i->volume))) pa->pa_volume_ = i->volume;
/ float{PA_VOLUME_NORM}; float volume = static_cast<float>(pa_cvolume_avg(&(pa->pa_volume_))) /
float{PA_VOLUME_NORM};
pa->sink_idx_ = i->index; pa->sink_idx_ = i->index;
pa->volume_ = std::round(volume * 100.0f); pa->volume_ = std::round(volume * 100.0f);
pa->muted_ = i->mute != 0; pa->muted_ = i->mute != 0;
@ -123,7 +192,7 @@ const std::string waybar::modules::Pulseaudio::getPortIcon() const
return port; return port;
} }
} }
return ""; return port_name_;
} }
auto waybar::modules::Pulseaudio::update() -> void auto waybar::modules::Pulseaudio::update() -> void
@ -133,16 +202,23 @@ auto waybar::modules::Pulseaudio::update() -> void
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_);
}
if (scrolling_) {
scrolling_ = false;
} }
label_.set_label(fmt::format(format,
fmt::arg("volume", volume_),
fmt::arg("icon", getIcon(volume_, getPortIcon()))));
label_.set_tooltip_text(desc_);
} }

View File

@ -1,56 +1,53 @@
#include "modules/sni/snh.hpp" #include "modules/sni/host.hpp"
#include <iostream> #include <iostream>
using namespace waybar::modules::SNI; using namespace waybar::modules::SNI;
Host::Host(Glib::Dispatcher* dp, const Json::Value &config) Host::Host(const std::size_t id, const Json::Value &config,
: dp_(dp), config_(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)
{ {
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, Host::~Host()
const gchar* name, gpointer data)
{ {
auto host = static_cast<SNI::Host *>(data); Gio::DBus::unwatch_name(bus_name_id_);
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, void Host::busAcquired(const Glib::RefPtr<Gio::DBus::Connection>& conn, Glib::ustring name)
const gchar* name, const gchar* name_owner, gpointer data)
{ {
auto host = static_cast<SNI::Host *>(data); watcher_id_ = Gio::DBus::watch_name(conn, "org.kde.StatusNotifierWatcher",
if (host->cancellable_ != nullptr) { 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 // TODO
return;
} }
host->cancellable_ = g_cancellable_new(); cancellable_ = g_cancellable_new();
sn_watcher_proxy_new( sn_watcher_proxy_new(
connection, conn->gobj(),
G_DBUS_PROXY_FLAGS_NONE, G_DBUS_PROXY_FLAGS_NONE,
"org.kde.StatusNotifierWatcher", "org.kde.StatusNotifierWatcher",
"/StatusNotifierWatcher", "/StatusNotifierWatcher",
host->cancellable_, &Host::proxyReady, data); cancellable_, &Host::proxyReady, this);
} }
void Host::nameVanished(GDBusConnection* connection, void Host::nameVanished(const Glib::RefPtr<Gio::DBus::Connection>& conn, const Glib::ustring name)
const gchar* name, gpointer data)
{ {
auto host = static_cast<SNI::Host *>(data); g_cancellable_cancel(cancellable_);
g_cancellable_cancel(host->cancellable_); g_clear_object(&cancellable_);
g_clear_object(&host->cancellable_); g_clear_object(&watcher_);
g_clear_object(&host->watcher_); items_.clear();
host->items.clear();
} }
void Host::proxyReady(GObject* src, GAsyncResult* res, void Host::proxyReady(GObject* src, GAsyncResult* res,
@ -104,8 +101,7 @@ void Host::registerHost(GObject* src, GAsyncResult* res,
g_strfreev(items); g_strfreev(items);
} }
void Host::itemRegistered( void Host::itemRegistered(SnWatcher* watcher, const gchar* service, gpointer data)
SnWatcher* watcher, const gchar* service, gpointer data)
{ {
auto host = static_cast<SNI::Host *>(data); auto host = static_cast<SNI::Host *>(data);
host->addRegisteredItem(service); host->addRegisteredItem(service);
@ -116,35 +112,28 @@ void Host::itemUnregistered(
{ {
auto host = static_cast<SNI::Host *>(data); auto host = static_cast<SNI::Host *>(data);
auto [bus_name, object_path] = host->getBusNameAndObjectPath(service); auto [bus_name, object_path] = host->getBusNameAndObjectPath(service);
for (auto it = host->items.begin(); it != host->items.end(); ++it) { for (auto it = host->items_.begin(); it != host->items_.end(); ++it) {
if (it->bus_name == bus_name && it->object_path == object_path) { if ((*it)->bus_name == bus_name && (*it)->object_path == object_path) {
host->items.erase(it); host->on_remove_(*it);
host->items_.erase(it);
break; break;
} }
} }
host->dp_->emit();
} }
std::tuple<std::string, std::string> Host::getBusNameAndObjectPath( std::tuple<std::string, std::string> Host::getBusNameAndObjectPath(
const gchar* service) const std::string service)
{ {
std::string bus_name; auto it = service.find("/");
std::string object_path; if (it != std::string::npos) {
gchar* tmp = g_strstr_len(service, -1, "/"); return {service.substr(0, it), service.substr(it)};
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 }; return {service, "/StatusNotifierItem"};
} }
void Host::addRegisteredItem(const gchar* service) void Host::addRegisteredItem(std::string service)
{ {
auto [bus_name, object_path] = getBusNameAndObjectPath(service); auto [bus_name, object_path] = getBusNameAndObjectPath(service);
items.emplace_back(bus_name, object_path, dp_, config_); items_.emplace_back(new Item(bus_name, object_path, config_));
on_add_(items_.back());
} }

View File

@ -1,17 +1,14 @@
#include "modules/sni/sni.hpp" #include "modules/sni/item.hpp"
#include <iostream> #include <iostream>
#include <libdbusmenu-gtk/dbusmenu-gtk.h>
waybar::modules::SNI::Item::Item(std::string bn, std::string op, waybar::modules::SNI::Item::Item(std::string bn, std::string op, const Json::Value& config)
Glib::Dispatcher *dp, Json::Value config) : bus_name(bn), object_path(op), icon_size(16), effective_icon_size(0)
: bus_name(bn), object_path(op), event_box(), icon_size(16), {
effective_icon_size(0), image(Gtk::manage(new Gtk::Image())), if (config["icon-size"].isUInt()) {
dp_(dp), config_(config) { icon_size = config["icon-size"].asUInt();
if (config_["icon-size"].isUInt()) {
icon_size = config_["icon-size"].asUInt();
} }
event_box.add(*image); event_box.add(image);
event_box.add_events(Gdk::BUTTON_PRESS_MASK); event_box.add_events(Gdk::BUTTON_PRESS_MASK);
event_box.signal_button_press_event().connect( event_box.signal_button_press_event().connect(
sigc::mem_fun(*this, &Item::handleClick)); sigc::mem_fun(*this, &Item::handleClick));
@ -39,11 +36,10 @@ void waybar::modules::SNI::Item::proxyReady(GObject *obj, GAsyncResult *res,
auto conn = g_dbus_proxy_get_connection(G_DBUS_PROXY(proxy)); auto conn = g_dbus_proxy_get_connection(G_DBUS_PROXY(proxy));
g_dbus_connection_call(conn, item->bus_name.c_str(), g_dbus_connection_call(conn, item->bus_name.c_str(),
item->object_path.c_str(), item->object_path.c_str(), "org.freedesktop.DBus.Properties", "GetAll",
"org.freedesktop.DBus.Properties", "GetAll", g_variant_new("(s)", "org.kde.StatusNotifierItem"),
g_variant_new("(s)", "org.kde.StatusNotifierItem"), G_VARIANT_TYPE("(a{sv})"), G_DBUS_CALL_FLAGS_NONE, -1, item->cancellable_,
G_VARIANT_TYPE("(a{sv})"), G_DBUS_CALL_FLAGS_NONE, -1, &Item::getAll, data);
item->cancellable_, &Item::getAll, data);
} }
void waybar::modules::SNI::Item::getAll(GObject *obj, GAsyncResult *res, void waybar::modules::SNI::Item::getAll(GObject *obj, GAsyncResult *res,
@ -106,18 +102,16 @@ void waybar::modules::SNI::Item::getAll(GObject *obj, GAsyncResult *res,
g_variant_unref(properties); g_variant_unref(properties);
if (item->id.empty() || item->category.empty() || item->status.empty()) { if (item->id.empty() || item->category.empty() || item->status.empty()) {
std::cerr << "Invalid Status Notifier Item: " + item->bus_name + "," + std::cerr << "Invalid Status Notifier Item: " + item->bus_name + "," +
item->object_path item->object_path << std::endl;
<< std::endl;
return; return;
} }
if (!item->icon_theme_path.empty()) { if (!item->icon_theme_path.empty()) {
GtkIconTheme *icon_theme = gtk_icon_theme_get_default(); GtkIconTheme *icon_theme = gtk_icon_theme_get_default();
gtk_icon_theme_append_search_path(icon_theme, gtk_icon_theme_append_search_path(icon_theme,
item->icon_theme_path.c_str()); item->icon_theme_path.c_str());
} }
item->updateImage(); item->updateImage();
item->updateMenu(); // item->event_box.set_tooltip_text(item->title);
item->dp_->emit();
// TODO: handle change // TODO: handle change
} }
@ -169,35 +163,28 @@ waybar::modules::SNI::Item::extractPixBuf(GVariant *variant) {
return Glib::RefPtr<Gdk::Pixbuf>{}; return Glib::RefPtr<Gdk::Pixbuf>{};
} }
void waybar::modules::SNI::Item::updateMenu()
{
event_box.set_tooltip_text(title);
if (!menu.empty()) {
auto *dbmenu = dbusmenu_gtkmenu_new(bus_name.data(), menu.data());
if (dbmenu) {
gtk_menu = Glib::wrap(GTK_MENU(dbmenu), false);
}
}
}
void waybar::modules::SNI::Item::updateImage() void waybar::modules::SNI::Item::updateImage()
{ {
image->set_from_icon_name("image-missing", Gtk::ICON_SIZE_MENU); image.set_from_icon_name("image-missing", Gtk::ICON_SIZE_MENU);
image->set_pixel_size(icon_size); image.set_pixel_size(icon_size);
if (!icon_name.empty()) { if (!icon_name.empty()) {
try { try {
// Try to find icons specified by path and filename // 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)) { if (std::filesystem::exists(icon_name)) {
#endif
auto pixbuf = Gdk::Pixbuf::create_from_file(icon_name); auto pixbuf = Gdk::Pixbuf::create_from_file(icon_name);
if (pixbuf->gobj() != nullptr) { if (pixbuf->gobj() != nullptr) {
// An icon specified by path and filename may be the wrong size for // An icon specified by path and filename may be the wrong size for
// the tray // the tray
pixbuf = pixbuf->scale_simple(icon_size, icon_size, pixbuf = pixbuf->scale_simple(icon_size, icon_size,
Gdk::InterpType::INTERP_BILINEAR); Gdk::InterpType::INTERP_BILINEAR);
image->set(pixbuf); image.set(pixbuf);
} }
} else { } else {
image->set(getIconByName(icon_name, icon_size)); image.set(getIconByName(icon_name, icon_size));
} }
} catch (Glib::Error &e) { } catch (Glib::Error &e) {
std::cerr << "Exception: " << e.what() << std::endl; std::cerr << "Exception: " << e.what() << std::endl;
@ -206,7 +193,7 @@ void waybar::modules::SNI::Item::updateImage()
// An icon extracted may be the wrong size for the tray // An icon extracted may be the wrong size for the tray
icon_pixmap = icon_pixmap->scale_simple(icon_size, icon_size, icon_pixmap = icon_pixmap->scale_simple(icon_size, icon_size,
Gdk::InterpType::INTERP_BILINEAR); Gdk::InterpType::INTERP_BILINEAR);
image->set(icon_pixmap); image.set(icon_pixmap);
} }
} }
@ -222,7 +209,7 @@ waybar::modules::SNI::Item::getIconByName(std::string name, int request_size) {
if (size == request_size || size == -1) { if (size == request_size || size == -1) {
tmp_size = request_size; tmp_size = request_size;
break; break;
} else if (size < request_size || size > tmp_size) { } else if (size < request_size || (size > tmp_size && tmp_size > 0)) {
tmp_size = size; tmp_size = size;
} }
} }
@ -230,38 +217,49 @@ waybar::modules::SNI::Item::getIconByName(std::string name, int request_size) {
tmp_size = request_size; tmp_size = request_size;
} }
return icon_theme->load_icon(name.c_str(), tmp_size, return icon_theme->load_icon(name.c_str(), tmp_size,
Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE); Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE);
} }
void waybar::modules::SNI::Item::handleActivate(GObject *src, GAsyncResult *res, void waybar::modules::SNI::Item::onMenuDestroyed(Item *self)
gpointer data) { {
auto item = static_cast<SNI::Item *>(data); self->gtk_menu = nullptr;
sn_item_call_activate_finish(item->proxy_, res, nullptr); self->dbus_menu = nullptr;
} }
void waybar::modules::SNI::Item::handleSecondaryActivate(GObject *src, bool waybar::modules::SNI::Item::makeMenu(GdkEventButton *const &ev)
GAsyncResult *res, {
gpointer data) { if (gtk_menu == nullptr) {
auto item = static_cast<SNI::Item *>(data); if (!menu.empty()) {
sn_item_call_secondary_activate_finish(item->proxy_, res, nullptr); 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 waybar::modules::SNI::Item::handleClick(GdkEventButton *const &ev) { bool waybar::modules::SNI::Item::handleClick(GdkEventButton *const &ev) {
if (ev->type == GDK_BUTTON_PRESS) { if ((ev->button == 1 && item_is_menu) || ev->button == 3) {
if (gtk_menu) { if (!makeMenu(ev)) {
if (!gtk_menu->get_attach_widget()) { return sn_item_call_context_menu_sync(proxy_, ev->x, ev->y, nullptr, nullptr);
gtk_menu->attach_to_widget(event_box);
}
gtk_menu->popup(ev->button, ev->time);
} else {
sn_item_call_activate(
proxy_, ev->x, ev->y, nullptr, &Item::handleActivate, this);
} }
} else if (ev->type == GDK_2BUTTON_PRESS) { } else if (ev->button == 1) {
sn_item_call_secondary_activate( return sn_item_call_activate_sync(proxy_, ev->x, ev->y, nullptr, nullptr);
proxy_, ev->x, ev->y, nullptr, &Item::handleSecondaryActivate, this); } else if (ev->button == 2) {
} else { return sn_item_call_secondary_activate_sync(proxy_, ev->x, ev->y,
return false; nullptr, nullptr);
} }
return true; return false;
} }

View File

@ -2,20 +2,31 @@
#include <iostream> #include <iostream>
waybar::modules::SNI::Tray::Tray(const Json::Value &config) waybar::modules::SNI::Tray::Tray(const std::string& id, const Json::Value &config)
: config_(config), watcher_(), host_(&dp, config) : config_(config), watcher_(), host_(nb_hosts_, config,
std::bind(&Tray::onAdd, this, std::placeholders::_1),
std::bind(&Tray::onRemove, this, std::placeholders::_1))
{ {
std::cout << "Tray is in beta, so there may be bugs or even be unusable." << std::endl;
if (config_["spacing"].isUInt()) { if (config_["spacing"].isUInt()) {
box_.set_spacing(config_["spacing"].asUInt()); box_.set_spacing(config_["spacing"].asUInt());
} }
nb_hosts_ += 1;
}
void waybar::modules::SNI::Tray::onAdd(std::unique_ptr<Item>& item)
{
box_.pack_start(item->event_box);
dp.emit();
}
void waybar::modules::SNI::Tray::onRemove(std::unique_ptr<Item>& item)
{
box_.remove(item->event_box);
dp.emit();
} }
auto waybar::modules::SNI::Tray::update() -> void { auto waybar::modules::SNI::Tray::update() -> void {
auto childrens = box_.get_children();
childrens.erase(childrens.begin(), childrens.end());
for (auto &item : host_.items) {
box_.pack_start(item.event_box);
}
if (box_.get_children().size() > 0) { if (box_.get_children().size() > 0) {
box_.set_name("tray"); box_.set_name("tray");
box_.show_all(); box_.show_all();

View File

@ -1,41 +1,32 @@
#include "modules/sni/snw.hpp" #include "modules/sni/watcher.hpp"
#include <iostream> #include <iostream>
using namespace waybar::modules::SNI; using namespace waybar::modules::SNI;
Watcher::Watcher() Watcher::Watcher()
{ : bus_name_id_(Gio::DBus::own_name(Gio::DBus::BusType::BUS_TYPE_SESSION,
GBusNameOwnerFlags flags = static_cast<GBusNameOwnerFlags>( "org.kde.StatusNotifierWatcher", sigc::mem_fun(*this, &Watcher::busAcquired),
G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT Gio::DBus::SlotNameAcquired(), Gio::DBus::SlotNameLost(),
| G_BUS_NAME_OWNER_FLAGS_REPLACE); Gio::DBus::BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT | Gio::DBus::BUS_NAME_OWNER_FLAGS_REPLACE)),
bus_name_id_ = g_bus_own_name(G_BUS_TYPE_SESSION, watcher_(sn_watcher_skeleton_new())
"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, void Watcher::busAcquired(const Glib::RefPtr<Gio::DBus::Connection>& conn, Glib::ustring name)
gpointer data)
{ {
GError* error = nullptr; GError* error = nullptr;
auto host = static_cast<SNI::Watcher*>(data); g_dbus_interface_skeleton_export(G_DBUS_INTERFACE_SKELETON(watcher_),
g_dbus_interface_skeleton_export(G_DBUS_INTERFACE_SKELETON(host->watcher_), conn->gobj(), "/StatusNotifierWatcher", &error);
connection, "/StatusNotifierWatcher", &error);
if (error != nullptr) { if (error != nullptr) {
std::cerr << error->message << std::endl; std::cerr << error->message << std::endl;
g_error_free(error); g_error_free(error);
return; return;
} }
g_signal_connect_swapped(host->watcher_, "handle-register-item", g_signal_connect_swapped(watcher_, "handle-register-item",
G_CALLBACK(&Watcher::handleRegisterItem), data); G_CALLBACK(&Watcher::handleRegisterItem), this);
g_signal_connect_swapped(host->watcher_, "handle-register-host", g_signal_connect_swapped(watcher_, "handle-register-host",
G_CALLBACK(&Watcher::handleRegisterHost), data); G_CALLBACK(&Watcher::handleRegisterHost), this);
} }
gboolean Watcher::handleRegisterHost(Watcher* obj, gboolean Watcher::handleRegisterHost(Watcher* obj,

View File

@ -1,11 +1,18 @@
#include "modules/sway/ipc/client.hpp" #include "modules/sway/ipc/client.hpp"
waybar::modules::sway::Ipc::Ipc() waybar::modules::sway::Ipc::Ipc()
: fd_(-1), fd_event_(-1) {
{} const std::string& socketPath = getSocketPath();
fd_ = open(socketPath);
fd_event_ = open(socketPath);
}
waybar::modules::sway::Ipc::~Ipc() waybar::modules::sway::Ipc::~Ipc()
{ {
// To fail the IPC header
write(fd_, "close-sway-ipc", 14);
write(fd_event_, "close-sway-ipc", 14);
close(fd_); close(fd_);
close(fd_event_); close(fd_event_);
} }
@ -53,13 +60,6 @@ int waybar::modules::sway::Ipc::open(const std::string& socketPath) const
return fd; return fd;
} }
void waybar::modules::sway::Ipc::connect()
{
const std::string& socketPath = getSocketPath();
fd_ = open(socketPath);
fd_event_ = open(socketPath);
}
struct waybar::modules::sway::Ipc::ipc_response struct waybar::modules::sway::Ipc::ipc_response
waybar::modules::sway::Ipc::recv(int fd) const waybar::modules::sway::Ipc::recv(int fd) const
{ {
@ -69,22 +69,25 @@ struct waybar::modules::sway::Ipc::ipc_response
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 (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.reserve(data32[0] + 1);
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"); throw std::runtime_error("Unable to receive IPC payload");
} }
total += res; total += res;
} }
@ -131,4 +134,4 @@ struct waybar::modules::sway::Ipc::ipc_response
waybar::modules::sway::Ipc::handleEvent() const waybar::modules::sway::Ipc::handleEvent() const
{ {
return recv(fd_event_); return recv(fd_event_);
} }

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

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

View File

@ -1,11 +1,17 @@
#include "modules/sway/window.hpp" #include "modules/sway/window.hpp"
waybar::modules::sway::Window::Window(Bar &bar, const Json::Value& config) waybar::modules::sway::Window::Window(const std::string& id, const Bar &bar, const Json::Value& config)
: ALabel(config, "{}"), bar_(bar), windowId_(-1) : ALabel(config, "{}"), bar_(bar), windowId_(-1)
{ {
label_.set_name("window"); label_.set_name("window");
ipc_.connect(); if (!id.empty()) {
ipc_.subscribe("[ \"window\" ]"); label_.get_style_context()->add_class(id);
}
if (label_.get_max_width_chars() == -1) {
label_.set_hexpand(true);
label_.set_ellipsize(Pango::EllipsizeMode::ELLIPSIZE_END);
}
ipc_.subscribe("[\"window\",\"workspace\"]");
getFocusedWindow(); getFocusedWindow();
// Launch worker // Launch worker
worker(); worker();
@ -17,28 +23,34 @@ void waybar::modules::sway::Window::worker()
try { try {
auto res = ipc_.handleEvent(); auto res = ipc_.handleEvent();
auto parsed = parser_.parse(res.payload); auto parsed = parser_.parse(res.payload);
// Check for waybar prevents flicker when hovering window module
if ((parsed["change"] == "focus" || parsed["change"] == "title") if ((parsed["change"] == "focus" || parsed["change"] == "title")
&& parsed["container"]["focused"].asBool()) { && parsed["container"]["focused"].asBool()
window_ = parsed["container"]["name"].asString(); && parsed["container"]["name"].asString() != "waybar") {
window_ = Glib::Markup::escape_text(parsed["container"]["name"].asString());
windowId_ = parsed["container"]["id"].asInt(); windowId_ = parsed["container"]["id"].asInt();
dp.emit(); dp.emit();
} else if (parsed["change"] == "close" } else if ((parsed["change"] == "close"
&& parsed["container"]["focused"].asBool() && parsed["container"]["focused"].asBool()
&& windowId_ == parsed["container"]["id"].asInt()) { && windowId_ == parsed["container"]["id"].asInt())
|| (parsed["change"] == "focus" && parsed["current"]["focus"].isArray()
&& parsed["current"]["focus"].empty())) {
window_.clear(); window_.clear();
windowId_ = -1; windowId_ = -1;
dp.emit(); 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 waybar::modules::sway::Window::update() -> void
{ {
label_.set_text(fmt::format(format_, window_)); label_.set_markup(fmt::format(format_, window_));
label_.set_tooltip_text(window_); if (tooltipEnabled()) {
label_.set_tooltip_text(window_);
}
} }
std::tuple<int, std::string> waybar::modules::sway::Window::getFocusedNode( std::tuple<int, std::string> waybar::modules::sway::Window::getFocusedNode(

View File

@ -1,11 +1,13 @@
#include "modules/sway/workspaces.hpp" #include "modules/sway/workspaces.hpp"
waybar::modules::sway::Workspaces::Workspaces(Bar& bar, waybar::modules::sway::Workspaces::Workspaces(const std::string& id, const Bar& bar,
const Json::Value& config) const Json::Value& config)
: bar_(bar), config_(config), scrolling_(false) : bar_(bar), config_(config), scrolling_(false)
{ {
box_.set_name("workspaces"); box_.set_name("workspaces");
ipc_.connect(); if (!id.empty()) {
box_.get_style_context()->add_class(id);
}
ipc_.subscribe("[ \"workspace\" ]"); ipc_.subscribe("[ \"workspace\" ]");
// Launch worker // Launch worker
worker(); worker();
@ -15,22 +17,19 @@ void waybar::modules::sway::Workspaces::worker()
{ {
thread_ = [this] { thread_ = [this] {
try { try {
// Wait for the name of the output if (!workspaces_.empty()) {
if (!config_["all-outputs"].asBool() && bar_.output_name.empty()) {
while (bar_.output_name.empty()) {
thread_.sleep_for(chrono::milliseconds(150));
}
} else if (!workspaces_.empty()) {
ipc_.handleEvent(); ipc_.handleEvent();
} }
{ {
std::lock_guard<std::mutex> lock(mutex_); std::lock_guard<std::mutex> lock(mutex_);
auto res = ipc_.sendCmd(IPC_GET_WORKSPACES); auto res = ipc_.sendCmd(IPC_GET_WORKSPACES);
workspaces_ = parser_.parse(res.payload); if (thread_.isRunning()) {
workspaces_ = parser_.parse(res.payload);
}
} }
dp.emit(); dp.emit();
} catch (const std::exception& e) { } catch (const std::exception& e) {
std::cerr << e.what() << std::endl; std::cerr << "Workspaces: " << e.what() << std::endl;
} }
}; };
} }
@ -41,8 +40,9 @@ auto waybar::modules::sway::Workspaces::update() -> void
std::lock_guard<std::mutex> lock(mutex_); 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](auto node) -> bool { return node["num"].asInt() == it->first; }); [it](auto node) -> bool { return node["name"].asString() == it->first; });
if (ws == workspaces_.end()) { if (ws == workspaces_.end() ||
(!config_["all-outputs"].asBool() && (*ws)["output"].asString() != bar_.output_name)) {
it = buttons_.erase(it); it = buttons_.erase(it);
needReorder = true; needReorder = true;
} else { } else {
@ -54,7 +54,7 @@ auto waybar::modules::sway::Workspaces::update() -> void
&& bar_.output_name != node["output"].asString()) { && bar_.output_name != node["output"].asString()) {
continue; continue;
} }
auto it = buttons_.find(node["num"].asInt()); auto it = buttons_.find(node["name"].asString());
if (it == buttons_.end()) { if (it == buttons_.end()) {
addWorkspace(node); addWorkspace(node);
needReorder = true; needReorder = true;
@ -76,13 +76,13 @@ auto waybar::modules::sway::Workspaces::update() -> void
button.get_style_context()->remove_class("urgent"); button.get_style_context()->remove_class("urgent");
} }
if (needReorder) { if (needReorder) {
box_.reorder_child(button, node["num"].asInt()); box_.reorder_child(button, getWorkspaceIndex(node["name"].asString()));
} }
auto icon = getIcon(node["name"].asString(), node); auto icon = getIcon(node["name"].asString(), node);
if (config_["format"].isString()) { if (config_["format"].isString()) {
auto format = config_["format"].asString(); auto format = config_["format"].asString();
button.set_label(fmt::format(format, fmt::arg("icon", icon), button.set_label(fmt::format(format, fmt::arg("icon", icon),
fmt::arg("name", node["name"].asString()), fmt::arg("name", trimWorkspaceName(node["name"].asString())),
fmt::arg("index", node["num"].asString()))); fmt::arg("index", node["num"].asString())));
} else { } else {
button.set_label(icon); button.set_label(icon);
@ -95,15 +95,15 @@ auto waybar::modules::sway::Workspaces::update() -> void
} }
} }
void waybar::modules::sway::Workspaces::addWorkspace(Json::Value node) void waybar::modules::sway::Workspaces::addWorkspace(const Json::Value &node)
{ {
auto icon = getIcon(node["name"].asString(), node); auto icon = getIcon(node["name"].asString(), node);
auto format = config_["format"].isString() auto format = config_["format"].isString()
? fmt::format(config_["format"].asString(), fmt::arg("icon", icon), ? fmt::format(config_["format"].asString(), fmt::arg("icon", icon),
fmt::arg("name", node["name"].asString()), fmt::arg("name", trimWorkspaceName(node["name"].asString())),
fmt::arg("index", node["num"].asString())) fmt::arg("index", node["num"].asString()))
: icon; : icon;
auto pair = buttons_.emplace(node["num"].asInt(), format); 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);
@ -121,7 +121,7 @@ void waybar::modules::sway::Workspaces::addWorkspace(Json::Value node)
button.signal_scroll_event() button.signal_scroll_event()
.connect(sigc::mem_fun(*this, &Workspaces::handleScroll)); .connect(sigc::mem_fun(*this, &Workspaces::handleScroll));
} }
box_.reorder_child(button, node["num"].asInt()); box_.reorder_child(button, getWorkspaceIndex(node["name"].asString()));
if (node["focused"].asBool()) { if (node["focused"].asBool()) {
button.get_style_context()->add_class("focused"); button.get_style_context()->add_class("focused");
} }
@ -134,11 +134,10 @@ void waybar::modules::sway::Workspaces::addWorkspace(Json::Value node)
button.show(); button.show();
} }
std::string waybar::modules::sway::Workspaces::getIcon(std::string name, std::string waybar::modules::sway::Workspaces::getIcon(const std::string &name,
Json::Value node) const Json::Value &node)
{ {
std::vector<std::string> keys = { std::vector<std::string> keys = { name, "urgent", "focused", "visible", "default" };
name, "urgent", "focused", "visible", "default"};
for (auto const& key : keys) { 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()) {
@ -158,73 +157,98 @@ bool waybar::modules::sway::Workspaces::handleScroll(GdkEventScroll *e)
return false; return false;
} }
scrolling_ = true; scrolling_ = true;
int id = -1; std::string name;
uint16_t idx = 0; uint16_t idx = 0;
{ {
std::lock_guard<std::mutex> lock(mutex_); std::lock_guard<std::mutex> lock(mutex_);
for (; idx < workspaces_.size(); idx += 1) { for (; idx < workspaces_.size(); idx += 1) {
if (workspaces_[idx]["focused"].asBool()) { if (workspaces_[idx]["focused"].asBool()) {
id = workspaces_[idx]["num"].asInt(); name = workspaces_[idx]["name"].asString();
break; break;
} }
} }
} }
if (id == -1) { if (name.empty()) {
scrolling_ = false; scrolling_ = false;
return false; return false;
} }
if (e->direction == GDK_SCROLL_UP) { if (e->direction == GDK_SCROLL_UP) {
id = getNextWorkspace(); name = getNextWorkspace();
} }
if (e->direction == GDK_SCROLL_DOWN) { if (e->direction == GDK_SCROLL_DOWN) {
id = getPrevWorkspace(); name = getPrevWorkspace();
} }
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) {
id = getNextWorkspace(); name = getNextWorkspace();
} else if (delta_y > 0) { } else if (delta_y > 0) {
id = getPrevWorkspace(); name = getPrevWorkspace();
} }
} }
{ if (!name.empty()) {
std::lock_guard<std::mutex> lock(mutex_); std::lock_guard<std::mutex> lock(mutex_);
if (id == workspaces_[idx]["num"].asInt()) { if (name == workspaces_[idx]["name"].asString()) {
scrolling_ = false; scrolling_ = false;
return false; return false;
} }
ipc_.sendCmd(IPC_COMMAND, fmt::format("workspace \"{}\"", id)); ipc_.sendCmd(IPC_COMMAND, fmt::format("workspace \"{}\"", name));
std::this_thread::sleep_for(std::chrono::milliseconds(150)); std::this_thread::sleep_for(std::chrono::milliseconds(150));
} }
return true; return true;
} }
int waybar::modules::sway::Workspaces::getPrevWorkspace() std::string waybar::modules::sway::Workspaces::getPrevWorkspace()
{ {
for (uint16_t i = 0; i != workspaces_.size(); i += 1) { for (uint16_t i = 0; i < workspaces_.size(); i += 1) {
if (workspaces_[i]["focused"].asBool()) { if (workspaces_[i]["focused"].asBool()) {
if (i > 0) { if (i > 0) {
return workspaces_[i - 1]["num"].asInt(); return workspaces_[i - 1]["name"].asString();
} }
return workspaces_[workspaces_.size() - 1]["num"].asInt(); return workspaces_[workspaces_.size() - 1]["name"].asString();
} }
} }
return -1; return "";
} }
int waybar::modules::sway::Workspaces::getNextWorkspace() std::string waybar::modules::sway::Workspaces::getNextWorkspace()
{ {
for (uint16_t i = 0; i != workspaces_.size(); i += 1) { for (uint16_t i = 0; i < workspaces_.size(); i += 1) {
if (workspaces_[i]["focused"].asBool()) { if (workspaces_[i]["focused"].asBool()) {
if (i + 1U < workspaces_.size()) { if (i + 1U < workspaces_.size()) {
return workspaces_[i + 1]["num"].asInt(); return workspaces_[i + 1]["name"].asString();
} }
return workspaces_[0]["num"].asInt(); return workspaces_[0]["String"].asString();
} }
} }
return -1; return "";
}
uint16_t waybar::modules::sway::Workspaces::getWorkspaceIndex(const std::string &name)
{
uint16_t idx = 0;
for (const auto &workspace : workspaces_) {
if (workspace["name"].asString() == name) {
return idx;
}
if (!config_["all-outputs"].asBool() && workspace["output"].asString() != bar_.output_name) {
// Nothing here
} else {
idx += 1;
}
}
return workspaces_.size();
}
std::string waybar::modules::sway::Workspaces::trimWorkspaceName(std::string name)
{
std::size_t found = name.find(":");
if (found!=std::string::npos) {
return name.substr(found+1);
}
return name;
} }
waybar::modules::sway::Workspaces::operator Gtk::Widget &() { waybar::modules::sway::Workspaces::operator Gtk::Widget &() {

10
subprojects/fmt.wrap Normal file
View File

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

View File

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