Compare commits

..

221 Commits
0.0.3 ... 0.2.3

Author SHA1 Message Date
3691d84543 chore: v0.2.3 2018-12-18 17:42:35 +01:00
b554094c7e feat: args && class id 2018-12-18 17:30:54 +01:00
66ad2864c2 fix(Pulseaudio): use markup 2018-12-17 09:00:40 +01:00
c7b0639f32 fix(workspaces): check thread is running 2018-12-09 10:49:28 +01:00
0acc50264e fix(custom): failed getline after closed pipe 2018-12-08 13:57:56 +01:00
1b13f9e38c fix(custom): close endless scripts 2018-12-08 12:58:47 +01:00
7befd27059 fix: clock interval 2018-12-04 09:38:08 +01:00
2ec34e4adb feat: add $HOME to valid path 2018-12-03 20:12:34 +01:00
812a7a9861 fix: typo 2018-12-03 11:13:56 +01:00
1d96d57b75 feat: warn user about stopped endless custom module 2018-12-03 09:35:10 +01:00
691fb88057 refactor(modules): const bar 2018-12-01 00:10:41 +01:00
281bab4943 feat(ci): opensuse 2018-11-30 21:48:42 +01:00
8e93fd3ae0 revert: Update to new Sway IPC protocol 2018-11-29 09:50:00 +01:00
37c7e586d6 Merge pull request #110 from tokyovigilante/ipc-fix
Update to new Sway IPC protocol
2018-11-29 00:40:57 +01:00
3a5a470d90 Update to new Sway IPC protocol 2018-11-28 22:27:06 +00:00
c051b517ca Merge pull request #108 from SibrenVasse/spotify
fix(custom): fix conditional statement
2018-11-26 20:09:21 +01:00
a0cdef569b fix(custom): fix conditional statement 2018-11-26 19:35:03 +01:00
a123711a8d fix(network): disconnected forced interface 2018-11-25 21:32:50 +01:00
391a7de94a fix(window): escape window name 2018-11-25 15:21:00 +01:00
952d877652 Merge pull request #103 from SibrenVasse/spotify
Event based mediaplayer script
2018-11-24 18:13:08 +01:00
5356d25a9b Merge branch 'master' into spotify 2018-11-24 18:11:48 +01:00
65b9911df4 Merge pull request #105 from David96/master
Allow custom plugins that don't execute anything
2018-11-24 18:01:31 +01:00
2aed121903 Allow custom plugins that don't execute anything 2018-11-24 17:24:02 +01:00
08bfdda4cb revert(network): wait interface 2018-11-24 16:01:22 +01:00
8f8ec3b999 feat(Label): on-click-right 2018-11-24 15:56:16 +01:00
a17220054d feat(client): pefix module name on update error 2018-11-24 11:20:03 +01:00
4cc48b3cfd fix(client): catch error on update 2018-11-24 11:13:52 +01:00
087de4e956 refactor(client): lambda to method 2018-11-24 11:04:56 +01:00
2c2a0473f4 feat(client): throw when we don't have required resources files 2018-11-23 19:31:40 +01:00
2e1f8b2fc5 fix(network): check len of netlinkResponse 2018-11-23 17:52:10 +01:00
686bc4828e refactor(network): only get info when there is an interface 2018-11-23 17:46:14 +01:00
f6c2a8d9b7 fix(network): free the message instead of the socket 2018-11-23 17:42:26 +01:00
baa7f52e21 refactor(network): wait for new address 2018-11-23 16:04:29 +01:00
c4c0c01a2f feat(custom): event based mediaplayer script 2018-11-23 12:08:15 +01:00
2b05b8e69a chore: v0.2.2 2018-11-23 12:03:23 +01:00
ad7400d5ce refactor(ALabel): add interval 2018-11-23 11:57:37 +01:00
36652158ad refactor(tray): more cpp binding 2018-11-23 10:46:58 +01:00
8db94f7efd revert(ALabel): ref on format 2018-11-22 16:50:42 +01:00
aaaa17d94f fix(config): remove duplicate tray 2018-11-22 16:21:46 +01:00
0b1b0eb1a7 feat(tray): multiple hosts 2018-11-22 16:20:49 +01:00
f00be0b552 refactor(tray): remove useless variable 2018-11-22 15:51:55 +01:00
ba79b4d397 refactor(tray): cleanup and fixes 2018-11-22 15:47:23 +01:00
bd62c4cbc5 feat(Label): use set_markup 2018-11-21 20:49:09 +01:00
b3559328f1 fix(window): ellipsize 2018-11-20 23:24:33 +01:00
69fceb1c92 fix(tray): sigsev on click when multiple icons is available 2018-11-16 12:01:03 +01:00
33f138c16e chore: v0.2.1 2018-11-16 10:15:27 +01:00
01692b719a chore: update README 2018-11-16 10:07:20 +01:00
8c26a6aab7 chore: update README 2018-11-16 10:06:24 +01:00
e42fae32ab feat(network): network info interval 2018-11-16 10:02:12 +01:00
c910767378 refactor: remove usless using 2018-11-15 14:48:49 +01:00
94b9f0a399 feat(cpu): add both usage and load 2018-11-15 14:44:43 +01:00
1665003d23 fix: sigsev 2018-11-14 19:14:51 +01:00
e5573c20e6 Merge pull request #96 from Robinhuett/module_network_ipaddr
Module network ipaddr
2018-11-14 10:44:27 +01:00
75cc1bc318 refactor(network): codestyle and error handling 2018-11-14 10:31:17 +01:00
50e782e028 chore: update example config 2018-11-13 21:40:47 +01:00
5c66b1a770 feat(network): display ip address and subnetmask 2018-11-13 21:31:26 +01:00
3dc0f7ccf9 Merge pull request #94 from Robinhuett/cpu_fix
Use /proc/stat for cpu load
2018-11-11 13:41:11 +01:00
e1d98f0ad9 fix(cpu): show correct load
feat(cpu): show cores in tooltip
2018-11-11 03:11:32 +01:00
7222668326 refactor: disable battery timer stop on inotify event for now 2018-11-09 23:02:46 +01:00
315e2defde Merge pull request #93 from Robinhuett/pulseaudio_scroll
Add config option for volume change scroll step size
2018-11-09 22:55:51 +01:00
45bb8b1a1f refactor: simpler memory code 2018-11-09 22:55:25 +01:00
e21df5ae36 Add config option for volume change scroll step size 2018-11-09 22:48:27 +01:00
a5bca24f9b Merge pull request #91 from Robinhuett/ram_free_used
Use /proc/meminfo for Memory module
2018-11-09 22:34:11 +01:00
c07037d6b8 Update README.md 2018-11-09 17:27:35 +01:00
a9751545fa fix: update travis 2018-11-09 17:15:19 +01:00
9ea0815dea Use ifstream to reaad /proc/meminfo 2018-11-09 16:24:13 +01:00
b8b799a187 [ci skip] remove .SRCINFO at root 2018-11-09 15:36:21 +01:00
39dfa66261 feat(ci): archlinux 2018-11-09 15:19:17 +01:00
13702012a4 CI (#90) 2018-11-09 12:07:16 +01:00
6b62079d8a rewind stream instead of opening a new one 2018-11-09 00:17:30 +01:00
ac0963c608 Use /proc/meminfo for Memory module 2018-11-08 21:09:56 +01:00
2d2fb88040 fix: fmt 2018-11-08 09:57:24 +01:00
0933aad75f Merge pull request #87 from David96/master
Fix workspaces not being removed from bar when moved to another output
2018-11-05 21:06:53 +01:00
adcd956c24 Fix workspaces not being removed from bar when moved to another output 2018-11-05 20:59:28 +01:00
6c9e37699b Merge pull request #86 from David96/master
Fix clicking and scrolling through workspaces
2018-11-05 20:35:48 +01:00
168415440f Fix clicking and scrolling through workspaces
The way waybar used the workspace "num", clicking a workspace called "1:
something" resulted in going to a newly created workspace called "1",
because the workspace ipc command expects the workspace name, not its number.
2018-11-05 20:16:19 +01:00
d6af63d84a chore: add travis 2018-11-05 11:59:05 +01:00
26182c222b Merge pull request #79 from vberger/master
Don't call layer_surface.set_size on configure
2018-11-03 13:36:04 +01:00
43cd80fb31 chore: 0.2.0 2018-11-03 13:20:05 +01:00
2f6abfda59 Don't call layer_surface.set_size on configure 2018-11-03 13:16:13 +01:00
5ece0d98ee Merge pull request #78 from mithodin/filesystem-experimental
add option for when filesystem still lives in the experimental namespace
2018-11-03 13:00:04 +01:00
0637888460 even simpler check 2018-11-03 12:44:15 +01:00
ebbdaa168c automatically detect where filesystem lives 2018-11-02 23:15:42 +01:00
6ab01b1ad4 fix(style): not charging 2018-11-02 23:00:38 +01:00
cf921a5e14 Merge pull request #76 from mithodin/charging-full
Add class for full battery and give option to interpret unknown as full
2018-11-02 22:51:57 +01:00
25f31b19f6 formatting is hard. 2018-11-02 22:50:01 +01:00
d8b6201632 ...and fix the function signature in the header 2018-11-02 22:15:54 +01:00
123ce083b4 fix typo and initialize old_status_ 2018-11-02 22:08:55 +01:00
0522577fe5 make status and state fully configurable formats 2018-11-02 22:04:43 +01:00
1ff9fd06af Merge pull request #77 from mithodin/old-gdbus-codegen
fix compilation on systems with old gdbus-codegen
2018-11-02 21:23:10 +01:00
b6cad05489 fix formatting 2018-11-02 21:13:57 +01:00
236be90c2f add option for when filesystem still lives in the experimental namespace 2018-11-02 20:59:41 +01:00
f137090d55 fix compilation on systems with old gdbus-codegen 2018-11-02 20:13:09 +01:00
9c57df505c Add class for full battery and give option to interpret unknown as full 2018-11-02 19:41:00 +01:00
00e7e87f55 fix: style 2018-11-02 17:39:00 +01:00
836c543c62 fix: style 2018-11-02 17:07:51 +01:00
7bca5fd6bd feat(Bar): add a warning about minimum height 2018-11-02 12:35:26 +01:00
61e9f0803d Merge pull request #75 from ForTheReallys/proper_height
Fix #54
2018-11-02 12:26:14 +01:00
9b201c77d7 feat: battery states && format-full/charging 2018-11-02 11:23:29 +01:00
4b68840212 Fix #54 2018-11-01 16:00:38 -05:00
9d4048983d refactor: remove useless tmp variable 2018-11-01 09:27:00 +01:00
0670225e69 Merge pull request #72 from Robinhuett/custom_module_states
Custom modules can control tooltip and CSS class
2018-11-01 09:11:15 +01:00
e23fbd0add Added return-type json to custom module 2018-11-01 00:40:44 +01:00
341d3300fa Custom modules can control tooltip and CSS class 2018-10-30 21:28:31 +01:00
c3e185546d Merge pull request #68 from harishkrupo/master
Add configuration options for widgets on mouse events
2018-10-30 16:32:37 +01:00
0e93de9c0a Merge pull request #71 from Robinhuett/configurable_battery_levels
Added second warning stage to battery module
2018-10-30 16:31:01 +01:00
3e34137ac7 pulseaudio: Change volume on scroll event
Subscribe for mouse scroll events on the pulseaudio widget
and change volume when event is received.
Scroll up increments the volume and scroll down decrements it.
These events are only subscibed when there are no user defined
commands present for them.

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

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

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

feat(wip): tray

feat(WIP): gdbus

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

feat(wip): tray

feat(WIP): gdbus

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

feat(wip): tray

feat(WIP): gdbus

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

2
.gitignore vendored
View File

@ -4,7 +4,7 @@ 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 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

View File

@ -1,11 +1,12 @@
# 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)<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)
- Tray (Beta) [#21](https://github.com/Alexays/Waybar/issues/21)
- Local time - Local time
- Battery - Battery
- Network - Network
@ -13,6 +14,7 @@
- Memory - Memory
- Cpu load average - Cpu load average
- Custom scripts - Custom scripts
- And much more customizations
**Configuration and Customization** **Configuration and Customization**
@ -22,12 +24,14 @@
```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
``` ```
Contributions welcome! - have fun :) Contributions welcome! - have fun :)<br>
The style guidelines is [Google's](https://google.github.io/styleguide/cppguide.html)
## License ## License

31
include/ALabel.hpp Normal file
View File

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

View File

@ -3,10 +3,13 @@
#include <gtkmm.h> #include <gtkmm.h>
namespace waybar { namespace waybar {
class IModule { class IModule {
public: public:
virtual ~IModule() {} virtual ~IModule() = default;
virtual auto update() -> void = 0; virtual auto update() -> void = 0;
virtual operator Gtk::Widget &() = 0; virtual operator Gtk::Widget &() = 0;
Glib::Dispatcher dp; // Hmmm Maybe I should create an abstract class ?
}; };
} }

View File

@ -4,47 +4,56 @@
#include <gtkmm.h> #include <gtkmm.h>
#include "wlr-layer-shell-unstable-v1-client-protocol.h" #include "wlr-layer-shell-unstable-v1-client-protocol.h"
#include "xdg-output-unstable-v1-client-protocol.h" #include "xdg-output-unstable-v1-client-protocol.h"
#include "IModule.hpp"
namespace waybar { namespace waybar {
struct Client; class Client;
class Factory;
struct Bar { class Bar {
Bar(Client& client, std::unique_ptr<struct wl_output *>&& output); public:
Bar(const Client&, std::unique_ptr<struct wl_output *>&&, uint32_t);
Bar(const Bar&) = delete; Bar(const Bar&) = delete;
Client& client;
auto toggle() -> void;
const Client& client;
Gtk::Window window; Gtk::Window window;
struct wl_surface *surface; struct wl_surface *surface;
struct zwlr_layer_surface_v1 *layerSurface; struct zwlr_layer_surface_v1 *layer_surface;
std::unique_ptr<struct wl_output *> output; std::unique_ptr<struct wl_output *> output;
std::string output_name;
uint32_t wl_name;
bool visible = true; bool visible = true;
std::string outputName;
auto setWidth(uint32_t) -> void;
auto toggle() -> void;
private: private:
static void _handleLogicalPosition(void *data, static void handleLogicalPosition(void *, struct zxdg_output_v1 *, int32_t,
struct zxdg_output_v1 *zxdg_output_v1, int32_t x, int32_t y); int32_t);
static void _handleLogicalSize(void *data, static void handleLogicalSize(void *, struct zxdg_output_v1 *, int32_t,
struct zxdg_output_v1 *zxdg_output_v1, int32_t width, int32_t height); int32_t);
static void _handleDone(void *data, struct zxdg_output_v1 *zxdg_output_v1); static void handleDone(void *, struct zxdg_output_v1 *);
static void _handleName(void *data, struct zxdg_output_v1 *xdg_output, static void handleName(void *, struct zxdg_output_v1 *, const char *);
const char *name); static void handleDescription(void *, struct zxdg_output_v1 *,
static void _handleDescription(void *data, const char *);
struct zxdg_output_v1 *zxdg_output_v1, const char *description); static void layerSurfaceHandleConfigure(void *,
static void _layerSurfaceHandleConfigure(void *data, struct zwlr_layer_surface_v1 *, uint32_t, uint32_t, uint32_t);
struct zwlr_layer_surface_v1 *surface, uint32_t serial, uint32_t width, static void layerSurfaceHandleClosed(void *,
uint32_t height); struct zwlr_layer_surface_v1 *);
static void _layerSurfaceHandleClosed(void *data,
struct zwlr_layer_surface_v1 *surface); auto setupConfig() -> void;
auto _setupConfig() -> void; auto setupWidgets() -> void;
auto _setupWidgets() -> void; auto setupCss() -> void;
auto _setupCss() -> void; void getModules(const Factory&, const std::string&);
uint32_t _width = 0;
uint32_t _height = 30; uint32_t width_ = 0;
Json::Value _config; uint32_t height_ = 30;
Glib::RefPtr<Gtk::StyleContext> _styleContext; Json::Value config_;
Glib::RefPtr<Gtk::CssProvider> _cssProvider; Glib::RefPtr<Gtk::StyleContext> style_context_;
struct zxdg_output_v1 *_xdgOutput; Glib::RefPtr<Gtk::CssProvider> css_provider_;
struct zxdg_output_v1 *xdg_output_;
std::vector<std::unique_ptr<waybar::IModule>> modules_left_;
std::vector<std::unique_ptr<waybar::IModule>> modules_center_;
std::vector<std::unique_ptr<waybar::IModule>> modules_right_;
}; };
} }

View File

@ -2,41 +2,39 @@
#include <unistd.h> #include <unistd.h>
#include <wordexp.h> #include <wordexp.h>
#include <fmt/format.h> #include <fmt/format.h>
#include <gdk/gdk.h> #include <gdk/gdk.h>
#include <wayland-client.h> #include <wayland-client.h>
#include <gdk/gdkwayland.h> #include <gdk/gdkwayland.h>
#include "bar.hpp" #include "bar.hpp"
namespace waybar { namespace waybar {
struct Client { class Client {
std::string cssFile; public:
std::string configFile; Client(int argc, char *argv[]);
int main(int argc, char *argv[]);
Gtk::Main gtk_main; Gtk::Main gtk_main;
std::string css_file;
std::string config_file;
Glib::RefPtr<Gdk::Display> gdk_display; Glib::RefPtr<Gdk::Display> gdk_display;
struct wl_display *wlDisplay = nullptr; struct wl_display *wl_display = nullptr;
struct wl_registry *registry = nullptr; struct wl_registry *registry = nullptr;
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 wl_output *wlOutput = nullptr;
std::vector<std::unique_ptr<Bar>> bars; std::vector<std::unique_ptr<Bar>> bars;
Client(int argc, char* argv[]);
void bind_interfaces();
auto setup_css();
int main(int argc, char* argv[]);
private: private:
static void _handle_global(void *data, struct wl_registry *registry, void setupConfigs(const std::string& config, const std::string& style);
void bindInterfaces();
const std::string getValidPath(std::vector<std::string> paths);
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);
static void _handle_global_remove(void *data, static void handleGlobalRemove(void *data,
struct wl_registry *registry, uint32_t name); struct wl_registry *registry, uint32_t name);
}; };
} }

View File

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

View File

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

View File

@ -1,31 +1,43 @@
#pragma once #pragma once
#include <json/json.h> #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/chrono.hpp"
#include "IModule.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 IModule { class Battery : public ALabel {
public: public:
Battery(Json::Value config); Battery(const std::string&, const Json::Value&);
~Battery();
auto update() -> void; auto update() -> void;
operator Gtk::Widget&();
private: private:
std::string _getIcon(uint16_t percentage); static inline const fs::path data_dir_ = "/sys/class/power_supply/";
static inline const fs::path _data_dir = "/sys/class/power_supply/";
std::vector<fs::path> _batteries; void worker();
util::SleeperThread _thread; const std::tuple<uint8_t, std::string> getInfos() const;
Gtk::Label _label; const std::string getState(uint8_t) const;
Json::Value _config;
util::SleeperThread thread_;
util::SleeperThread thread_timer_;
std::vector<fs::path> batteries_;
int fd_;
std::string old_status_;
}; };
} }

View File

@ -1,21 +1,18 @@
#pragma once #pragma once
#include <json/json.h>
#include <fmt/format.h> #include <fmt/format.h>
#include "fmt/time.h"
#include "util/chrono.hpp" #include "util/chrono.hpp"
#include "IModule.hpp" #include "ALabel.hpp"
namespace waybar::modules { namespace waybar::modules {
class Clock : public IModule { class Clock : public ALabel {
public: public:
Clock(Json::Value config); Clock(const std::string&, const Json::Value&);
auto update() -> void; auto update() -> void;
operator Gtk::Widget &();
private: private:
Gtk::Label _label; waybar::util::SleeperThread thread_;
waybar::util::SleeperThread _thread;
Json::Value _config;
}; };
} }

View File

@ -1,22 +1,28 @@
#pragma once #pragma once
#include <json/json.h>
#include <fmt/format.h> #include <fmt/format.h>
#include <sys/sysinfo.h> #include <sys/sysinfo.h>
#include <fstream>
#include <vector>
#include <numeric>
#include <iostream>
#include "util/chrono.hpp" #include "util/chrono.hpp"
#include "IModule.hpp" #include "ALabel.hpp"
namespace waybar::modules { namespace waybar::modules {
class Cpu : public IModule { class Cpu : public ALabel {
public: public:
Cpu(Json::Value config); Cpu(const std::string&, const Json::Value&);
auto update() -> void; auto update() -> void;
operator Gtk::Widget &();
private: private:
Gtk::Label _label; static inline const std::string data_dir_ = "/proc/stat";
waybar::util::SleeperThread _thread; uint16_t getCpuLoad();
Json::Value _config; 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_;
}; };
} }

View File

@ -1,22 +1,34 @@
#pragma once #pragma once
#include <json/json.h>
#include <fmt/format.h> #include <fmt/format.h>
#include <iostream>
#include "util/chrono.hpp" #include "util/chrono.hpp"
#include "IModule.hpp" #include "util/command.hpp"
#include "util/json.hpp"
#include "ALabel.hpp"
namespace waybar::modules { namespace waybar::modules {
class Custom : public IModule { class Custom : public ALabel {
public: public:
Custom(std::string name, Json::Value config); Custom(const std::string&, const Json::Value&);
~Custom();
auto update() -> void; auto update() -> void;
operator Gtk::Widget &();
private: private:
Gtk::Label _label; void delayWorker();
waybar::util::SleeperThread _thread; void continuousWorker();
const std::string _name; void parseOutputRaw();
Json::Value _config; void parseOutputJson();
const std::string name_;
std::string text_;
std::string tooltip_;
std::string class_;
std::string prevclass_;
waybar::util::SleeperThread thread_;
waybar::util::command::res output_;
waybar::util::JsonParser parser_;
FILE* fp_;
}; };
} }

View File

@ -1,22 +1,22 @@
#pragma once #pragma once
#include <json/json.h>
#include <fmt/format.h> #include <fmt/format.h>
#include <sys/sysinfo.h> #include <fstream>
#include "util/chrono.hpp" #include "util/chrono.hpp"
#include "IModule.hpp" #include "ALabel.hpp"
namespace waybar::modules { namespace waybar::modules {
class Memory : public IModule { class Memory : public ALabel {
public: public:
Memory(Json::Value config); Memory(const std::string&, const Json::Value&);
auto update() -> void; auto update() -> void;
operator Gtk::Widget &();
private: private:
Gtk::Label _label; static inline const std::string data_dir_ = "/proc/meminfo";
waybar::util::SleeperThread _thread; unsigned long memtotal_;
Json::Value _config; unsigned long memfree_;
void parseMeminfo();
waybar::util::SleeperThread thread_;
}; };
} }

View File

@ -1,35 +1,54 @@
#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 <json/json.h>
#include <fmt/format.h> #include <fmt/format.h>
#include "util/chrono.hpp" #include "util/chrono.hpp"
#include "IModule.hpp" #include "ALabel.hpp"
namespace waybar::modules { namespace waybar::modules {
class Network : public IModule { class Network : public ALabel {
public: public:
Network(Json::Value config); Network(const std::string&, const Json::Value&);
~Network();
auto update() -> void; auto update() -> void;
operator Gtk::Widget &();
private: private:
void _parseEssid(struct nlattr **bss); static int netlinkRequest(int, void*, uint32_t, uint32_t groups = 0);
void _parseSignal(struct nlattr **bss); static int netlinkResponse(int, void*, uint32_t, uint32_t groups = 0);
bool _associatedOrJoined(struct nlattr **bss); static int scanCb(struct nl_msg*, void*);
static int _scanCb(struct nl_msg *msg, void *data);
auto _getInfo() -> void; void worker();
Gtk::Label _label; void disconnected();
waybar::util::SleeperThread _thread; void initNL80211();
Json::Value _config; int getExternalInterface();
std::size_t _ifid; void getInterfaceAddress();
std::string _essid; void parseEssid(struct nlattr**);
int _signalStrengthdBm; void parseSignal(struct nlattr**);
int _signalStrength; bool associatedOrJoined(struct nlattr**);
auto getInfo() -> void;
waybar::util::SleeperThread thread_;
waybar::util::SleeperThread thread_timer_;
int ifid_;
sa_family_t family_;
int sock_fd_;
struct sockaddr_nl nladdr_ = {0};
struct nl_sock* sk_ = nullptr;
int nl80211_id_;
std::string essid_;
std::string ifname_;
std::string ipaddr_;
std::string netmask_;
int cidr_;
int signal_strength_dbm_;
uint16_t signal_strength_;
}; };
} }

View File

@ -1,36 +1,39 @@
#pragma once #pragma once
#include <pulse/pulseaudio.h>
#include <json/json.h>
#include <fmt/format.h> #include <fmt/format.h>
#include <pulse/pulseaudio.h>
#include <pulse/volume.h>
#include <algorithm> #include <algorithm>
#include "IModule.hpp" #include "ALabel.hpp"
namespace waybar::modules { namespace waybar::modules {
class Pulseaudio : public IModule { class Pulseaudio : public ALabel {
public: public:
Pulseaudio(Json::Value config); Pulseaudio(const std::string&, const Json::Value&);
~Pulseaudio();
auto update() -> void; auto update() -> void;
operator Gtk::Widget &();
private: private:
std::string _getIcon(uint16_t percentage); static void subscribeCb(pa_context*, pa_subscription_event_type_t,
static void _subscribeCb(pa_context *context, uint32_t, void*);
pa_subscription_event_type_t type, uint32_t idx, void *data); static void contextStateCb(pa_context*, void*);
static void _contextStateCb(pa_context *c, void *data); static void sinkInfoCb(pa_context*, const pa_sink_info*, int, void*);
static void _sinkInfoCb(pa_context *context, const pa_sink_info *i, static void serverInfoCb(pa_context*, const pa_server_info*, void*);
int eol, void *data); static void volumeModifyCb(pa_context*, int, void*);
static void _serverInfoCb(pa_context *context, const pa_server_info *i, bool handleScroll(GdkEventScroll* e);
void *data);
Gtk::Label _label; const std::string getPortIcon() const;
Json::Value _config;
pa_threaded_mainloop *_mainloop; pa_threaded_mainloop* mainloop_;
pa_mainloop_api *_mainloop_api; pa_mainloop_api* mainloop_api_;
pa_context *_context; pa_context* context_;
uint32_t _sinkIdx{0}; uint32_t sink_idx_{0};
int _volume; uint16_t volume_;
bool _muted; pa_cvolume pa_volume_;
std::string _desc; bool muted_;
std::string port_name_;
std::string desc_;
bool scrolling_;
}; };
} } // namespace waybar::modules

View File

@ -0,0 +1,41 @@
#pragma once
#include <gtkmm.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

@ -0,0 +1,59 @@
#pragma once
#include <dbus-status-notifier-item.h>
#include <gtkmm.h>
#include <json/json.h>
#include <libdbusmenu-gtk/dbusmenu-gtk.h>
#ifdef FILESYSTEM_EXPERIMENTAL
#include <experimental/filesystem>
#else
#include <filesystem>
#endif
namespace waybar::modules::SNI {
class Item {
public:
Item(std::string, std::string, const Json::Value&);
~Item() = default;
std::string bus_name;
std::string object_path;
int icon_size;
int effective_icon_size;
Gtk::Image image;
Gtk::EventBox event_box;
std::string category;
std::string id;
std::string status;
std::string title;
int32_t window_id;
std::string icon_name;
Glib::RefPtr<Gdk::Pixbuf> icon_pixmap;
std::string overlay_icon_name;
std::string attention_icon_name;
std::string attention_movie_name;
std::string icon_theme_path;
std::string menu;
DbusmenuGtkMenu *dbus_menu = nullptr;
Gtk::Menu *gtk_menu = nullptr;
bool item_is_menu;
private:
static void proxyReady(GObject *obj, GAsyncResult *res, gpointer data);
static void getAll(GObject *obj, GAsyncResult *res, gpointer data);
void updateImage();
Glib::RefPtr<Gdk::Pixbuf> extractPixBuf(GVariant *variant);
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*/);
GCancellable *cancellable_ = nullptr;
SnItem *proxy_ = nullptr;
};
} // namespace waybar::modules::SNI

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
#pragma once #pragma once
#define event_mask(ev) (1 << (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

View File

@ -0,0 +1,27 @@
#pragma once
#include <fmt/format.h>
#include "bar.hpp"
#include "client.hpp"
#include "util/chrono.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&);
auto update() -> void;
private:
void worker();
const Bar& bar_;
waybar::util::SleeperThread thread_;
util::JsonParser parser_;
Ipc ipc_;
std::string mode_;
};
}

View File

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

View File

@ -0,0 +1,38 @@
#pragma once
#include <fmt/format.h>
#include "bar.hpp"
#include "client.hpp"
#include "util/chrono.hpp"
#include "util/json.hpp"
#include "IModule.hpp"
#include "modules/sway/ipc/client.hpp"
namespace waybar::modules::sway {
class Workspaces : public IModule {
public:
Workspaces(const std::string&, const waybar::Bar&, const Json::Value&);
auto update() -> void;
operator Gtk::Widget &();
private:
void worker();
void addWorkspace(Json::Value);
std::string getIcon(std::string, Json::Value);
bool handleScroll(GdkEventScroll*);
std::string getPrevWorkspace();
std::string getNextWorkspace();
const Bar& bar_;
const Json::Value& config_;
waybar::util::SleeperThread thread_;
Gtk::Box box_;
util::JsonParser parser_;
std::mutex mutex_;
bool scrolling_;
std::unordered_map<std::string, Gtk::Button> buttons_;
Json::Value workspaces_;
Ipc ipc_;
};
}

View File

@ -1,37 +0,0 @@
#pragma once
#include <fmt/format.h>
#include "bar.hpp"
#include "client.hpp"
#include "util/chrono.hpp"
#include "util/json.hpp"
#include "IModule.hpp"
namespace waybar::modules {
class Workspaces : public IModule {
public:
Workspaces(waybar::Bar &bar, Json::Value config);
auto update() -> void;
operator Gtk::Widget &();
private:
void _addWorkspace(Json::Value node);
std::string _getIcon(std::string name);
Json::Value _getWorkspaces(const std::string data);
bool _handleScroll(GdkEventScroll *e);
int _getPrevWorkspace();
int _getNextWorkspace();
Bar &_bar;
Json::Value _config;
waybar::util::SleeperThread _thread;
Gtk::Box _box;
util::JsonParser _parser;
std::mutex _mutex;
bool _scrolling;
std::unordered_map<int, Gtk::Button> _buttons;
Json::Value _workspaces;
int _ipcfd;
int _ipcEventfd;
};
}

View File

@ -5,6 +5,7 @@
#include <functional> #include <functional>
#include <condition_variable> #include <condition_variable>
#include <thread> #include <thread>
#include <gtkmm.h>
namespace waybar::chrono { namespace waybar::chrono {
@ -14,18 +15,6 @@ namespace waybar::chrono {
using duration = clock::duration; using duration = clock::duration;
using time_point = std::chrono::time_point<clock, duration>; using time_point = std::chrono::time_point<clock, duration>;
inline struct timespec to_timespec(time_point t) noexcept
{
long secs = duration_cast<seconds>(t.time_since_epoch()).count();
long nsc = duration_cast<nanoseconds>(t.time_since_epoch() % seconds(1)).count();
return {secs, nsc};
}
inline time_point to_time_point(struct timespec t) noexcept
{
return time_point(duration_cast<duration>(seconds(t.tv_sec) + nanoseconds(t.tv_nsec)));
}
} }
namespace waybar::util { namespace waybar::util {
@ -34,59 +23,61 @@ namespace waybar::util {
SleeperThread() = default; SleeperThread() = default;
SleeperThread(std::function<void()> func) SleeperThread(std::function<void()> func)
: thread{[this, func] { : do_run_(true), thread_{[this, func] {
do { while (do_run_) func();
func();
} while (do_run);
}} }}
{ {}
defined = true;
}
SleeperThread& operator=(std::function<void()> func) SleeperThread& operator=(std::function<void()> func)
{ {
thread = std::thread([this, func] { do_run_ = true;
do { thread_ = std::thread([this, func] {
func(); while (do_run_) func();
} while (do_run);
}); });
defined = true;
return *this; return *this;
} }
bool isRunnging() const
{
return do_run_;
}
auto sleep_for(chrono::duration dur) auto sleep_for(chrono::duration dur)
{ {
auto lock = std::unique_lock(mutex); auto lock = std::unique_lock(mutex_);
return condvar.wait_for(lock, dur); return condvar_.wait_for(lock, dur);
} }
auto sleep_until(chrono::time_point time) auto sleep_until(chrono::time_point time)
{ {
auto lock = std::unique_lock(mutex); auto lock = std::unique_lock(mutex_);
return condvar.wait_until(lock, time); return condvar_.wait_until(lock, time);
} }
auto wake_up() auto wake_up()
{ {
condvar.notify_all(); condvar_.notify_all();
}
auto stop()
{
do_run_ = false;
condvar_.notify_all();
} }
~SleeperThread() ~SleeperThread()
{ {
do_run = false; stop();
if (defined) { if (thread_.joinable()) {
condvar.notify_all(); thread_.detach();
thread.join();
} }
} }
private: private:
std::thread thread; bool do_run_ = false;
std::condition_variable condvar; std::thread thread_;
std::mutex mutex; std::condition_variable condvar_;
bool defined = false; std::mutex mutex_;
bool do_run = true;
}; };
} }

1264
include/util/clara.hpp Normal file

File diff suppressed because it is too large Load Diff

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

@ -0,0 +1,51 @@
#pragma once
#include <sys/wait.h>
namespace waybar::util::command {
struct res {
int exit_code;
std::string out;
};
inline struct res exec(const std::string cmd)
{
FILE* fp(popen(cmd.c_str(), "r"));
if (!fp) {
return { -1, "" };
}
std::array<char, 128> buffer = {0};
std::string output;
while (feof(fp) == 0) {
if (fgets(buffer.data(), 128, fp) != nullptr) {
output += buffer.data();
}
}
// Remove last newline
if (!output.empty() && output[output.length()-1] == '\n') {
output.erase(output.length()-1);
}
int exit_code = WEXITSTATUS(pclose(fp));
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

@ -7,28 +7,25 @@ namespace waybar::util {
struct JsonParser { struct JsonParser {
JsonParser() JsonParser()
: _reader(_builder.newCharReader()) : reader_(builder_.newCharReader())
{} {}
Json::Value parse(const std::string data) const Json::Value parse(const std::string data) const
{ {
Json::Value root; Json::Value root;
std::string err; std::string err;
bool res = bool res =
_reader->parse(data.c_str(), data.c_str() + data.size(), &root, &err); reader_->parse(data.c_str(), data.c_str() + data.size(), &root, &err);
if (!res) if (!res)
throw std::runtime_error(err); throw std::runtime_error(err);
return root; return root;
} }
~JsonParser() ~JsonParser() = default;
{
delete _reader;
}
private: private:
Json::CharReaderBuilder _builder; Json::CharReaderBuilder builder_;
Json::CharReader *_reader; std::unique_ptr<Json::CharReader> const reader_;
}; };
} }

View File

@ -1,11 +1,15 @@
project( project(
'waybar', 'cpp', 'c', 'waybar', 'cpp', 'c',
version: '0.0.3', version: '0.2.3',
license: 'MIT', license: 'MIT',
default_options : ['cpp_std=c++17'], default_options : [
'cpp_std=c++17',
'buildtype=release',
'default_library=static'
],
) )
cpp_args = [] cpp_args = ['-DVERSION="@0@"'.format(meson.project_version())]
cpp_link_args = [] cpp_link_args = []
if false # libc++ if false # libc++
@ -14,32 +18,82 @@ 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')
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')
wlroots = dependency('wlroots', fallback: ['wlroots', 'wlroots']) wlroots = dependency('wlroots', fallback: ['wlroots', 'wlroots'])
gtkmm = dependency('gtkmm-3.0') gtkmm = dependency('gtkmm-3.0')
dbusmenu_gtk = dependency('dbusmenu-gtk3-0.4', required: get_option('dbusmenu-gtk'))
giounix = dependency('gio-unix-2.0', required: get_option('dbusmenu-gtk'))
jsoncpp = dependency('jsoncpp') jsoncpp = dependency('jsoncpp')
sigcpp = dependency('sigc++-2.0') sigcpp = dependency('sigc++-2.0')
libnl = dependency('libnl-3.0') libnl = dependency('libnl-3.0', required: get_option('libnl'))
libnlgen = dependency('libnl-genl-3.0') libnlgen = dependency('libnl-genl-3.0', required: get_option('libnl'))
libpulse = dependency('libpulse') libpulse = dependency('libpulse', required: get_option('pulseaudio'))
src_files = files(
'src/factory.cpp',
'src/ALabel.cpp',
'src/modules/memory.cpp',
'src/modules/battery.cpp',
'src/modules/clock.cpp',
'src/modules/custom.cpp',
'src/modules/cpu.cpp',
'src/main.cpp',
'src/bar.cpp',
'src/client.cpp'
)
if find_program('sway', required : false).found()
add_project_arguments('-DHAVE_SWAY', language: 'cpp')
src_files += [
'src/modules/sway/ipc/client.cpp',
'src/modules/sway/mode.cpp',
'src/modules/sway/window.cpp',
'src/modules/sway/workspaces.cpp'
]
endif
if libnl.found() and libnlgen.found()
add_project_arguments('-DHAVE_LIBNL', language: 'cpp')
src_files += 'src/modules/network.cpp'
endif
if libpulse.found()
add_project_arguments('-DHAVE_LIBPULSE', language: 'cpp')
src_files += 'src/modules/pulseaudio.cpp'
endif
if dbusmenu_gtk.found()
add_project_arguments('-DHAVE_DBUSMENU', language: 'cpp')
src_files += files(
'src/modules/sni/tray.cpp',
'src/modules/sni/watcher.cpp',
'src/modules/sni/host.cpp',
'src/modules/sni/item.cpp'
)
endif
subdir('protocol') subdir('protocol')
executable( executable(
'waybar', 'waybar',
run_command('find', './src', '-name', '*.cpp').stdout().strip().split('\n'), src_files,
dependencies: [ dependencies: [
thread_dep, thread_dep,
wlroots, wlroots,
@ -51,9 +105,11 @@ executable(
libinput, libinput,
wayland_cursor, wayland_cursor,
gtkmm, gtkmm,
dbusmenu_gtk,
giounix,
libnl, libnl,
libnlgen, libnlgen,
libpulse, libpulse
], ],
include_directories: [include_directories('include')], include_directories: [include_directories('include')],
install: true, install: true,
@ -62,5 +118,17 @@ executable(
install_data( install_data(
'./resources/config', './resources/config',
'./resources/style.css', './resources/style.css',
install_dir: '/etc/xdg/waybar', install_dir: join_paths(get_option('out'), 'etc/xdg/waybar')
) )
clangtidy = find_program('clang-tidy', required: false)
if clangtidy.found()
run_target(
'tidy',
command: [
clangtidy,
'-checks=*,-fuchsia-default-arguments',
'-p', meson.build_root()
] + src_files)
endif

4
meson_options.txt Normal file
View File

@ -0,0 +1,4 @@
option('libnl', type: 'feature', value: 'auto', description: 'Enable libnl support for network related features')
option('pulseaudio', type: 'feature', value: 'auto', description: 'Enable support for pulseaudio')
option('dbusmenu-gtk', type: 'feature', value: 'auto', description: 'Enable support for tray')
option('out', type: 'string', value : '/', description: 'output prefix directory')

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 22 KiB

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

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

View File

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

View File

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

View File

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

View File

@ -1,42 +1,83 @@
{ {
// "layer": "top", // Waybar at top layer "layer": "top", // Waybar at top layer
// "position": "bottom", // Waybar at the bottom of your screen // "position": "bottom", // Waybar at the bottom of your screen
// "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": ["workspaces", "custom/spotify"], "modules-left": ["sway/workspaces", "sway/mode", "custom/spotify"],
"modules-right": ["pulseaudio", "network", "cpu", "memory", "battery", "clock"], "modules-center": ["sway/window"],
"modules-right": ["pulseaudio", "network", "cpu", "memory", "battery", "battery#bat2", "clock", "tray"],
// Modules configuration // Modules configuration
"workspaces": { // "sway/workspaces": {
"format-icons": { // "disable-scroll": true,
"1": "", // "all-outputs": true,
"2": "", // "format": "{name}: {icon}",
"3": "", // "format-icons": {
"4": "", // "1": "",
"5": "" // "2": "",
} // "3": "",
// "4": "",
// "5": "",
// "urgent": "",
// "focused": "",
// "default": ""
// }
// },
"sway/mode": {
"format": "<span style=\"italic\">{}</span>"
},
"tray": {
// "icon-size": 21,
"spacing": 10
},
"clock": {
"format-alt": "{:%Y-%m-%d}"
}, },
"cpu": { "cpu": {
"format": "{}% " "format": "{usage}% "
}, },
"memory": { "memory": {
"format": "{}% " "format": "{}% "
}, },
"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": {
"bat": "BAT2"
},
"network": { "network": {
"interface": "wlp2s0", // "interface": "wlp2s0", // (Optional) To force the use of this interface
"format": "{essid} ({signalStrength}%) " "format-wifi": "{essid} ({signalStrength}%) ",
"format-ethernet": "{ifname}: {ipaddr}/{cidr} ",
"format-disconnected": "Disconnected ⚠"
}, },
"pulseaudio": { "pulseaudio": {
//"scroll-step": 1,
"format": "{volume}% {icon}", "format": "{volume}% {icon}",
"format-bluetooth": "{volume}% {icon}",
"format-muted": "", "format-muted": "",
"format-icons": ["", ""] "format-icons": {
"headphones": "",
"handsfree": "",
"headset": "",
"phone": "",
"portable": "",
"car": "",
"default": ["", ""]
},
"on-click": "pavucontrol"
}, },
"custom/spotify": { "custom/spotify": {
"format": " {}", "format": " {}",
"exec": "$HOME/.bin/mediaplayer.sh" "max-length": 40,
"exec": "$HOME/.config/waybar/mediaplayer.py 2> /dev/null" // Script in resources folder
} }
} }

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

@ -3,31 +3,33 @@
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 { window#waybar {
background: rgba(43, 48, 59, 0.5); background: rgba(43, 48, 59, 0.5);
border-bottom: 3px solid rgba(100, 114, 125, 0.5); border-bottom: 3px solid rgba(100, 114, 125, 0.5);
color: white; color: white;
} }
#workspaces button { #workspaces button {
padding: 0 8px; padding: 0 5px;
background: transparent; background: transparent;
color: white; color: white;
border-bottom: 3px solid transparent; border-bottom: 3px solid transparent;
} }
#workspaces button label { #workspaces button.focused {
font-size: 12px;
}
#workspaces button.current {
background: #64727D; background: #64727D;
border-bottom: 3px solid white; border-bottom: 3px solid white;
} }
#clock, #battery, #cpu, #memory, #network, #pulseaudio, #custom-spotify { #mode {
background: #64727D;
border-bottom: 3px solid white;
}
#clock, #battery, #cpu, #memory, #network, #pulseaudio, #custom-spotify, #tray, #mode {
padding: 0 10px; padding: 0 10px;
margin: 0 5px; margin: 0 5px;
} }
@ -53,7 +55,7 @@ window {
} }
} }
#battery.warning { #battery.warning:not(.charging) {
background: #f53c3c; background: #f53c3c;
color: white; color: white;
animation-name: blink; animation-name: blink;
@ -76,6 +78,10 @@ window {
background: #2980b9; background: #2980b9;
} }
#network.disconnected {
background: #f53c3c;
}
#pulseaudio { #pulseaudio {
background: #f1c40f; background: #f1c40f;
color: black; color: black;
@ -90,3 +96,7 @@ window {
background: #66cc99; background: #66cc99;
color: #2a5c45; color: #2a5c45;
} }
#tray {
background-color: #2980b9;
}

110
src/ALabel.cpp Normal file
View File

@ -0,0 +1,110 @@
#include "ALabel.hpp"
#include <util/command.hpp>
#include <iostream>
waybar::ALabel::ALabel(const Json::Value& config, const std::string format, uint16_t interval)
: config_(config),
format_(config_["format"].isString() ? config_["format"].asString() : format),
interval_(std::chrono::seconds(config_["interval"].isUInt()
? config_["interval"].asUInt() : interval)), default_format_(format_)
{
event_box_.add(label_);
if (config_["max-length"].isUInt()) {
label_.set_max_width_chars(config_["max-length"].asUInt());
label_.set_ellipsize(Pango::EllipsizeMode::ELLIPSIZE_END);
}
if (config_["format-alt"].isString()) {
event_box_.add_events(Gdk::BUTTON_PRESS_MASK);
event_box_.signal_button_press_event().connect(
sigc::mem_fun(*this, &ALabel::handleToggle));
}
// configure events' user commands
if (config_["on-click"].isString() || config_["on-click-right"].isString()) {
event_box_.add_events(Gdk::BUTTON_PRESS_MASK);
event_box_.signal_button_press_event().connect(
sigc::mem_fun(*this, &ALabel::handleToggle));
}
if (config_["on-scroll-up"].isString() || config_["on-scroll-down"].isString()) {
event_box_.add_events(Gdk::SCROLL_MASK);
event_box_.signal_scroll_event().connect(
sigc::mem_fun(*this, &ALabel::handleScroll));
}
}
auto waybar::ALabel::update() -> void {
// Nothing here
}
bool waybar::ALabel::handleToggle(GdkEventButton* const& e) {
if (config_["on-click"].isString() && e->button == 1) {
waybar::util::command::forkExec(config_["on-click"].asString());
} else if (config_["on-click-right"].isString() && e->button == 3) {
waybar::util::command::forkExec(config_["on-click-right"].asString());
} else {
alt = !alt;
if (alt) {
format_ = config_["format-alt"].asString();
} else {
format_ = default_format_;
}
}
dp.emit();
return true;
}
bool waybar::ALabel::handleScroll(GdkEventScroll* e) {
// Avoid concurrent scroll event
std::lock_guard<std::mutex> lock(mutex_);
bool direction_up = false;
if (e->direction == GDK_SCROLL_UP) {
direction_up = true;
}
if (e->direction == GDK_SCROLL_DOWN) {
direction_up = false;
}
if (e->direction == GDK_SCROLL_SMOOTH) {
gdouble delta_x, delta_y;
gdk_event_get_scroll_deltas(reinterpret_cast<const GdkEvent*>(e),
&delta_x, &delta_y);
if (delta_y < 0) {
direction_up = true;
} else if (delta_y > 0) {
direction_up = false;
}
}
if (direction_up && config_["on-scroll-up"].isString()) {
waybar::util::command::forkExec(config_["on-scroll-up"].asString());
} else if (config_["on-scroll-down"].isString()) {
waybar::util::command::forkExec(config_["on-scroll-down"].asString());
}
dp.emit();
return true;
}
std::string waybar::ALabel::getIcon(uint16_t percentage,
const std::string& alt) {
auto format_icons = config_["format-icons"];
if (format_icons.isObject()) {
if (!alt.empty() && format_icons[alt].isString()) {
format_icons = format_icons[alt];
} else {
format_icons = format_icons["default"];
}
}
if (format_icons.isArray()) {
auto size = format_icons.size();
auto idx = std::clamp(percentage / (100 / size), 0U, size - 1);
format_icons = format_icons[idx];
}
if (format_icons.isString()) {
return format_icons.asString();
}
return "";
}
waybar::ALabel::operator Gtk::Widget&() { return event_box_; }

View File

@ -3,186 +3,225 @@
#include "factory.hpp" #include "factory.hpp"
#include "util/json.hpp" #include "util/json.hpp"
waybar::Bar::Bar(Client &client, std::unique_ptr<struct wl_output *> &&p_output) waybar::Bar::Bar(const Client& client,
std::unique_ptr<struct wl_output *> &&p_output, uint32_t p_wl_name)
: client(client), window{Gtk::WindowType::WINDOW_TOPLEVEL}, : client(client), window{Gtk::WindowType::WINDOW_TOPLEVEL},
output(std::move(p_output)) surface(nullptr), layer_surface(nullptr),
output(std::move(p_output)), wl_name(p_wl_name)
{ {
static const struct zxdg_output_v1_listener xdgOutputListener = { static const struct zxdg_output_v1_listener xdgOutputListener = {
.logical_position = _handleLogicalPosition, .logical_position = handleLogicalPosition,
.logical_size = _handleLogicalSize, .logical_size = handleLogicalSize,
.done = _handleDone, .done = handleDone,
.name = _handleName, .name = handleName,
.description = _handleDescription, .description = handleDescription,
}; };
_xdgOutput = xdg_output_ =
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(_xdgOutput, &xdgOutputListener, this); zxdg_output_v1_add_listener(xdg_output_, &xdgOutputListener, this);
window.set_title("waybar"); window.set_title("waybar");
window.set_name("waybar");
window.set_decorated(false); window.set_decorated(false);
_setupConfig(); window.set_resizable(false);
_setupCss(); setupConfig();
_setupWidgets(); setupCss();
if (_config["height"])
_height = _config["height"].asUInt(); auto wrap = reinterpret_cast<GtkWidget*>(window.gobj());
bool positionBottom = _config["position"] == "bottom"; gtk_widget_realize(wrap);
bool layerTop = _config["layer"] == "top"; GdkWindow *gdk_window = gtk_widget_get_window(wrap);
gtk_widget_realize(GTK_WIDGET(window.gobj())); gdk_wayland_window_set_use_custom_surface(gdk_window);
GdkWindow *gdkWindow = gtk_widget_get_window(GTK_WIDGET(window.gobj())); surface = gdk_wayland_window_get_wl_surface(gdk_window);
gdk_wayland_window_set_use_custom_surface(gdkWindow);
surface = gdk_wayland_window_get_wl_surface(gdkWindow); std::size_t layer_top = config_["layer"] == "top"
layerSurface = zwlr_layer_shell_v1_get_layer_surface( ? ZWLR_LAYER_SHELL_V1_LAYER_TOP : ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM;
client.layer_shell, surface, *output, layer_surface = zwlr_layer_shell_v1_get_layer_surface(
(layerTop ? ZWLR_LAYER_SHELL_V1_LAYER_TOP : ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM), client.layer_shell, surface, *output, layer_top, "waybar");
"waybar");
zwlr_layer_surface_v1_set_anchor(layerSurface, static const struct zwlr_layer_surface_v1_listener layer_surface_listener = {
ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | .configure = layerSurfaceHandleConfigure,
(positionBottom ? ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM : ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP)); .closed = layerSurfaceHandleClosed,
zwlr_layer_surface_v1_set_size(layerSurface, _width, _height);
static const struct zwlr_layer_surface_v1_listener layerSurfaceListener = {
.configure = _layerSurfaceHandleConfigure,
.closed = _layerSurfaceHandleClosed,
}; };
zwlr_layer_surface_v1_add_listener(layerSurface, &layerSurfaceListener, zwlr_layer_surface_v1_add_listener(layer_surface,
this); &layer_surface_listener, this);
std::size_t anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT
| ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
if (config_["position"] == "bottom") {
anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM;
} else {
anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP;
}
auto height = config_["height"].isUInt() ? config_["height"].asUInt() : height_;
auto width = config_["width"].isUInt() ? config_["width"].asUInt() : width_;
zwlr_layer_surface_v1_set_anchor(layer_surface, anchor);
zwlr_layer_surface_v1_set_exclusive_zone(layer_surface, height);
zwlr_layer_surface_v1_set_size(layer_surface, width, height);
wl_surface_commit(surface); wl_surface_commit(surface);
setupWidgets();
} }
void waybar::Bar::_handleLogicalPosition(void *data, void waybar::Bar::handleLogicalPosition(void* /*data*/,
struct zxdg_output_v1 *zxdg_output_v1, int32_t x, int32_t y) struct zxdg_output_v1* /*zxdg_output_v1*/, int32_t /*x*/, int32_t /*y*/)
{ {
// Nothing here // Nothing here
} }
void waybar::Bar::_handleLogicalSize(void *data, void waybar::Bar::handleLogicalSize(void* /*data*/,
struct zxdg_output_v1 *zxdg_output_v1, int32_t width, int32_t height) struct zxdg_output_v1* /*zxdg_output_v1*/, int32_t /*width*/,
int32_t /*height*/)
{ {
// Nothing here // Nothing here
} }
void waybar::Bar::_handleDone(void *data, struct zxdg_output_v1 *zxdg_output_v1) void waybar::Bar::handleDone(void* /*data*/,
struct zxdg_output_v1* /*zxdg_output_v1*/)
{ {
// Nothing here // Nothing here
} }
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 = reinterpret_cast<waybar::Bar *>(data); auto o = static_cast<waybar::Bar *>(data);
o->outputName = name; o->output_name = name;
} }
void waybar::Bar::_handleDescription(void *data, void waybar::Bar::handleDescription(void* /*data*/,
struct zxdg_output_v1 *zxdg_output_v1, const char *description) struct zxdg_output_v1* /*zxdg_output_v1*/, const char* /*description*/)
{ {
// Nothing here // Nothing here
} }
void waybar::Bar::_layerSurfaceHandleConfigure( void waybar::Bar::layerSurfaceHandleConfigure(void* data,
void *data, struct zwlr_layer_surface_v1 *surface, uint32_t serial, struct zwlr_layer_surface_v1* surface, uint32_t serial, uint32_t width,
uint32_t width, uint32_t height) uint32_t height)
{ {
auto o = reinterpret_cast<waybar::Bar *>(data); auto o = static_cast<waybar::Bar *>(data);
o->window.show_all(); o->window.show_all();
o->setWidth(o->_config["width"] ? o->_config["width"].asUInt() : width);
zwlr_layer_surface_v1_ack_configure(surface, serial); zwlr_layer_surface_v1_ack_configure(surface, serial);
if (o->_height != height) { if (width != o->width_ || height != o->height_) {
height = o->_height; o->width_ = width;
std::cout << fmt::format("New Height: {}", height) << std::endl; o->height_ = height;
zwlr_layer_surface_v1_set_size(surface, o->_width, height); o->window.set_size_request(o->width_, o->height_);
zwlr_layer_surface_v1_set_exclusive_zone(surface, o->visible ? height : 0); 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(
"Bar configured (width: {}, height: {}) for output: {}",
o->width_, o->height_, o->output_name) << std::endl;
wl_surface_commit(o->surface); wl_surface_commit(o->surface);
} }
} }
void waybar::Bar::_layerSurfaceHandleClosed(void *data, void waybar::Bar::layerSurfaceHandleClosed(void* data,
struct zwlr_layer_surface_v1 *surface) struct zwlr_layer_surface_v1* /*surface*/)
{ {
auto o = reinterpret_cast<waybar::Bar *>(data); auto o = static_cast<waybar::Bar *>(data);
zwlr_layer_surface_v1_destroy(o->layerSurface); std::cout << "Bar removed from output: " + o->output_name << std::endl;
o->layerSurface = nullptr; zwlr_layer_surface_v1_destroy(o->layer_surface);
wl_surface_destroy(o->surface); wl_output_destroy(*o->output);
o->surface = nullptr; zxdg_output_v1_destroy(o->xdg_output_);
o->window.close(); o->modules_left_.clear();
} o->modules_center_.clear();
o->modules_right_.clear();
auto waybar::Bar::setWidth(uint32_t width) -> void
{
if (width == this->_width) return;
std::cout << fmt::format("Bar width configured: {}", width) << std::endl;
this->_width = width;
window.set_size_request(width);
window.resize(width, _height);
zwlr_layer_surface_v1_set_size(layerSurface, width, _height + 1);
wl_surface_commit(surface);
} }
auto waybar::Bar::toggle() -> void auto waybar::Bar::toggle() -> void
{ {
visible = !visible; visible = !visible;
auto zone = visible ? _height : 0; auto zone = visible ? height_ : 0;
zwlr_layer_surface_v1_set_exclusive_zone(layerSurface, zone); zwlr_layer_surface_v1_set_exclusive_zone(layer_surface, zone);
wl_surface_commit(surface); wl_surface_commit(surface);
} }
auto waybar::Bar::_setupConfig() -> void auto waybar::Bar::setupConfig() -> void
{ {
util::JsonParser parser; std::ifstream file(client.config_file);
std::ifstream file(client.configFile); if (!file.is_open()) {
if (!file.is_open())
throw std::runtime_error("Can't open config file"); throw std::runtime_error("Can't open config file");
}
std::string str((std::istreambuf_iterator<char>(file)), std::string str((std::istreambuf_iterator<char>(file)),
std::istreambuf_iterator<char>()); std::istreambuf_iterator<char>());
_config = parser.parse(str); util::JsonParser parser;
config_ = parser.parse(str);
} }
auto waybar::Bar::_setupCss() -> void auto waybar::Bar::setupCss() -> void
{ {
_cssProvider = Gtk::CssProvider::create(); css_provider_ = Gtk::CssProvider::create();
_styleContext = Gtk::StyleContext::create(); style_context_ = Gtk::StyleContext::create();
// load our css file, wherever that may be hiding // Load our css file, wherever that may be hiding
if (_cssProvider->load_from_path(client.cssFile)) { if (css_provider_->load_from_path(client.css_file)) {
Glib::RefPtr<Gdk::Screen> screen = window.get_screen(); Glib::RefPtr<Gdk::Screen> screen = window.get_screen();
_styleContext->add_provider_for_screen(screen, _cssProvider, style_context_->add_provider_for_screen(screen, css_provider_,
GTK_STYLE_PROVIDER_PRIORITY_USER); GTK_STYLE_PROVIDER_PRIORITY_USER);
} }
} }
auto waybar::Bar::_setupWidgets() -> void void waybar::Bar::getModules(const Factory& factory, const std::string& pos)
{
if (config_[pos].isArray()) {
for (const auto &name : config_[pos]) {
try {
auto module = factory.makeModule(name.asString());
if (pos == "modules-left") {
modules_left_.emplace_back(module);
}
if (pos == "modules-center") {
modules_center_.emplace_back(module);
}
if (pos == "modules-right") {
modules_right_.emplace_back(module);
}
module->dp.connect([module, &name] {
try {
module->update();
} catch (const std::exception& e) {
std::cerr << name.asString() + ": " + e.what() << std::endl;
}
});
} catch (const std::exception& e) {
std::cerr << e.what() << std::endl;
}
}
}
}
auto waybar::Bar::setupWidgets() -> void
{ {
auto &left = *Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 0)); auto &left = *Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 0));
auto &center = *Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 0)); auto &center = *Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 0));
auto &right = *Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 0)); auto &right = *Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 0));
auto &box1 = *Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 0)); auto &box = *Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 0));
window.add(box1); window.add(box);
box1.set_homogeneous(true); box.pack_start(left, true, true);
box1.pack_start(left, true, true); box.set_center_widget(center);
box1.pack_start(center, false, false); box.pack_end(right, true, true);
box1.pack_end(right, true, true);
Factory factory(*this, _config); Factory factory(*this, config_);
getModules(factory, "modules-left");
if (_config["modules-left"]) { getModules(factory, "modules-center");
for (auto name : _config["modules-left"]) { getModules(factory, "modules-right");
auto module = factory.makeModule(name.asString()); for (auto const& module : modules_left_) {
if (module)
left.pack_start(*module, false, true, 0); left.pack_start(*module, false, true, 0);
} }
for (auto const& module : modules_center_) {
center.pack_start(*module, true, true, 0);
} }
if (_config["modules-center"]) { std::reverse(modules_right_.begin(), modules_right_.end());
for (auto name : _config["modules-center"]) { for (auto const& module : modules_right_) {
auto module = factory.makeModule(name.asString());
if (module)
center.pack_start(*module, true, false, 10);
}
}
if (_config["modules-right"]) {
std::reverse(_config["modules-right"].begin(), _config["modules-right"].end());
for (auto name : _config["modules-right"]) {
auto module = factory.makeModule(name.asString());
if (module)
right.pack_end(*module, false, false, 0); right.pack_end(*module, false, false, 0);
} }
} }
}

View File

@ -1,93 +1,143 @@
#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_main(argc, argv), : gtk_main(argc, argv),
gdk_display(Gdk::Display::get_default()), gdk_display(Gdk::Display::get_default())
wlDisplay(gdk_wayland_display_get_wl_display(gdk_display->gobj())) {
if (!gdk_display) {
throw std::runtime_error("Can't find display");
}
if (!GDK_IS_WAYLAND_DISPLAY(gdk_display->gobj())) {
throw std::runtime_error("Bar need to run under Wayland");
}
wl_display = gdk_wayland_display_get_wl_display(gdk_display->gobj());
}
const std::string waybar::Client::getValidPath(std::vector<std::string> paths)
{ {
auto getFirstValidPath = [] (std::vector<std::string> possiblePaths) {
wordexp_t p; wordexp_t p;
for (std::string path: possiblePaths) { for (const std::string &path: paths) {
if (wordexp(path.c_str(), &p, 0) == 0) { if (wordexp(path.c_str(), &p, 0) == 0) {
if (access(p.we_wordv[0], F_OK) == 0) { if (access(*p.we_wordv, F_OK) == 0) {
std::string result = p.we_wordv[0]; std::string result = *p.we_wordv;
wordfree(&p); wordfree(&p);
return result; return result;
} else {
wordfree(&p);
} }
wordfree(&p);
} }
} }
return std::string(); return std::string();
}; }
configFile = getFirstValidPath({ void waybar::Client::handleGlobal(void *data, struct wl_registry *registry,
uint32_t name, const char *interface, uint32_t version)
{
auto o = static_cast<waybar::Client *>(data);
if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) {
o->layer_shell = static_cast<struct zwlr_layer_shell_v1 *>(
wl_registry_bind(registry, name, &zwlr_layer_shell_v1_interface, version));
} else if (strcmp(interface, wl_output_interface.name) == 0) {
auto output = std::make_unique<struct wl_output *>();
*output = static_cast<struct wl_output *>(wl_registry_bind(registry, name,
&wl_output_interface, version));
if (o->xdg_output_manager != nullptr) {
o->bars.emplace_back(std::make_unique<Bar>(*o, std::move(output), name));
}
} else if (strcmp(interface, wl_seat_interface.name) == 0) {
o->seat = static_cast<struct wl_seat *>(wl_registry_bind(registry, name,
&wl_seat_interface, version));
} else if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0
&& version >= ZXDG_OUTPUT_V1_NAME_SINCE_VERSION) {
o->xdg_output_manager = static_cast<struct zxdg_output_manager_v1 *>(
wl_registry_bind(registry, name,
&zxdg_output_manager_v1_interface, ZXDG_OUTPUT_V1_NAME_SINCE_VERSION));
}
}
void waybar::Client::handleGlobalRemove(void* data,
struct wl_registry* /*registry*/, uint32_t name)
{
auto o = static_cast<waybar::Client *>(data);
for (auto it = o->bars.begin(); it != o->bars.end(); ++it) {
if ((**it).wl_name == name) {
o->bars.erase(it);
break;
}
}
}
void waybar::Client::setupConfigs(const std::string& config, const std::string& style)
{
config_file = config.empty() ? getValidPath({
"$XDG_CONFIG_HOME/waybar/config", "$XDG_CONFIG_HOME/waybar/config",
"$HOME/.config/waybar/config",
"$HOME/waybar/config", "$HOME/waybar/config",
"/etc/xdg/waybar/config", "/etc/xdg/waybar/config",
"./resources/config", "./resources/config",
}); }) : config;
cssFile = getFirstValidPath({ css_file = style.empty() ? getValidPath({
"$XDG_CONFIG_HOME/waybar/style.css", "$XDG_CONFIG_HOME/waybar/style.css",
"$HOME/.config/waybar/style.css",
"$HOME/waybar/style.css", "$HOME/waybar/style.css",
"/etc/xdg/waybar/style.css", "/etc/xdg/waybar/style.css",
"./resources/style.css", "./resources/style.css",
}); }) : style;
if (css_file.empty() || config_file.empty()) {
throw std::runtime_error("Missing required resources files");
}
std::cout << "Resources files: " + config_file + ", " + css_file << std::endl;
} }
void waybar::Client::_handle_global(void *data, struct wl_registry *registry, void waybar::Client::bindInterfaces()
uint32_t name, const char *interface, uint32_t version)
{ {
auto o = reinterpret_cast<waybar::Client *>(data); registry = wl_display_get_registry(wl_display);
if (!strcmp(interface, zwlr_layer_shell_v1_interface.name)) {
o->layer_shell = (zwlr_layer_shell_v1 *)wl_registry_bind(registry, name,
&zwlr_layer_shell_v1_interface, version);
} else if (!strcmp(interface, wl_output_interface.name)) {
o->wlOutput = (struct wl_output *)wl_registry_bind(registry, name,
&wl_output_interface, version);
auto output = std::make_unique<struct wl_output *>();
*output = o->wlOutput;
if (o->xdg_output_manager)
o->bars.emplace_back(std::make_unique<Bar>(*o, std::move(output)));
} else if (!strcmp(interface, wl_seat_interface.name)) {
o->seat = (struct wl_seat *)wl_registry_bind(registry, name,
&wl_seat_interface, version);
} else if (!strcmp(interface, zxdg_output_manager_v1_interface.name)
&& version >= ZXDG_OUTPUT_V1_NAME_SINCE_VERSION) {
o->xdg_output_manager =
(struct zxdg_output_manager_v1 *)wl_registry_bind(registry, name,
&zxdg_output_manager_v1_interface, ZXDG_OUTPUT_V1_NAME_SINCE_VERSION);
if (o->wlOutput) {
auto output = std::make_unique<struct wl_output *>();
*output = o->wlOutput;
o->bars.emplace_back(std::make_unique<Bar>(*o, std::move(output)));
}
}
}
void waybar::Client::_handle_global_remove(void *data,
struct wl_registry *registry, uint32_t name)
{
// TODO
}
void waybar::Client::bind_interfaces()
{
registry = wl_display_get_registry(wlDisplay);
static const struct wl_registry_listener registry_listener = { static const struct wl_registry_listener registry_listener = {
.global = _handle_global, .global = handleGlobal,
.global_remove = _handle_global_remove, .global_remove = handleGlobalRemove,
}; };
wl_registry_add_listener(registry, &registry_listener, this); wl_registry_add_listener(registry, &registry_listener, this);
wl_display_roundtrip(wlDisplay); 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[])
{ {
bind_interfaces(); bool show_help = false;
gtk_main.run(); bool show_version = false;
std::string config;
std::string style;
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");
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();
gtk_main.run();
bars.clear();
zxdg_output_manager_v1_destroy(xdg_output_manager);
zwlr_layer_shell_v1_destroy(layer_shell);
wl_registry_destroy(registry);
wl_seat_destroy(seat);
wl_display_disconnect(wl_display);
return 0; return 0;
} }

View File

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

View File

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

View File

@ -1,19 +1,21 @@
#include "client.hpp"
#include <csignal> #include <csignal>
#include <iostream> #include <iostream>
#include "client.hpp"
namespace waybar { namespace waybar {
static Client* client; static Client* client;
}
} // namespace waybar
int main(int argc, char* argv[]) int main(int argc, char* argv[])
{ {
try { try {
waybar::Client c(argc, argv); waybar::Client c(argc, 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.get()->toggle(); bar->toggle();
} }
}); });

View File

@ -1,81 +1,143 @@
#include "modules/battery.hpp" #include "modules/battery.hpp"
waybar::modules::Battery::Battery(Json::Value config) waybar::modules::Battery::Battery(const std::string& id, const Json::Value& config)
: _config(config) : ALabel(config, "{capacity}%", 60)
{ {
label_.set_name("battery");
if (!id.empty()) {
label_.get_style_context()->add_class(id);
}
try { try {
for (auto &node : fs::directory_iterator(_data_dir)) { if (config_["bat"].isString()) {
auto dir = data_dir_ / config_["bat"].asString();
if (fs::is_directory(dir) && fs::exists(dir / "capacity")
&& fs::exists(dir / "status") && fs::exists(dir / "uevent")) {
batteries_.push_back(dir);
}
} else {
for (auto const& node : fs::directory_iterator(data_dir_)) {
if (fs::is_directory(node) && fs::exists(node / "capacity") if (fs::is_directory(node) && fs::exists(node / "capacity")
&& fs::exists(node / "status") && fs::exists(node / "uevent")) && fs::exists(node / "status") && fs::exists(node / "uevent")) {
_batteries.push_back(node); batteries_.push_back(node);
}
}
} }
} catch (fs::filesystem_error &e) { } catch (fs::filesystem_error &e) {
throw std::runtime_error(e.what()); throw std::runtime_error(e.what());
} }
if (batteries_.empty()) {
if (!_batteries.size()) if (config_["bat"].isString()) {
throw std::runtime_error("No battery named " + config_["bat"].asString());
}
throw std::runtime_error("No batteries."); throw std::runtime_error("No batteries.");
}
auto fd = inotify_init(); fd_ = inotify_init1(IN_CLOEXEC);
if (fd == -1) if (fd_ == -1) {
throw std::runtime_error("Unable to listen batteries."); throw std::runtime_error("Unable to listen batteries.");
for (auto &bat : _batteries) }
inotify_add_watch(fd, (bat / "uevent").c_str(), IN_ACCESS); for (auto const& bat : batteries_) {
// Trigger first value inotify_add_watch(fd_, (bat / "uevent").c_str(), IN_ACCESS);
}
worker();
}
waybar::modules::Battery::~Battery()
{
close(fd_);
}
void waybar::modules::Battery::worker()
{
// Trigger first values
update(); update();
_label.set_name("battery"); thread_timer_ = [this] {
_thread = [this, fd] { thread_.sleep_for(interval_);
struct inotify_event event; dp.emit();
int nbytes = read(fd, &event, sizeof(event));
if (nbytes != sizeof(event))
return;
Glib::signal_idle().connect_once(sigc::mem_fun(*this, &Battery::update));
}; };
thread_ = [this] {
struct inotify_event event = {0};
int nbytes = read(fd_, &event, sizeof(event));
if (nbytes != sizeof(event)) {
return;
}
// TODO: don't stop timer for now since there is some bugs :?
// thread_timer_.stop();
dp.emit();
};
}
const std::tuple<uint8_t, std::string> waybar::modules::Battery::getInfos() const
{
try {
uint16_t total = 0;
std::string status = "Unknown";
for (auto const& bat : batteries_) {
uint16_t capacity;
std::string _status;
std::ifstream(bat / "capacity") >> capacity;
std::ifstream(bat / "status") >> _status;
if (_status != "Unknown") {
status = _status;
}
total += capacity;
}
uint16_t capacity = total / batteries_.size();
return {capacity, status};
} catch (const std::exception& e) {
std::cerr << e.what() << std::endl;
return {0, "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 waybar::modules::Battery::update() -> void
{ {
try { auto [capacity, status] = getInfos();
uint16_t total = 0; label_.set_tooltip_text(status);
bool charging = false; std::transform(status.begin(), status.end(), status.begin(), ::tolower);
std::string status; auto format = format_;
for (auto &bat : _batteries) { auto state = getState(capacity);
uint16_t capacity; label_.get_style_context()->remove_class(old_status_);
std::ifstream(bat / "capacity") >> capacity; label_.get_style_context()->add_class(status);
std::ifstream(bat / "status") >> status; old_status_ = status;
if (status == "Charging") if (!state.empty() && config_["format-" + status + "-" + state].isString()) {
charging = true; format = config_["format-" + status + "-" + state].asString();
total += capacity; } else if (config_["format-" + status].isString()) {
format = config_["format-" + status].asString();
} else if (!state.empty() && config_["format-" + state].isString()) {
format = config_["format-" + state].asString();
} }
uint16_t capacity = total / _batteries.size(); if (format.empty()) {
auto format = _config["format"] event_box_.hide();
? _config["format"].asString() : "{capacity}%"; } else {
_label.set_text(fmt::format(format, fmt::arg("capacity", capacity), event_box_.show();
fmt::arg("icon", _getIcon(capacity)))); label_.set_markup(fmt::format(format, fmt::arg("capacity", capacity),
_label.set_tooltip_text(status); fmt::arg("icon", getIcon(capacity))));
if (charging)
_label.get_style_context()->add_class("charging");
else
_label.get_style_context()->remove_class("charging");
auto critical = _config["critical"] ? _config["critical"].asUInt() : 15;
if (capacity <= critical && !charging)
_label.get_style_context()->add_class("warning");
else
_label.get_style_context()->remove_class("warning");
} catch (const std::exception& e) {
std::cerr << e.what() << std::endl;
} }
} }
std::string waybar::modules::Battery::_getIcon(uint16_t percentage)
{
if (!_config["format-icons"] || !_config["format-icons"].isArray()) return "";
auto size = _config["format-icons"].size();
auto idx = std::clamp(percentage / (100 / size), 0U, size - 1);
return _config["format-icons"][idx].asString();
}
waybar::modules::Battery::operator Gtk::Widget &()
{
return _label;
}

View File

@ -1,27 +1,25 @@
#include "modules/clock.hpp" #include "modules/clock.hpp"
waybar::modules::Clock::Clock(Json::Value config) waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
: _config(config) : ALabel(config, "{:%H:%M}", 60)
{ {
_label.set_name("clock"); label_.set_name("clock");
_thread = [this] { if (!id.empty()) {
Glib::signal_idle().connect_once(sigc::mem_fun(*this, &Clock::update)); label_.get_style_context()->add_class(id);
}
thread_ = [this] {
auto now = waybar::chrono::clock::now(); auto now = waybar::chrono::clock::now();
auto timeout = dp.emit();
std::chrono::floor<std::chrono::minutes>(now + std::chrono::minutes(1)); auto timeout = std::chrono::floor<std::chrono::seconds>(now + interval_);
_thread.sleep_until(timeout); auto time_s = std::chrono::time_point_cast<std::chrono::seconds>(timeout);
}; auto sub_m =
std::chrono::duration_cast<std::chrono::seconds>(time_s.time_since_epoch()).count() % interval_.count();
thread_.sleep_until(timeout - std::chrono::seconds(sub_m - 1));
}; };
}
auto waybar::modules::Clock::update() -> void auto waybar::modules::Clock::update() -> void
{ {
auto t = std::time(nullptr); auto localtime = fmt::localtime(std::time(nullptr));
auto localtime = std::localtime(&t); label_.set_markup(fmt::format(format_, localtime));
auto format =
_config["format"] ? _config["format"].asString() : "{:02}:{:02}";
_label.set_text(fmt::format(format, localtime->tm_hour, localtime->tm_min));
}
waybar::modules::Clock::operator Gtk::Widget &() {
return _label;
} }

View File

@ -1,27 +1,88 @@
#include "modules/cpu.hpp" #include "modules/cpu.hpp"
waybar::modules::Cpu::Cpu(Json::Value config) waybar::modules::Cpu::Cpu(const std::string& id, const Json::Value& config)
: _config(config) : ALabel(config, "{usage}%", 10)
{ {
_label.set_name("cpu"); label_.set_name("cpu");
int interval = _config["interval"] ? _config["inveral"].asInt() : 10; if (!id.empty()) {
_thread = [this, interval] { label_.get_style_context()->add_class(id);
Glib::signal_idle().connect_once(sigc::mem_fun(*this, &Cpu::update)); }
_thread.sleep_for(chrono::seconds(interval)); thread_ = [this] {
}; dp.emit();
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
if (!sysinfo(&info)) { auto cpu_load = getCpuLoad();
float f_load = 1.f / (1 << SI_LOAD_SHIFT); auto [cpu_usage, tooltip] = getCpuUsage();
int load = info.loads[0] * f_load * 100 / get_nprocs(); label_.set_tooltip_text(tooltip);
auto format = _config["format"] ? _config["format"].asString() : "{}%"; label_.set_markup(fmt::format(format_,
_label.set_text(fmt::format(format, load)); fmt::arg("load", cpu_load), fmt::arg("usage", cpu_usage)));
}
} }
waybar::modules::Cpu::operator Gtk::Widget &() { uint16_t waybar::modules::Cpu::getCpuLoad()
return _label; {
struct sysinfo info = {0};
if (sysinfo(&info) == 0) {
float f_load = 1.f / (1u << SI_LOAD_SHIFT);
uint16_t load = info.loads[0] * f_load * 100 / get_nprocs();
return load;
}
throw std::runtime_error("Can't get Cpu load");
}
std::tuple<uint16_t, std::string> waybar::modules::Cpu::getCpuUsage()
{
if (prev_times_.empty()) {
prev_times_ = parseCpuinfo();
std::this_thread::sleep_for(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,49 +1,144 @@
#include "modules/custom.hpp" #include "modules/custom.hpp"
#include <iostream>
waybar::modules::Custom::Custom(std::string name, Json::Value config) waybar::modules::Custom::Custom(const std::string& name,
: _name(name), _config(config) const Json::Value& config)
: ALabel(config, "{}"), name_(name), fp_(nullptr)
{ {
if (!_config["exec"]) label_.set_name("custom-" + name_);
throw std::runtime_error(name + " has no exec path."); if (config_["exec"].isString()) {
int interval = _config["interval"] ? _config["inveral"].asInt() : 30; if (interval_.count() > 0) {
_thread = [this, interval] { delayWorker();
Glib::signal_idle().connect_once(sigc::mem_fun(*this, &Custom::update)); } else {
_thread.sleep_for(chrono::seconds(interval)); continuousWorker();
}; }
}; }
dp.emit();
}
auto waybar::modules::Custom::update() -> void waybar::modules::Custom::~Custom()
{ {
std::array<char, 128> buffer; if (fp_) {
std::string output; pclose(fp_);
std::shared_ptr<FILE> fp(popen(_config["exec"].asCString(), "r"), pclose); fp_ = nullptr;
if (!fp) { }
std::cerr << _name + " can't exec " + _config["exec"].asString() << std::endl; }
void waybar::modules::Custom::delayWorker()
{
thread_ = [this] {
bool can_update = true;
if (config_["exec-if"].isString()) {
auto res = waybar::util::command::exec(config_["exec-if"].asString());
if (res.exit_code != 0) {
can_update = false;
event_box_.hide();
}
}
if (can_update) {
output_ = waybar::util::command::exec(config_["exec"].asString());
dp.emit();
}
thread_.sleep_for(interval_);
};
}
void waybar::modules::Custom::continuousWorker()
{
auto cmd = config_["exec"].asString();
fp_ = popen(cmd.c_str(), "r");
if (!fp_) {
throw std::runtime_error("Unable to open " + cmd);
}
thread_ = [this] {
char* buff = nullptr;
size_t len = 0;
if (getline(&buff, &len, fp_) == -1) {
if (fp_) {
pclose(fp_);
fp_ = nullptr;
}
thread_.stop();
output_ = { 1, "" };
std::cerr << name_ + " just stopped, is it endless?" << std::endl;
dp.emit();
return; return;
} }
while (!feof(fp.get())) {
if (fgets(buffer.data(), 128, fp.get()) != nullptr) std::string output = buff;
output += buffer.data();
}
// Remove last newline // Remove last newline
if (!output.empty() && output[output.length()-1] == '\n') { if (!output.empty() && output[output.length()-1] == '\n') {
output.erase(output.length()-1); output.erase(output.length()-1);
} }
output_ = { 0, output };
dp.emit();
};
}
auto waybar::modules::Custom::update() -> void
{
// Hide label if output is empty // Hide label if output is empty
if (output.empty()) { if (config_["exec"].isString() && (output_.out.empty() || output_.exit_code != 0)) {
_label.set_name(""); event_box_.hide();
_label.hide();
} else { } else {
_label.set_name("custom-" + _name); if (config_["return-type"].asString() == "json") {
auto format = _config["format"] ? _config["format"].asString() : "{}"; parseOutputJson();
_label.set_text(fmt::format(format, output)); } else {
_label.show(); parseOutputRaw();
}
auto str = fmt::format(format_, text_);
label_.set_markup(str);
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();
} }
} }
waybar::modules::Custom::operator Gtk::Widget &() { void waybar::modules::Custom::parseOutputRaw()
return _label; {
std::istringstream output(output_.out);
std::string line;
int i = 0;
while (getline(output, line)) {
if (i == 0) {
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);
text_ = parsed["text"].asString();
tooltip_ = parsed["tooltip"].asString();
class_ = parsed["class"].asString();
break;
}
} }

View File

@ -1,30 +1,63 @@
#include "modules/memory.hpp" #include "modules/memory.hpp"
waybar::modules::Memory::Memory(Json::Value config) waybar::modules::Memory::Memory(const std::string& id, const Json::Value& config)
: _config(config) : ALabel(config, "{}%", 30)
{ {
_label.set_name("memory"); label_.set_name("memory");
int interval = _config["interval"] ? _config["inveral"].asInt() : 30; if (!id.empty()) {
_thread = [this, interval] { label_.get_style_context()->add_class(id);
Glib::signal_idle().connect_once(sigc::mem_fun(*this, &Memory::update)); }
_thread.sleep_for(chrono::seconds(interval)); thread_ = [this] {
}; dp.emit();
thread_.sleep_for(interval_);
}; };
}
auto waybar::modules::Memory::update() -> void auto waybar::modules::Memory::update() -> void
{ {
struct sysinfo info; parseMeminfo();
if (!sysinfo(&info)) { 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);
auto format = _config["format"] ? _config["format"].asString() : "{}%"; label_.set_tooltip_text(fmt::format("{:.{}f}Gb used", used_ram_gigabytes, 1));
_label.set_text(fmt::format(format, used_ram_percentage)); event_box_.show();
auto used_ram_gigabytes = (total - freeram) / std::pow(1024, 3); } else {
_label.set_tooltip_text(fmt::format("{:.{}f}Gb used", used_ram_gigabytes, 1)); event_box_.hide();
} }
} }
waybar::modules::Memory::operator Gtk::Widget &() { void waybar::modules::Memory::parseMeminfo()
return _label; {
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,30 +1,349 @@
#include "modules/network.hpp" #include "modules/network.hpp"
waybar::modules::Network::Network(Json::Value config) waybar::modules::Network::Network(const std::string& id, const Json::Value& config)
: _config(config), _ifid(if_nametoindex(config["interface"].asCString())) : ALabel(config, "{ifname}", 60), family_(AF_INET),
cidr_(-1), signal_strength_dbm_(0), signal_strength_(0)
{ {
if (_ifid == 0) label_.set_name("network");
if (!id.empty()) {
label_.get_style_context()->add_class(id);
}
sock_fd_ = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
if (sock_fd_ < 0) {
throw std::runtime_error("Can't open network socket");
}
nladdr_.nl_family = AF_NETLINK;
nladdr_.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR;
if (bind(sock_fd_, reinterpret_cast<struct sockaddr *>(&nladdr_),
sizeof(nladdr_)) != 0) {
throw std::runtime_error("Can't bind network socket");
}
if (config_["interface"].isString()) {
ifid_ = if_nametoindex(config_["interface"].asCString());
ifname_ = config_["interface"].asString();
if (ifid_ <= 0) {
throw std::runtime_error("Can't found network interface"); throw std::runtime_error("Can't found network interface");
_label.set_name("network"); }
int interval = _config["interval"] ? _config["inveral"].asInt() : 30; } else {
_thread = [this, interval] { ifid_ = getExternalInterface();
Glib::signal_idle().connect_once(sigc::mem_fun(*this, &Network::update)); if (ifid_ > 0) {
_thread.sleep_for(chrono::minutes(1)); char ifname[IF_NAMESIZE];
if_indextoname(ifid_, ifname);
ifname_ = ifname;
}
}
initNL80211();
// Trigger first values
getInfo();
dp.emit();
worker();
}
waybar::modules::Network::~Network()
{
close(sock_fd_);
nl_socket_free(sk_);
}
void waybar::modules::Network::worker()
{
thread_ = [this] {
char buf[4096];
auto len = netlinkResponse(sock_fd_, buf, sizeof(buf), RTMGRP_LINK | RTMGRP_IPV4_IFADDR);
if (len == 0) {
return;
}
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) {
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>(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) {
if (ifid_ > 0) {
getInfo();
}
dp.emit();
}
}; };
thread_timer_ = [this] {
thread_.sleep_for(interval_);
if (ifid_ > 0) {
getInfo();
dp.emit();
}
}; };
}
auto waybar::modules::Network::update() -> void auto waybar::modules::Network::update() -> void
{ {
_getInfo(); auto format = format_;
auto format = _config["format"] ? _config["format"].asString() : "{essid}"; if (ifid_ <= 0 || ipaddr_.empty()) {
_label.set_text(fmt::format(format, format = config_["format-disconnected"].isString()
fmt::arg("essid", _essid), ? config_["format-disconnected"].asString() : format;
fmt::arg("signaldBm", _signalStrengthdBm), label_.get_style_context()->add_class("disconnected");
fmt::arg("signalStrength", _signalStrength) } else {
if (essid_.empty()) {
format = config_["format-ethernet"].isString()
? config_["format-ethernet"].asString() : format;
} else {
format = config_["format-wifi"].isString()
? config_["format-wifi"].asString() : format;
}
label_.get_style_context()->remove_class("disconnected");
}
label_.set_markup(fmt::format(format,
fmt::arg("essid", essid_),
fmt::arg("signaldBm", signal_strength_dbm_),
fmt::arg("signalStrength", signal_strength_),
fmt::arg("ifname", ifname_),
fmt::arg("netmask", netmask_),
fmt::arg("ipaddr", ipaddr_),
fmt::arg("cidr", cidr_)
)); ));
} }
int waybar::modules::Network::_scanCb(struct nl_msg *msg, void *data) { void waybar::modules::Network::disconnected()
{
essid_.clear();
signal_strength_dbm_ = 0;
signal_strength_ = 0;
ipaddr_.clear();
netmask_.clear();
cidr_ = 0;
if (!config_["interface"].isString()) {
ifname_.clear();
ifid_ = -1;
}
// Need to wait otherwise we'll have the same information
thread_.sleep_for(std::chrono::seconds(1));
}
void waybar::modules::Network::initNL80211()
{
sk_ = nl_socket_alloc();
if (genl_connect(sk_) != 0) {
nl_socket_free(sk_);
throw std::runtime_error("Can't connect to netlink socket");
}
if (nl_socket_modify_cb(sk_, NL_CB_VALID, NL_CB_CUSTOM, scanCb, this) < 0) {
nl_socket_free(sk_);
throw std::runtime_error("Can't connect to netlink socket");
}
nl80211_id_ = genl_ctrl_resolve(sk_, "nl80211");
if (nl80211_id_ < 0) {
nl_socket_free(sk_);
throw std::runtime_error("Can't resolve nl80211 interface");
}
}
// Based on https://gist.github.com/Yawning/c70d804d4b8ae78cc698
int waybar::modules::Network::getExternalInterface()
{
static const uint32_t route_buffer_size = 8192;
struct nlmsghdr *hdr = nullptr;
struct rtmsg *rt = nullptr;
char resp[route_buffer_size] = {0};
int ifidx = -1;
/* Prepare request. */
constexpr uint32_t reqlen = NLMSG_SPACE(sizeof(*rt));
char req[reqlen] = {0};
/* Build the RTM_GETROUTE request. */
hdr = reinterpret_cast<struct nlmsghdr *>(req);
hdr->nlmsg_len = NLMSG_LENGTH(sizeof(*rt));
hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
hdr->nlmsg_type = RTM_GETROUTE;
rt = static_cast<struct rtmsg *>(NLMSG_DATA(hdr));
rt->rtm_family = family_;
rt->rtm_table = RT_TABLE_MAIN;
/* Issue the query. */
if (netlinkRequest(sock_fd_, req, reqlen) < 0) {
goto out;
}
/* Read the response(s).
*
* WARNING: All the packets generated by the request must be consumed (as in,
* consume responses till NLMSG_DONE/NLMSG_ERROR is encountered).
*/
do {
auto len = netlinkResponse(sock_fd_, resp, route_buffer_size);
if (len < 0) {
goto out;
}
/* Parse the response payload into netlink messages. */
for (hdr = reinterpret_cast<struct nlmsghdr *>(resp); NLMSG_OK(hdr, len);
hdr = NLMSG_NEXT(hdr, len)) {
if (hdr->nlmsg_type == NLMSG_DONE) {
goto out;
}
if (hdr->nlmsg_type == NLMSG_ERROR) {
/* Even if we found the interface index, something is broken with the
* netlink socket, so return an error.
*/
ifidx = -1;
goto out;
}
/* If we found the correct answer, skip parsing the attributes. */
if (ifidx != -1) {
continue;
}
/* Find the message(s) concerting the main routing table, each message
* corresponds to a single routing table entry.
*/
rt = static_cast<struct rtmsg *>(NLMSG_DATA(hdr));
if (rt->rtm_table != RT_TABLE_MAIN) {
continue;
}
/* Parse all the attributes for a single routing table entry. */
struct rtattr *attr = RTM_RTA(rt);
uint64_t attrlen = RTM_PAYLOAD(hdr);
bool has_gateway = false;
bool has_destination = false;
int temp_idx = -1;
for (; RTA_OK(attr, attrlen); attr = RTA_NEXT(attr, attrlen)) {
/* Determine if this routing table entry corresponds to the default
* route by seeing if it has a gateway, and if a destination addr is
* set, that it is all 0s.
*/
switch (attr->rta_type) {
case RTA_GATEWAY:
/* The gateway of the route.
*
* If someone every needs to figure out the gateway address as well,
* it's here as the attribute payload.
*/
has_gateway = true;
break;
case RTA_DST: {
/* The destination address.
* Should be either missing, or maybe all 0s. Accept both.
*/
const uint32_t nr_zeroes = (family_ == AF_INET) ? 4 : 16;
unsigned char c = 0;
size_t dstlen = RTA_PAYLOAD(attr);
if (dstlen != nr_zeroes) {
break;
}
for (uint32_t i = 0; i < dstlen; i += 1) {
c |= *((unsigned char *)RTA_DATA(attr) + i);
}
has_destination = (c == 0);
break;
}
case RTA_OIF:
/* The output interface index. */
temp_idx = *static_cast<int*>(RTA_DATA(attr));
break;
default:
break;
}
}
/* If this is the default route, and we know the interface index,
* we can stop parsing this message.
*/
if (has_gateway && !has_destination && temp_idx != -1) {
ifidx = temp_idx;
break;
}
}
} while (true);
out:
return ifidx;
}
void waybar::modules::Network::getInterfaceAddress() {
unsigned int cidrRaw;
struct ifaddrs *ifaddr, *ifa;
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);
} else {
ipaddr_.clear();
netmask_.clear();
cidr_ = 0;
}
}
int waybar::modules::Network::netlinkRequest(int fd, void *req,
uint32_t reqlen, uint32_t groups)
{
struct sockaddr_nl sa = {};
sa.nl_family = AF_NETLINK;
sa.nl_groups = groups;
struct iovec iov = { req, reqlen };
struct msghdr msg = { &sa, sizeof(sa), &iov, 1, nullptr, 0, 0 };
return sendmsg(fd, &msg, 0);
}
int waybar::modules::Network::netlinkResponse(int fd, void *resp,
uint32_t resplen, uint32_t groups)
{
struct sockaddr_nl sa = {};
sa.nl_family = AF_NETLINK;
sa.nl_groups = groups;
struct iovec iov = { resp, resplen };
struct msghdr msg = { &sa, sizeof(sa), &iov, 1, nullptr, 0, 0 };
auto ret = recvmsg(fd, &msg, 0);
if (msg.msg_flags & MSG_TRUNC) {
return -1;
}
return ret;
}
int waybar::modules::Network::scanCb(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];
@ -40,23 +359,27 @@ int waybar::modules::Network::_scanCb(struct nl_msg *msg, void *data) {
bss_policy[NL80211_BSS_SIGNAL_UNSPEC].type = NLA_U8; bss_policy[NL80211_BSS_SIGNAL_UNSPEC].type = NLA_U8;
bss_policy[NL80211_BSS_STATUS].type = NLA_U32; bss_policy[NL80211_BSS_STATUS].type = NLA_U32;
if (nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), nullptr) < 0) if (nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), nullptr) < 0) {
return NL_SKIP; return NL_SKIP;
if (!tb[NL80211_ATTR_BSS]) }
if (tb[NL80211_ATTR_BSS] == nullptr) {
return NL_SKIP; return NL_SKIP;
if (nla_parse_nested(bss, NL80211_BSS_MAX, tb[NL80211_ATTR_BSS], bss_policy)) }
if (nla_parse_nested(bss, NL80211_BSS_MAX, tb[NL80211_ATTR_BSS], bss_policy) != 0) {
return NL_SKIP; return NL_SKIP;
if (!net->_associatedOrJoined(bss)) }
if (!net->associatedOrJoined(bss)) {
return NL_SKIP; return NL_SKIP;
net->_parseEssid(bss); }
net->_parseSignal(bss); net->parseEssid(bss);
// TODO: parse quality net->parseSignal(bss);
// TODO(someone): parse quality
return NL_SKIP; return NL_SKIP;
} }
void waybar::modules::Network::_parseEssid(struct nlattr **bss) void waybar::modules::Network::parseEssid(struct nlattr **bss)
{ {
_essid.clear(); essid_.clear();
if (bss[NL80211_BSS_INFORMATION_ELEMENTS] != nullptr) { if (bss[NL80211_BSS_INFORMATION_ELEMENTS] != nullptr) {
auto ies = auto ies =
static_cast<char*>(nla_data(bss[NL80211_BSS_INFORMATION_ELEMENTS])); static_cast<char*>(nla_data(bss[NL80211_BSS_INFORMATION_ELEMENTS]));
@ -69,29 +392,30 @@ void waybar::modules::Network::_parseEssid(struct nlattr **bss)
if (ies_len > hdr_len && ies_len > ies[1] + hdr_len) { if (ies_len > hdr_len && ies_len > ies[1] + hdr_len) {
auto essid_begin = ies + hdr_len; auto essid_begin = ies + hdr_len;
auto essid_end = essid_begin + ies[1]; auto essid_end = essid_begin + ies[1];
std::copy(essid_begin, essid_end, std::back_inserter(_essid)); std::copy(essid_begin, essid_end, std::back_inserter(essid_));
} }
} }
} }
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
_signalStrengthdBm = signal_strength_dbm_ =
static_cast<int>(nla_get_u32(bss[NL80211_BSS_SIGNAL_MBM])) / 100; static_cast<int>(nla_get_u32(bss[NL80211_BSS_SIGNAL_MBM])) / 100;
// WiFi-hardware usually operates in the range -90 to -20dBm. // WiFi-hardware usually operates in the range -90 to -20dBm.
const int hardwareMax = -20; const int hardwareMax = -20;
const int hardwareMin = -90; const int hardwareMin = -90;
_signalStrength = ((double)(_signalStrengthdBm - hardwareMin) signal_strength_ = ((signal_strength_dbm_ - hardwareMin)
/ (double)(hardwareMax - hardwareMin)) * 100; / double{hardwareMax - hardwareMin}) * 100;
} }
} }
bool waybar::modules::Network::_associatedOrJoined(struct nlattr** bss) bool waybar::modules::Network::associatedOrJoined(struct nlattr** bss)
{ {
if (!bss[NL80211_BSS_STATUS]) if (bss[NL80211_BSS_STATUS] == nullptr) {
return false; return false;
}
auto status = nla_get_u32(bss[NL80211_BSS_STATUS]); auto status = nla_get_u32(bss[NL80211_BSS_STATUS]);
switch (status) { switch (status) {
case NL80211_BSS_STATUS_ASSOCIATED: case NL80211_BSS_STATUS_ASSOCIATED:
@ -103,37 +427,19 @@ bool waybar::modules::Network::_associatedOrJoined(struct nlattr** bss)
} }
} }
auto waybar::modules::Network::_getInfo() -> void auto waybar::modules::Network::getInfo() -> void
{ {
struct nl_sock *sk = nl_socket_alloc(); getInterfaceAddress();
if (genl_connect(sk) != 0) { struct nl_msg* nl_msg = nlmsg_alloc();
nl_socket_free(sk); if (nl_msg == nullptr) {
nlmsg_free(nl_msg);
return; return;
} }
if (nl_socket_modify_cb(sk, NL_CB_VALID, NL_CB_CUSTOM, _scanCb, this) < 0) { if (genlmsg_put(nl_msg, NL_AUTO_PORT, NL_AUTO_SEQ, nl80211_id_, 0, NLM_F_DUMP,
nl_socket_free(sk); NL80211_CMD_GET_SCAN, 0) == nullptr
|| nla_put_u32(nl_msg, NL80211_ATTR_IFINDEX, ifid_) < 0) {
nlmsg_free(nl_msg);
return; return;
} }
const int nl80211_id = genl_ctrl_resolve(sk, "nl80211"); nl_send_sync(sk_, nl_msg);
if (nl80211_id < 0) {
nl_socket_free(sk);
return;
}
struct nl_msg *msg = nlmsg_alloc();
if (!msg) {
nl_socket_free(sk);
return;
}
if (!genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, nl80211_id, 0,
NLM_F_DUMP, NL80211_CMD_GET_SCAN, 0) ||
nla_put_u32(msg, NL80211_ATTR_IFINDEX, _ifid) < 0) {
nlmsg_free(msg);
return;
}
nl_send_sync(sk, msg);
nl_socket_free(sk);
}
waybar::modules::Network::operator Gtk::Widget &() {
return _label;
} }

View File

@ -1,39 +1,67 @@
#include "modules/pulseaudio.hpp" #include "modules/pulseaudio.hpp"
waybar::modules::Pulseaudio::Pulseaudio(Json::Value config) waybar::modules::Pulseaudio::Pulseaudio(const std::string& id, const Json::Value &config)
: _config(config), _mainloop(nullptr), _mainloop_api(nullptr), : ALabel(config, "{volume}%"),
_context(nullptr), _sinkIdx(0), _volume(0), _muted(false) mainloop_(nullptr),
{ mainloop_api_(nullptr),
_label.set_name("pulseaudio"); context_(nullptr),
_mainloop = pa_threaded_mainloop_new(); sink_idx_(0),
if (!_mainloop) volume_(0),
muted_(false),
scrolling_(false) {
label_.set_name("pulseaudio");
if (!id.empty()) {
label_.get_style_context()->add_class(id);
}
mainloop_ = pa_threaded_mainloop_new();
if (mainloop_ == nullptr) {
throw std::runtime_error("pa_mainloop_new() failed."); throw std::runtime_error("pa_mainloop_new() failed.");
pa_threaded_mainloop_lock(_mainloop); }
_mainloop_api = pa_threaded_mainloop_get_api(_mainloop); pa_threaded_mainloop_lock(mainloop_);
_context = pa_context_new(_mainloop_api, "waybar"); mainloop_api_ = pa_threaded_mainloop_get_api(mainloop_);
if (!_context) context_ = pa_context_new(mainloop_api_, "waybar");
if (context_ == nullptr) {
throw std::runtime_error("pa_context_new() failed."); throw std::runtime_error("pa_context_new() failed.");
if (pa_context_connect(_context, NULL, PA_CONTEXT_NOAUTOSPAWN, NULL) < 0) }
throw std::runtime_error(fmt::format("pa_context_connect() failed: {}", if (pa_context_connect(context_, nullptr, PA_CONTEXT_NOAUTOSPAWN,
pa_strerror(pa_context_errno(_context)))); nullptr) < 0) {
pa_context_set_state_callback(_context, _contextStateCb, this); auto err = fmt::format("pa_context_connect() failed: {}",
if (pa_threaded_mainloop_start(_mainloop) < 0) pa_strerror(pa_context_errno(context_)));
throw std::runtime_error(err);
}
pa_context_set_state_callback(context_, contextStateCb, this);
if (pa_threaded_mainloop_start(mainloop_) < 0) {
throw std::runtime_error("pa_mainloop_run() failed."); throw std::runtime_error("pa_mainloop_run() failed.");
pa_threaded_mainloop_unlock(_mainloop); }
}; pa_threaded_mainloop_unlock(mainloop_);
void waybar::modules::Pulseaudio::_contextStateCb(pa_context *c, void *data) // define the pulse scroll events only when no user provided
// events are configured
if (!config["on-scroll-up"].isString() &&
!config["on-scroll-down"].isString()) {
event_box_.add_events(Gdk::SCROLL_MASK);
event_box_.signal_scroll_event().connect(
sigc::mem_fun(*this, &Pulseaudio::handleScroll));
}
}
waybar::modules::Pulseaudio::~Pulseaudio() {
mainloop_api_->quit(mainloop_api_, 0);
pa_threaded_mainloop_stop(mainloop_);
pa_threaded_mainloop_free(mainloop_);
}
void waybar::modules::Pulseaudio::contextStateCb(pa_context *c, void *data)
{ {
auto pa = static_cast<waybar::modules::Pulseaudio *>(data); auto pa = static_cast<waybar::modules::Pulseaudio *>(data);
switch (pa_context_get_state(c)) { switch (pa_context_get_state(c)) {
case PA_CONTEXT_TERMINATED: case PA_CONTEXT_TERMINATED:
pa->_mainloop_api->quit(pa->_mainloop_api, 0); pa->mainloop_api_->quit(pa->mainloop_api_, 0);
break; break;
case PA_CONTEXT_READY: case PA_CONTEXT_READY:
pa_context_get_server_info(c, _serverInfoCb, data); pa_context_get_server_info(c, serverInfoCb, data);
pa_context_set_subscribe_callback(c, _subscribeCb, data); pa_context_set_subscribe_callback(c, subscribeCb, data);
pa_context_subscribe(c, PA_SUBSCRIPTION_MASK_SINK, nullptr, pa_context_subscribe(c, PA_SUBSCRIPTION_MASK_SINK, nullptr, nullptr);
nullptr);
break; break;
case PA_CONTEXT_CONNECTING: case PA_CONTEXT_CONNECTING:
case PA_CONTEXT_AUTHORIZING: case PA_CONTEXT_AUTHORIZING:
@ -41,46 +69,97 @@ void waybar::modules::Pulseaudio::_contextStateCb(pa_context *c, void *data)
break; break;
case PA_CONTEXT_FAILED: case PA_CONTEXT_FAILED:
default: default:
pa->_mainloop_api->quit(pa->_mainloop_api, 1); pa->mainloop_api_->quit(pa->mainloop_api_, 1);
break; break;
} }
} }
bool waybar::modules::Pulseaudio::handleScroll(GdkEventScroll *e) {
// Avoid concurrent scroll event
bool direction_up = false;
uint16_t change = config_["scroll-step"].isUInt() ? config_["scroll-step"].asUInt() * 100 : 100;
pa_cvolume pa_volume = pa_volume_;
if (scrolling_) {
return false;
}
scrolling_ = true;
if (e->direction == GDK_SCROLL_UP) {
direction_up = true;
}
if (e->direction == GDK_SCROLL_DOWN) {
direction_up = false;
}
if (e->direction == GDK_SCROLL_SMOOTH) {
gdouble delta_x, delta_y;
gdk_event_get_scroll_deltas(reinterpret_cast<const GdkEvent *>(e), &delta_x,
&delta_y);
if (delta_y < 0) {
direction_up = true;
} else if (delta_y > 0) {
direction_up = false;
}
}
if (direction_up) {
if (volume_ + 1 < 100) pa_cvolume_inc(&pa_volume, change);
} else {
if (volume_ - 1 > 0) pa_cvolume_dec(&pa_volume, change);
}
pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume,
volumeModifyCb, this);
return true;
}
/* /*
* Called when an event we subscribed to occurs. * Called when an event we subscribed to occurs.
*/ */
void waybar::modules::Pulseaudio::_subscribeCb(pa_context *context, void waybar::modules::Pulseaudio::subscribeCb(pa_context* context,
pa_subscription_event_type_t type, uint32_t idx, void* data) pa_subscription_event_type_t type, uint32_t idx, void* data)
{ {
unsigned facility = type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK; unsigned facility = type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK;
pa_operation *op = nullptr;
switch (facility) { switch (facility) {
case PA_SUBSCRIPTION_EVENT_SINK: case PA_SUBSCRIPTION_EVENT_SINK:
pa_context_get_sink_info_by_index(context, idx, _sinkInfoCb, data); pa_context_get_sink_info_by_index(context, idx, sinkInfoCb, data);
break; break;
default: default:
assert(0);
break; break;
} }
if (op) }
pa_operation_unref(op);
/*
* 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) { if (i != nullptr) {
auto pa = static_cast<waybar::modules::Pulseaudio *>(data); auto pa = static_cast<waybar::modules::Pulseaudio *>(data);
float volume = (float)pa_cvolume_avg(&(i->volume)) / (float)PA_VOLUME_NORM; pa->pa_volume_ = i->volume;
pa->_sinkIdx = i->index; float volume = static_cast<float>(pa_cvolume_avg(&(pa->pa_volume_))) /
pa->_volume = volume * 100.0f; float{PA_VOLUME_NORM};
pa->_muted = i->mute; pa->sink_idx_ = i->index;
pa->_desc = i->description; pa->volume_ = std::round(volume * 100.0f);
Glib::signal_idle().connect_once(sigc::mem_fun(*pa, &Pulseaudio::update)); pa->muted_ = i->mute != 0;
pa->desc_ = i->description;
pa->port_name_ = i->active_port ? i->active_port->name : "Unknown";
pa->dp.emit();
} }
} }
@ -88,36 +167,54 @@ void waybar::modules::Pulseaudio::_sinkInfoCb(pa_context *context,
* Called when the requested information on the server is ready. This is * Called when the requested information on the server is ready. This is
* used to find the default PulseAudio sink. * used to find the default PulseAudio sink.
*/ */
void waybar::modules::Pulseaudio::_serverInfoCb(pa_context *context, void waybar::modules::Pulseaudio::serverInfoCb(pa_context *context,
const pa_server_info *i, void *data) const pa_server_info *i, void *data)
{ {
pa_context_get_sink_info_by_name(context, i->default_sink_name, _sinkInfoCb, pa_context_get_sink_info_by_name(context, i->default_sink_name,
data); sinkInfoCb, data);
}
const std::string waybar::modules::Pulseaudio::getPortIcon() const
{
std::vector<std::string> ports = {
"headphones",
"speaker",
"hdmi",
"headset",
"handsfree",
"portable",
"car",
"hifi",
"phone",
};
for (auto const& port : ports) {
if (port_name_.find(port) != std::string::npos) {
return port;
}
}
return "";
} }
auto waybar::modules::Pulseaudio::update() -> void auto waybar::modules::Pulseaudio::update() -> void
{ {
auto format = _config["format"] ? _config["format"].asString() : "{volume}%"; auto format = format_;
if (_muted) { if (muted_) {
format = format =
_config["format-muted"] ? _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 } else if (port_name_.find("a2dp_sink") != std::string::npos) {
_label.get_style_context()->remove_class("muted"); format = config_["format-bluetooth"].isString()
_label.set_label(fmt::format(format, ? config_["format-bluetooth"].asString() : format;
fmt::arg("volume", _volume), label_.get_style_context()->add_class("bluetooth");
fmt::arg("icon", _getIcon(_volume)))); } else {
_label.set_tooltip_text(_desc); label_.get_style_context()->remove_class("muted");
label_.get_style_context()->add_class("bluetooth");
} }
label_.set_markup(
std::string waybar::modules::Pulseaudio::_getIcon(uint16_t percentage) fmt::format(format, fmt::arg("volume", volume_),
{ fmt::arg("icon", getIcon(volume_, getPortIcon()))));
if (!_config["format-icons"] || !_config["format-icons"].isArray()) return ""; label_.set_tooltip_text(desc_);
auto size = _config["format-icons"].size(); if (scrolling_) {
auto idx = std::clamp(percentage / (100 / size), 0U, size - 1); scrolling_ = false;
return _config["format-icons"][idx].asString();
} }
waybar::modules::Pulseaudio::operator Gtk::Widget &() {
return _label;
} }

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

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

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

@ -0,0 +1,265 @@
#include "modules/sni/item.hpp"
#include <iostream>
waybar::modules::SNI::Item::Item(std::string bn, std::string op, const Json::Value& config)
: bus_name(bn), object_path(op), icon_size(16), effective_icon_size(0)
{
if (config["icon-size"].isUInt()) {
icon_size = config["icon-size"].asUInt();
}
event_box.add(image);
event_box.add_events(Gdk::BUTTON_PRESS_MASK);
event_box.signal_button_press_event().connect(
sigc::mem_fun(*this, &Item::handleClick));
cancellable_ = g_cancellable_new();
sn_item_proxy_new_for_bus(
G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_NONE, bus_name.c_str(),
object_path.c_str(), cancellable_, &Item::proxyReady, this);
}
void waybar::modules::SNI::Item::proxyReady(GObject *obj, GAsyncResult *res,
gpointer data) {
GError *error = nullptr;
SnItem *proxy = sn_item_proxy_new_for_bus_finish(res, &error);
if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
g_error_free(error);
return;
}
auto item = static_cast<SNI::Item *>(data);
item->proxy_ = proxy;
if (error) {
std::cerr << error->message << std::endl;
g_error_free(error);
return;
}
auto conn = g_dbus_proxy_get_connection(G_DBUS_PROXY(proxy));
g_dbus_connection_call(conn, item->bus_name.c_str(),
item->object_path.c_str(), "org.freedesktop.DBus.Properties", "GetAll",
g_variant_new("(s)", "org.kde.StatusNotifierItem"),
G_VARIANT_TYPE("(a{sv})"), G_DBUS_CALL_FLAGS_NONE, -1, item->cancellable_,
&Item::getAll, data);
}
void waybar::modules::SNI::Item::getAll(GObject *obj, GAsyncResult *res,
gpointer data) {
GError *error = nullptr;
auto conn = G_DBUS_CONNECTION(obj);
GVariant *properties = g_dbus_connection_call_finish(conn, res, &error);
if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
g_error_free(error);
return;
}
auto item = static_cast<SNI::Item *>(data);
if (error) {
std::cerr << error->message << std::endl;
g_error_free(error);
return;
}
GVariantIter *it = nullptr;
g_variant_get(properties, "(a{sv})", &it);
gchar *key;
GVariant *value;
while (g_variant_iter_next(it, "{sv}", &key, &value)) {
if (g_strcmp0(key, "Category") == 0) {
item->category = g_variant_dup_string(value, nullptr);
} else if (g_strcmp0(key, "Id") == 0) {
item->id = g_variant_dup_string(value, nullptr);
} else if (g_strcmp0(key, "Title") == 0) {
item->title = g_variant_dup_string(value, nullptr);
} else if (g_strcmp0(key, "Status") == 0) {
item->status = g_variant_dup_string(value, nullptr);
} else if (g_strcmp0(key, "WindowId") == 0) {
item->window_id = g_variant_get_int32(value);
} else if (g_strcmp0(key, "IconName") == 0) {
item->icon_name = g_variant_dup_string(value, nullptr);
} else if (g_strcmp0(key, "IconPixmap") == 0) {
item->icon_pixmap = item->extractPixBuf(value);
} else if (g_strcmp0(key, "OverlayIconName") == 0) {
item->overlay_icon_name = g_variant_dup_string(value, nullptr);
} else if (g_strcmp0(key, "OverlayIconPixmap") == 0) {
// TODO: overlay_icon_pixmap
} else if (g_strcmp0(key, "AttentionIconName") == 0) {
item->attention_icon_name = g_variant_dup_string(value, nullptr);
} else if (g_strcmp0(key, "AttentionIconPixmap") == 0) {
// TODO: attention_icon_pixmap
} else if (g_strcmp0(key, "AttentionMovieName") == 0) {
item->attention_movie_name = g_variant_dup_string(value, nullptr);
} else if (g_strcmp0(key, "ToolTip") == 0) {
// TODO: tooltip
} else if (g_strcmp0(key, "IconThemePath") == 0) {
item->icon_theme_path = g_variant_dup_string(value, nullptr);
} else if (g_strcmp0(key, "Menu") == 0) {
item->menu = g_variant_dup_string(value, nullptr);
} else if (g_strcmp0(key, "ItemIsMenu") == 0) {
item->item_is_menu = g_variant_get_boolean(value);
}
g_variant_unref(value);
g_free(key);
}
g_variant_iter_free(it);
g_variant_unref(properties);
if (item->id.empty() || item->category.empty() || item->status.empty()) {
std::cerr << "Invalid Status Notifier Item: " + item->bus_name + "," +
item->object_path << std::endl;
return;
}
if (!item->icon_theme_path.empty()) {
GtkIconTheme *icon_theme = gtk_icon_theme_get_default();
gtk_icon_theme_append_search_path(icon_theme,
item->icon_theme_path.c_str());
}
item->updateImage();
// item->event_box.set_tooltip_text(item->title);
// TODO: handle change
}
Glib::RefPtr<Gdk::Pixbuf>
waybar::modules::SNI::Item::extractPixBuf(GVariant *variant) {
GVariantIter *it;
g_variant_get(variant, "a(iiay)", &it);
if (it == nullptr) {
return Glib::RefPtr<Gdk::Pixbuf>{};
}
GVariant *val;
gint lwidth = 0;
gint lheight = 0;
gint width;
gint height;
guchar *array = nullptr;
while (g_variant_iter_loop(it, "(ii@ay)", &width, &height, &val)) {
if (width > 0 && height > 0 && val != nullptr &&
width * height > lwidth * lheight) {
auto size = g_variant_get_size(val);
/* Sanity check */
if (size == 4U * width * height) {
/* Find the largest image */
gconstpointer data = g_variant_get_data(val);
if (data != nullptr) {
if (array != nullptr) {
g_free(array);
}
array = static_cast<guchar *>(g_memdup(data, size));
lwidth = width;
lheight = height;
}
}
}
}
g_variant_iter_free(it);
if (array != nullptr) {
/* argb to rgba */
for (uint32_t i = 0; i < 4U * lwidth * lheight; i += 4) {
guchar alpha = array[i];
array[i] = array[i + 1];
array[i + 1] = array[i + 2];
array[i + 2] = array[i + 3];
array[i + 3] = alpha;
}
return Gdk::Pixbuf::create_from_data(array, Gdk::Colorspace::COLORSPACE_RGB,
true, 8, lwidth, lheight, 4 * lwidth);
}
return Glib::RefPtr<Gdk::Pixbuf>{};
}
void waybar::modules::SNI::Item::updateImage()
{
image.set_from_icon_name("image-missing", Gtk::ICON_SIZE_MENU);
image.set_pixel_size(icon_size);
if (!icon_name.empty()) {
try {
// Try to find icons specified by path and filename
#ifdef FILESYSTEM_EXPERIMENTAL
if (std::experimental::filesystem::exists(icon_name)) {
#else
if (std::filesystem::exists(icon_name)) {
#endif
auto pixbuf = Gdk::Pixbuf::create_from_file(icon_name);
if (pixbuf->gobj() != nullptr) {
// An icon specified by path and filename may be the wrong size for
// the tray
pixbuf = pixbuf->scale_simple(icon_size, icon_size,
Gdk::InterpType::INTERP_BILINEAR);
image.set(pixbuf);
}
} else {
image.set(getIconByName(icon_name, icon_size));
}
} catch (Glib::Error &e) {
std::cerr << "Exception: " << e.what() << std::endl;
}
} else if (icon_pixmap) {
// An icon extracted may be the wrong size for the tray
icon_pixmap = icon_pixmap->scale_simple(icon_size, icon_size,
Gdk::InterpType::INTERP_BILINEAR);
image.set(icon_pixmap);
}
}
Glib::RefPtr<Gdk::Pixbuf>
waybar::modules::SNI::Item::getIconByName(std::string name, int request_size) {
int tmp_size = 0;
Glib::RefPtr<Gtk::IconTheme> icon_theme = Gtk::IconTheme::get_default();
icon_theme->rescan_if_needed();
auto sizes = icon_theme->get_icon_sizes(name.c_str());
for (auto const &size : sizes) {
// -1 == scalable
if (size == request_size || size == -1) {
tmp_size = request_size;
break;
} else if (size < request_size || size > tmp_size) {
tmp_size = size;
}
}
if (tmp_size == 0) {
tmp_size = request_size;
}
return icon_theme->load_icon(name.c_str(), tmp_size,
Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE);
}
void waybar::modules::SNI::Item::onMenuDestroyed(Item *self)
{
self->gtk_menu = nullptr;
self->dbus_menu = nullptr;
}
bool waybar::modules::SNI::Item::makeMenu(GdkEventButton *const &ev)
{
if (gtk_menu == nullptr) {
if (!menu.empty()) {
dbus_menu = dbusmenu_gtkmenu_new(bus_name.data(), menu.data());
if (dbus_menu != nullptr) {
g_object_ref_sink(G_OBJECT(dbus_menu));
g_object_weak_ref(G_OBJECT(dbus_menu), (GWeakNotify)onMenuDestroyed, this);
gtk_menu = Glib::wrap(GTK_MENU(dbus_menu));
gtk_menu->attach_to_widget(event_box);
}
}
}
if (gtk_menu != nullptr) {
#if GTK_CHECK_VERSION(3, 22, 0)
gtk_menu->popup_at_pointer(reinterpret_cast<GdkEvent*>(ev));
#else
gtk_menu->popup(ev->button, ev->time);
#endif
return true;
}
return false;
}
bool waybar::modules::SNI::Item::handleClick(GdkEventButton *const &ev) {
if ((ev->button == 1 && item_is_menu) || ev->button == 3) {
if (!makeMenu(ev)) {
return sn_item_call_context_menu_sync(proxy_, ev->x, ev->y, nullptr, nullptr);
}
} else if (ev->button == 1) {
return sn_item_call_activate_sync(proxy_, ev->x, ev->y, nullptr, nullptr);
} else if (ev->button == 2) {
return sn_item_call_secondary_activate_sync(proxy_, ev->x, ev->y,
nullptr, nullptr);
}
return false;
}

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

@ -0,0 +1,40 @@
#include "modules/sni/tray.hpp"
#include <iostream>
waybar::modules::SNI::Tray::Tray(const std::string& id, const Json::Value &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()) {
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 {
if (box_.get_children().size() > 0) {
box_.set_name("tray");
box_.show_all();
} else {
box_.set_name("");
}
}
waybar::modules::SNI::Tray::operator Gtk::Widget &() {
return box_;
}

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

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

View File

@ -0,0 +1,132 @@
#include "modules/sway/ipc/client.hpp"
waybar::modules::sway::Ipc::Ipc()
: fd_(-1), fd_event_(-1)
{}
waybar::modules::sway::Ipc::~Ipc()
{
close(fd_);
close(fd_event_);
}
const std::string waybar::modules::sway::Ipc::getSocketPath() const
{
const char *env = getenv("SWAYSOCK");
if (env != nullptr) {
return std::string(env);
}
std::string str;
{
std::string str_buf;
FILE* in;
char buf[512] = { 0 };
if ((in = popen("sway --get-socketpath 2>/dev/null", "r")) == nullptr) {
throw std::runtime_error("Failed to get socket path");
}
while (fgets(buf, sizeof(buf), in) != nullptr) {
str_buf.append(buf, sizeof(buf));
}
pclose(in);
str = str_buf;
}
if (str.back() == '\n') {
str.pop_back();
}
return str;
}
int waybar::modules::sway::Ipc::open(const std::string& socketPath) const
{
struct sockaddr_un addr = {0};
int fd = -1;
if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
throw std::runtime_error("Unable to open Unix socket");
}
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, socketPath.c_str(), sizeof(addr.sun_path) - 1);
addr.sun_path[sizeof(addr.sun_path) - 1] = 0;
int l = sizeof(struct sockaddr_un);
if (::connect(fd, reinterpret_cast<struct sockaddr *>(&addr), l) == -1) {
throw std::runtime_error("Unable to connect to Sway");
}
return fd;
}
void waybar::modules::sway::Ipc::connect()
{
const std::string& socketPath = getSocketPath();
fd_ = open(socketPath);
fd_event_ = open(socketPath);
}
struct waybar::modules::sway::Ipc::ipc_response
waybar::modules::sway::Ipc::recv(int fd) const
{
std::string header;
header.reserve(ipc_header_size_);
auto data32 = reinterpret_cast<uint32_t *>(header.data() + ipc_magic_.size());
size_t total = 0;
while (total < ipc_header_size_) {
auto res = ::recv(fd, header.data() + total, ipc_header_size_ - total, 0);
if (res <= 0) {
throw std::runtime_error("Unable to receive IPC response");
}
total += res;
}
total = 0;
std::string payload;
payload.reserve(data32[0] + 1);
while (total < data32[0]) {
auto res = ::recv(fd, payload.data() + total, data32[0] - total, 0);
if (res < 0) {
throw std::runtime_error("Unable to receive IPC response");
}
total += res;
}
payload[data32[0]] = 0;
return { data32[0], data32[1], &payload.front() };
}
struct waybar::modules::sway::Ipc::ipc_response
waybar::modules::sway::Ipc::send(int fd, uint32_t type,
const std::string& payload) const
{
std::string header;
header.reserve(ipc_header_size_);
auto data32 = reinterpret_cast<uint32_t *>(header.data() + ipc_magic_.size());
memcpy(header.data(), ipc_magic_.c_str(), ipc_magic_.size());
data32[0] = payload.size();
data32[1] = type;
if (::send(fd, header.data(), ipc_header_size_, 0) == -1) {
throw std::runtime_error("Unable to send IPC header");
}
if (::send(fd, payload.c_str(), payload.size(), 0) == -1) {
throw std::runtime_error("Unable to send IPC payload");
}
return recv(fd);
}
struct waybar::modules::sway::Ipc::ipc_response
waybar::modules::sway::Ipc::sendCmd(uint32_t type,
const std::string& payload) const
{
return send(fd_, type, payload);
}
void waybar::modules::sway::Ipc::subscribe(const std::string& payload) const
{
auto res = send(fd_event_, IPC_SUBSCRIBE, payload);
if (res.payload != "{\"success\": true}") {
throw std::runtime_error("Unable to subscribe ipc event");
}
}
struct waybar::modules::sway::Ipc::ipc_response
waybar::modules::sway::Ipc::handleEvent() const
{
return recv(fd_event_);
}

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

@ -0,0 +1,46 @@
#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_.connect();
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();
dp.emit();
}
else if ((parsed["change"]) == "default" ) {
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_));
label_.set_tooltip_text(mode_);
event_box_.show();
}
}

View File

@ -0,0 +1,80 @@
#include "modules/sway/window.hpp"
waybar::modules::sway::Window::Window(const std::string& id, const Bar &bar, const Json::Value& config)
: ALabel(config, "{}"), bar_(bar), windowId_(-1)
{
label_.set_name("window");
if (!id.empty()) {
label_.get_style_context()->add_class(id);
}
if (label_.get_max_width_chars() == -1) {
label_.set_hexpand(true);
label_.set_ellipsize(Pango::EllipsizeMode::ELLIPSIZE_END);
}
ipc_.connect();
ipc_.subscribe("[\"window\",\"workspace\"]");
getFocusedWindow();
// Launch worker
worker();
}
void waybar::modules::sway::Window::worker()
{
thread_ = [this] {
try {
auto res = ipc_.handleEvent();
auto parsed = parser_.parse(res.payload);
if ((parsed["change"] == "focus" || parsed["change"] == "title")
&& parsed["container"]["focused"].asBool()) {
window_ = Glib::Markup::escape_text(parsed["container"]["name"].asString());
windowId_ = parsed["container"]["id"].asInt();
dp.emit();
} else if ((parsed["change"] == "close"
&& parsed["container"]["focused"].asBool()
&& windowId_ == parsed["container"]["id"].asInt())
|| (parsed["change"] == "focus" && parsed["current"]["focus"].isArray()
&& parsed["current"]["focus"].empty())) {
window_.clear();
windowId_ = -1;
dp.emit();
}
} catch (const std::exception& e) {
std::cerr << "Window: " << e.what() << std::endl;
}
};
}
auto waybar::modules::sway::Window::update() -> void
{
label_.set_markup(fmt::format(format_, window_));
label_.set_tooltip_text(window_);
}
std::tuple<int, std::string> waybar::modules::sway::Window::getFocusedNode(
Json::Value nodes)
{
for (auto const& node : nodes) {
if (node["focused"].asBool() && node["type"] == "con") {
return { node["id"].asInt(), node["name"].asString() };
}
auto [id, name] = getFocusedNode(node["nodes"]);
if (id > -1 && !name.empty()) {
return { id, name };
}
}
return { -1, std::string() };
}
void waybar::modules::sway::Window::getFocusedWindow()
{
try {
auto res = ipc_.sendCmd(IPC_GET_TREE);
auto parsed = parser_.parse(res.payload);
auto [id, name] = getFocusedNode(parsed["nodes"]);
windowId_ = id;
window_ = name;
Glib::signal_idle().connect_once(sigc::mem_fun(*this, &Window::update));
} catch (const std::exception &e) {
std::cerr << e.what() << std::endl;
}
}

View File

@ -0,0 +1,237 @@
#include "modules/sway/workspaces.hpp"
waybar::modules::sway::Workspaces::Workspaces(const std::string& id, const Bar& bar,
const Json::Value& config)
: bar_(bar), config_(config), scrolling_(false)
{
box_.set_name("workspaces");
if (!id.empty()) {
box_.get_style_context()->add_class(id);
}
ipc_.connect();
ipc_.subscribe("[ \"workspace\" ]");
// Launch worker
worker();
}
void waybar::modules::sway::Workspaces::worker()
{
thread_ = [this] {
try {
// Wait for the name of the output
if (!config_["all-outputs"].asBool() && bar_.output_name.empty()) {
while (bar_.output_name.empty()) {
thread_.sleep_for(chrono::milliseconds(150));
}
} else if (thread_.isRunnging() && !workspaces_.empty()) {
ipc_.handleEvent();
}
{
std::lock_guard<std::mutex> lock(mutex_);
auto res = ipc_.sendCmd(IPC_GET_WORKSPACES);
workspaces_ = parser_.parse(res.payload);
}
dp.emit();
} catch (const std::exception& e) {
std::cerr << "Workspaces: " << e.what() << std::endl;
}
};
}
auto waybar::modules::sway::Workspaces::update() -> void
{
bool needReorder = false;
std::lock_guard<std::mutex> lock(mutex_);
for (auto it = buttons_.begin(); it != buttons_.end();) {
auto ws = std::find_if(workspaces_.begin(), workspaces_.end(),
[it](auto node) -> bool { return node["name"].asString() == it->first; });
if (ws == workspaces_.end() ||
(!config_["all-outputs"].asBool() &&
(*ws)["output"].asString() != bar_.output_name)) {
it = buttons_.erase(it);
needReorder = true;
} else {
++it;
}
}
for (auto const& node : workspaces_) {
if (!config_["all-outputs"].asBool()
&& bar_.output_name != node["output"].asString()) {
continue;
}
auto it = buttons_.find(node["name"].asString());
if (it == buttons_.end()) {
addWorkspace(node);
needReorder = true;
} else {
auto &button = it->second;
if (node["focused"].asBool()) {
button.get_style_context()->add_class("focused");
} else {
button.get_style_context()->remove_class("focused");
}
if (node["visible"].asBool()) {
button.get_style_context()->add_class("visible");
} else {
button.get_style_context()->remove_class("visible");
}
if (node["urgent"].asBool()) {
button.get_style_context()->add_class("urgent");
} else {
button.get_style_context()->remove_class("urgent");
}
if (needReorder) {
box_.reorder_child(button, node["num"].asInt());
}
auto icon = getIcon(node["name"].asString(), node);
if (config_["format"].isString()) {
auto format = config_["format"].asString();
button.set_label(fmt::format(format, fmt::arg("icon", icon),
fmt::arg("name", node["name"].asString()),
fmt::arg("index", node["num"].asString())));
} else {
button.set_label(icon);
}
button.show();
}
}
if (scrolling_) {
scrolling_ = false;
}
}
void waybar::modules::sway::Workspaces::addWorkspace(Json::Value node)
{
auto icon = getIcon(node["name"].asString(), node);
auto format = config_["format"].isString()
? fmt::format(config_["format"].asString(), fmt::arg("icon", icon),
fmt::arg("name", node["name"].asString()),
fmt::arg("index", node["num"].asString()))
: icon;
auto pair = buttons_.emplace(node["name"].asString(), format);
auto &button = pair.first->second;
box_.pack_start(button, false, false, 0);
button.set_relief(Gtk::RELIEF_NONE);
button.signal_clicked().connect([this, pair] {
try {
std::lock_guard<std::mutex> lock(mutex_);
auto cmd = fmt::format("workspace \"{}\"", pair.first->first);
ipc_.sendCmd(IPC_COMMAND, cmd);
} catch (const std::exception& e) {
std::cerr << e.what() << std::endl;
}
});
button.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);
if (!config_["disable-scroll"].asBool()) {
button.signal_scroll_event()
.connect(sigc::mem_fun(*this, &Workspaces::handleScroll));
}
box_.reorder_child(button, node["num"].asInt());
if (node["focused"].asBool()) {
button.get_style_context()->add_class("focused");
}
if (node["visible"].asBool()) {
button.get_style_context()->add_class("visible");
}
if (node["urgent"].asBool()) {
button.get_style_context()->add_class("urgent");
}
button.show();
}
std::string waybar::modules::sway::Workspaces::getIcon(std::string name,
Json::Value node)
{
std::vector<std::string> keys = {
name, "urgent", "focused", "visible", "default"};
for (auto const& key : keys) {
if (key == "focused" || key == "visible" || key == "urgent") {
if (config_["format-icons"][key].isString() && node[key].asBool()) {
return config_["format-icons"][key].asString();
}
} else if (config_["format-icons"][key].isString()) {
return config_["format-icons"][key].asString();
}
}
return name;
}
bool waybar::modules::sway::Workspaces::handleScroll(GdkEventScroll *e)
{
// Avoid concurrent scroll event
if (scrolling_) {
return false;
}
scrolling_ = true;
std::string name;
uint16_t idx = 0;
{
std::lock_guard<std::mutex> lock(mutex_);
for (; idx < workspaces_.size(); idx += 1) {
if (workspaces_[idx]["focused"].asBool()) {
name = workspaces_[idx]["name"].asString();
break;
}
}
}
if (name.empty()) {
scrolling_ = false;
return false;
}
if (e->direction == GDK_SCROLL_UP) {
name = getNextWorkspace();
}
if (e->direction == GDK_SCROLL_DOWN) {
name = getPrevWorkspace();
}
if (e->direction == GDK_SCROLL_SMOOTH) {
gdouble delta_x, delta_y;
gdk_event_get_scroll_deltas(reinterpret_cast<const GdkEvent *>(e),
&delta_x, &delta_y);
if (delta_y < 0) {
name = getNextWorkspace();
} else if (delta_y > 0) {
name = getPrevWorkspace();
}
}
if (!name.empty()) {
std::lock_guard<std::mutex> lock(mutex_);
if (name == workspaces_[idx]["name"].asString()) {
scrolling_ = false;
return false;
}
ipc_.sendCmd(IPC_COMMAND, fmt::format("workspace \"{}\"", name));
std::this_thread::sleep_for(std::chrono::milliseconds(150));
}
return true;
}
std::string waybar::modules::sway::Workspaces::getPrevWorkspace()
{
for (uint16_t i = 0; i != workspaces_.size(); i += 1) {
if (workspaces_[i]["focused"].asBool()) {
if (i > 0) {
return workspaces_[i - 1]["name"].asString();
}
return workspaces_[workspaces_.size() - 1]["name"].asString();
}
}
return "";
}
std::string waybar::modules::sway::Workspaces::getNextWorkspace()
{
for (uint16_t i = 0; i != workspaces_.size(); i += 1) {
if (workspaces_[i]["focused"].asBool()) {
if (i + 1U < workspaces_.size()) {
return workspaces_[i + 1]["name"].asString();
}
return workspaces_[0]["String"].asString();
}
}
return "";
}
waybar::modules::sway::Workspaces::operator Gtk::Widget &() {
return box_;
}

View File

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

10
subprojects/fmt.wrap Normal file
View File

@ -0,0 +1,10 @@
[wrap-file]
directory = fmt-5.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