Compare commits

...

440 Commits
0.9.2 ... 0.9.7

Author SHA1 Message Date
5300461c79 chore: v0.9.7 2021-04-15 21:17:54 +02:00
d0f60c47bf Merge pull request #1070 from jgmdev/cpumodulefix
[Module CPU] fix crash due to empty frequencies.
2021-04-15 21:17:00 +02:00
07f2470e36 Merge pull request #974 from kamushadenes/patch-1
Improve Pulseaudio sink/source separation
2021-04-15 21:09:35 +02:00
f8f1e791a3 [Module CPU] fix crash due to empty frequencies.
On some systems (eg: ARM) the supported frequencies of the CPU are not
properly reported by /proc/cpuinfo so if that fails try to retrieve them
from /sys/devices/system/cpu/cpufreq/policy[0-9]/cpuinfo_[max|min]_freq.
2021-04-15 14:30:29 -04:00
729a4fe37e chore: v0.9.6 2021-04-15 16:09:45 +02:00
97e4b53cf3 Merge pull request #1061 from akheron/multi-battery-status
Use the correct battery status when multiple batteries are present
2021-03-28 19:35:51 +02:00
c850212288 Use the correct battery status when multiple batteries are present 2021-03-28 20:07:35 +03:00
600afaf530 Merge pull request #1037 from Moonlight-Angel/cpu-frequency
Add cpu min/max/avg frequencies
2021-03-25 12:09:42 +01:00
c21dc681c9 Merge pull request #1052 from Logarithmus/fix-sway-language-manpage
meson.build: add missing waybar-sway-language manpage
2021-03-23 14:57:06 +01:00
f4ad5d36ec meson.build: add missing waybar-sway-language manpage 2021-03-23 16:51:08 +03:00
f627fe3a39 Merge pull request #1051 from lunik1/disk-css
Add default styling for disk module
2021-03-23 12:31:52 +01:00
7b6dc33824 Merge pull request #1056 from martin2250/patch-1
Add style for battery state "plugged"
2021-03-23 12:30:50 +01:00
b4ee994515 Add style for battery state "plugged" 2021-03-23 00:26:45 +01:00
b1dd62078f Merge pull request #1002 from nullobsi/master
add length limits for MPD module tags
2021-03-15 09:15:09 +01:00
bf3efdb89c Merge branch 'master' into master 2021-03-14 21:34:25 -07:00
354de5f13f style: add styling to disk module 2021-03-13 15:17:11 +00:00
b4ffb8af45 Merge pull request #1050 from cmovcc/cmovcc/load-values
Fix: CPU load values
2021-03-13 16:11:39 +01:00
a49b12b66b Fix CPU load values 2021-03-12 20:58:51 +01:00
1573e1eb97 change variable instead of substr(len) 2021-02-26 13:29:58 -08:00
9b9daaee6f Merge branch 'master' into master 2021-02-26 13:22:34 -08:00
99643ba2e6 Stub parseCpuFrequencies on *BSD platforms 2021-02-25 09:14:51 +01:00
08ea5ebe1f Add cpu frequency 2021-02-25 09:14:51 +01:00
cb1c7ea12c Merge pull request #1032 from matteodelabre/terminate-custom-on-exit
Terminate custom module scripts on exit
2021-02-23 09:40:05 +01:00
1026100c9d Merge pull request #1035 from alebastr/deferred-output-removal
fix: schedule output destruction on idle callback
2021-02-23 09:39:09 +01:00
943ba3a2da fix: schedule output destruction on idle callback
Defer destruction of bars for the output to the next iteration of the
event loop to avoid deleting objects referenced by currently executed
code.
2021-02-22 18:35:09 -08:00
bfa9f1e69b Merge branch 'master' into master 2021-02-22 09:34:58 -08:00
4d150e9340 Merge pull request #1036 from WhyNotHugo/systemd-reload
Configure systemd.service file to allow reloading
2021-02-19 15:21:15 +01:00
2019028688 Configure systemd.service file to allow reloading
This allows `systemctl --user reload waybar` to reload waybar's config
as expected.
2021-02-19 14:33:38 +01:00
b4728f2e1d Merge branch 'master' into master 2021-02-16 21:51:31 -08:00
d8706af2ea Terminate custom module scripts on exit
(Fixes #358.)

Subprocesses created for custom module scripts were previously left
running when the parent Waybar process exited. This patch sets the
parent-death signal of child processes (PR_SET_PDEATHSIG on Linux,
PROC_PDEATHSIG_CTL on FreeBSD) to SIGTERM.

Caveats:

* This uses Linux-specific or FreeBSD-specific calls. I don’t know if
  this project targets other systems?
* There is a possibility that Waybar exits after calling `fork()`, but
  before calling `prctl` to set the parent-death signal. In this case,
  the child will not receive the SIGTERM signal and will continue to
  run. I did not handle this case as I consider it quite unlikely, since
  module scripts are usually launched only when Waybar starts. Please
  let me know if you think it needs to be handled.

Testing:

* With `htop` open, run Waybar v0.9.5 with a custom module that has an
  `exec` script. Terminate the Waybar process and notice that the
  script’s subprocess stays alive and is now a child of the init
  process.
* Run Waybar with this patch and follow the same steps as above. Notice
  that this time the script’s subprocess terminates when the parent
  exits.
2021-02-12 21:14:46 +01:00
08e19602f7 Merge pull request #1015 from alebastr/rfkill-events
rfkill code refactoring
2021-02-11 10:20:13 +01:00
b12b500bfc Merge branch 'master' into master 2021-02-10 09:39:03 -08:00
e786ea601e fix(rfkill): handle EAGAIN correctly 2021-02-10 08:26:21 -08:00
36da8117c0 Merge pull request #1026 from euclio/idle-inhibit-fix
disable Idle Inhibitor module if unsupported
2021-02-10 10:32:09 +01:00
6d5afdaa5f fix(network): don't block the main thread on rfkill update
Moving rfkill to the main event loop had unexpected side-effects.
Notably, the network module mutex can block all the main thread events
for several seconds while the network worker thread is sleeping.

Instead of waiting for the mutex let's hope that the worker thread
succeeds and schedule timer thread wakeup just in case.
2021-02-09 21:27:22 -08:00
52dd3d2446 refactor(bluetooth): remove interval and timer thread
The timer thread was always reading the same value from Rfkill state.
2021-02-09 21:27:21 -08:00
ecc32ddd18 refactor(bluetooth): remove Bluetooth::status_
The string was always overwritten in `update()`; don't need to store
temporary value in the class.
2021-02-09 21:27:20 -08:00
38c29fc242 refactor(rfkill): poll rfkill events from Glib main loop
Open rfkill device only once per module.
Remove rfkill threads and use `Glib::signal_io` as a more efficient way
to poll the rfkill device.
Handle runtime errors from rfkill and stop polling of the device instead
of crashing waybar.
2021-02-09 21:27:19 -08:00
40f4dc9ecf fix(rfkill): accept events larger than v1 event size
Kernel 5.11 added one more field to the `struct rfkill_event` and broke
unnecessarily strict check in `rfkill.cpp`. According to `linux/rfkill.h`,
we must accept events at least as large as v1 event size and should be
prepared to get additional fields at the end of a v1 event structure.
2021-02-09 21:27:18 -08:00
95a6689077 disable Idle Inhibitor module if unsupported 2021-02-09 18:37:13 -05:00
c5f875dc5f Merge pull request #1027 from alebastr/output-fixes
Fix more issues with duplicated bars
2021-02-09 11:17:16 +01:00
89b5e819a3 fix(client): improve guard from repeated xdg_output.done events
Multiple .done events may arrive in batch. In this case libwayland would
queue xdg_output.destroy and dispatch all pending events, triggering
this callback several times for the same output.

Delete xdg_output pointer immediately on the first event and use the
value as a guard for reentering.
2021-02-08 23:25:58 -08:00
6585381230 fix(client): remove unnecessary wl_output_roundtrip
At this point we're not awaiting any protocol events and flushing
wayland queue makes little sense. As #1019 shows, it may be even harmful
as an extra roundtrip could process wl_output disappearance and delete
output object right from under our code.
2021-02-08 22:30:01 -08:00
f3ce7ff86c Merge pull request #1021 from jgmdev/taskbar-icons
[wlr/taskbar] Check StartupWMClass
2021-02-07 16:52:34 +01:00
e4a65c72dd Added missing 'if' space. 2021-02-07 04:27:16 -04:00
f14a73584f [wlr/taskbar] Added break when matching StartupWMClass is found. 2021-02-07 01:01:57 -04:00
fffb52dd93 [wlr/taskbar] Check StartupWMClass on list returned by g_desktop_app_info_searchi() 2021-02-07 00:50:52 -04:00
71f9ed3099 Merge pull request #1018 from jgmdev/taskbar-icons
[wlr/taskbar] Fix unhandled exception crash when icon name is a path.
2021-02-04 10:05:02 +01:00
e293b89f6b [wlr/taskbar] Removed unnecessary catch statement. 2021-02-04 04:57:08 -04:00
8a284e7c74 [wlr/taskbar] Declared load_icon_from_file() static. 2021-02-03 21:14:04 -04:00
22ed153004 [wlr/taskbar] Fix unhandled exception crash when icon name is a path. 2021-02-03 21:04:10 -04:00
ff9f09a24e Merge pull request #1014 from Moonlight-Angel/disable-auto-back-and-forth
Add a way to configure auto_back_and_forth on Sway workspaces
2021-02-03 09:57:05 +01:00
7eb2a6b709 Add a configuration entry to disable auto_back_and_forth on Sway workspaces 2021-02-02 21:58:26 +01:00
f2e9bb54f0 Merge pull request #1011 from jgmdev/taskbar-icons
[wlr/taskbar] More icon search improvements.
2021-02-02 09:03:35 +01:00
ac6667b1c9 [wlr/taskbar] More icon search improvements.
* Added ~/.local/share prefix to search in user defined apps.
* Add support for apps that don't properly set an id like pamac.
2021-02-02 01:03:28 -04:00
7d78a3aeef Merge pull request #1001 from max-k/master
[sway/window] Add app_id to usable fields in title
2021-02-01 22:23:49 +01:00
aa088721c3 Merge pull request #1008 from nullobsi/label-sizing
improve module sizing options
2021-02-01 22:22:33 +01:00
97f7050d7d Update man pages 2021-02-01 08:34:51 -08:00
e21be3382b Merge pull request #1007 from nullobsi/fullwidth-length
[calendar] CJK locale formatting
2021-02-01 11:15:30 +01:00
72cd753c02 align should use rotate property 2021-02-01 01:44:51 -08:00
c8d7b6fa92 rename fixed-length to min-length 2021-01-31 14:03:49 -08:00
8c70513a24 add common align config property to set text alignment
add fixed-length property to set the fixed width of the label
2021-01-31 13:58:41 -08:00
35062ceb99 Merge branch 'master' into master 2021-01-31 12:01:49 -08:00
f05afb5468 Merge branch 'master' into fullwidth-length 2021-01-31 11:58:12 -08:00
ecba117dc0 remove unnessecary logging 2021-01-31 11:56:25 -08:00
d2a1f41750 Use g_unichar_iswide to properly align calendar on CJK locales 2021-01-31 11:53:53 -08:00
be777b8525 Merge pull request #1006 from jgmdev/taskbar-icons
Improved wlr/taskbar icon search.
2021-01-31 20:47:23 +01:00
3881af4bbe Improved wlr/taskbar icon search. 2021-01-31 15:37:26 -04:00
933e0f5280 Merge pull request #1003 from joshuachp/patch-1
Update waybar-bluetooth.5.scd
2021-01-31 15:44:00 +01:00
149c1c2f1b Update waybar-bluetooth.5.scd
Remove the `status` from the `tooltip-format` example since it will
throw error. Related to #685
2021-01-31 11:37:41 +01:00
6cc3212605 add length limits for MPD module tags 2021-01-30 18:04:59 -08:00
e19aa1d43a [sway/window] Add app_id to usable fields in title 2021-01-30 01:41:45 +01:00
69a366dced Merge pull request #996 from martin2250/master
add power formatter to battery module
2021-01-24 23:33:12 +01:00
c9ef731fd0 Merge pull request #992 from alebastr/990-duplicated-bars-on-output-events
Fix duplicate bars on xdg_output property changes
2021-01-24 23:31:34 +01:00
cd97bdb30f document power formatter in battery module 2021-01-24 21:49:00 +01:00
3bcf390484 add power to battery formatter 2021-01-24 21:39:14 +01:00
7fa1c11833 fix(client): unsubscribe after receiving xdg_output.done event
Ignore any further xdg_output events. Name and description are constant
for the lifetime of wl_output in xdg-output-unstable-v1 version 2 and we
don't need other properties.

Fixes #990.
2021-01-21 08:35:38 -08:00
ab0f2c13af fix(client): attach styles only once
Gdk >= 3.10 has only one GdkScreen. No need to reattach styles on every
output change.
2021-01-21 08:32:44 -08:00
dc38640341 Merge branch 'master' into patch-1 2021-01-18 10:55:40 -03:00
66e5fda418 Merge pull request #964 from Ocisra/master
Add an option to use battery design capacity
2021-01-18 13:48:03 +01:00
e06d603154 Merge pull request #830 from Markaos/battery-custom-tooltip
Allow customization of battery module tooltip
2021-01-18 13:46:57 +01:00
392b0679c9 Merge branch 'master' into master 2021-01-18 12:39:25 +01:00
336cc9f336 . 2021-01-18 12:39:41 +01:00
0bd96f339e typo 2021-01-18 12:38:02 +01:00
ce0bf6269b battery: use timeTo as the default format name 2021-01-18 12:32:51 +01:00
fdaba72974 Merge branch 'master' into battery-custom-tooltip 2021-01-18 12:22:44 +01:00
a2d98ddde8 Merge pull request #842 from rdnetto/config-reloading
Implement support for reloading of config files.
2021-01-18 12:06:25 +01:00
51bfe9eaf6 Merge pull request #881 from olemartinorg/master
Support format-{state} for cpu/disk/memory
2021-01-18 12:02:59 +01:00
a25cf4d188 Merge pull request #891 from danieldg/temp-tooltip
Add "tooltip-format" to temperature module
2021-01-18 12:01:34 +01:00
ede1146ddc Merge pull request #903 from spk/simpleclock
Add simpleclock as fallback when hhdate is not available
2021-01-18 12:00:48 +01:00
b916ed3cae Merge pull request #980 from sjtio/master
add option 'tag-labels' to river/tags
2021-01-18 11:56:37 +01:00
9d5ce45f3b add option tag-labels to river/tags 2021-01-15 01:07:56 +00:00
a7941a00c5 fix missing parentheses 2021-01-12 19:10:34 -03:00
f4ffb21c8c improve sink/source separation
Add additional fields, namely `source_volume` and `source_desc`
Add `tooltip-format`, reverting to default behavior if not specified
Add additional CSS classes, namely `sink-muted` and `source-muted`
2021-01-12 18:51:44 -03:00
29cba22405 Merge pull request #969 from alebastr/wlr-taskbar-fixes
Fix issues in wlr/taskbar
2021-01-11 09:23:40 +01:00
b79301a5bd fix(wlr/taskbar): protocol error when reconnecting outputs
Destroy request is not specified for foreign toplevel manager and it
does not prevent the compositor from sending more events.
Libwayland would ignore events to a destroyed objects, but that could
indirectly cause a gap in the sequence of new object ids and trigger
error condition in the library.

With this commit waybar sends a `stop` request to notify the compositor
about the destruction of a toplevel manager. That fixes abnormal
termination of the bar with following errors:
```
(waybar:11791): Gdk-DEBUG: 20:04:19.778: not a valid new object id (4278190088), message toplevel(n)

Gdk-Message: 20:04:19.778: Error reading events from display: Invalid argument
```
2021-01-08 15:41:48 -08:00
ef9c3ef1cb fix(wlr/taskbar): fix wl_array out-of-bounds access
wl_array->size contains the number of bytes in the array instead of the
number of elements.
2021-01-08 15:28:29 -08:00
1f620828c2 Merge pull request #965 from ofwinterpassed/master
Fixing logic in getIcon
2021-01-03 19:55:33 +01:00
f20dbbbd74 Fixing logic in getIcon 2021-01-03 19:08:06 +01:00
00046d309d add an option to use battery design capacity as a reference for percentage informations 2021-01-03 15:25:19 +01:00
7b7edc9029 Merge pull request #949 from jbeich/ci
CI: add FreeBSD to Actions
2021-01-02 11:56:08 +01:00
bd208fcec6 Merge pull request #956 from AndreasBackx/feature/output-identifier
Added waybar_output.identifier support. #602
2021-01-02 11:52:14 +01:00
f233d27b78 Merge pull request #959 from dorgnarg/fix-section-css-classes
Fix for group module selectors when bar is vertical
2020-12-28 23:15:46 +01:00
42e8667773 Better way of doing it by just moving the original delcarations after everything's for sure been set 2020-12-28 13:44:16 -07:00
c0361e8546 A hopeful fix to the module section classes when the bar is vertical 2020-12-28 13:34:59 -07:00
3fbbbf8541 Removed redundant log line. 2020-12-25 23:31:29 +00:00
e5684c6127 Separated name and description setup and moved bar creation to done callback. 2020-12-25 23:03:01 +00:00
0233e0eeec Added waybar_output.identifier support.
Resolves #602.
2020-12-25 20:54:38 +00:00
7fbd3657e8 Merge pull request #955 from ilpianista/bugfix/revert-pipewire-pulse
Revert "Fix waybar-pulseaudio with pipewire-pulse"
2020-12-25 17:50:01 +01:00
005af7f7b7 Revert "Fix waybar-pulseaudio with pipewire-pulse"
This reverts commit 0d03c1d4da.
2020-12-25 17:37:21 +01:00
94f8f74f51 Merge pull request #953 from Alexays/revert-901-patch-1
Revert "Replace lowercase "k" with uppercase "K" to make it look more consistent"
2020-12-25 09:28:22 +01:00
f391186749 Revert "Replace lowercase "k" with uppercase "K" to make it look more consistent" 2020-12-25 09:28:05 +01:00
e4340a7536 CI: add FreeBSD to Actions 2020-12-23 20:40:44 +00:00
dd2792b204 Merge pull request #937 from ilpianista/bugfix/pipewire-pulse
Fix waybar-pulseaudio with pipewire-pulse
2020-12-23 21:39:40 +01:00
08ee5385ec Merge pull request #942 from danielrainer/patch-1
Fix typo in states man page
2020-12-23 21:37:47 +01:00
73eb517b86 Merge pull request #946 from narilth/sway-solo-css-fix
Fix Sway #waybar.solo CSS rule applying on split
2020-12-23 21:37:25 +01:00
4b29aef048 chore: v0.9.5 2020-12-23 21:33:40 +01:00
cb7baee045 Fixed compile error 2020-12-18 18:17:17 -05:00
85ca5027f4 Fix Sway #waybar.solo CSS rule applying on split
This error occurs because of an incorrect assumption that the size of
the list of nodes that contains the focused window is the number of
windows in a workspace.

The windows in a workspace are stored as a tree by Sway, rather than a
list, so the number of windows has to be found by counting the leaves of
a workspace tree.
2020-12-18 18:14:14 -05:00
50ecc97284 Fix typo 2020-12-12 23:21:17 +01:00
d382734698 Merge branch 'master' into bugfix/pipewire-pulse 2020-12-09 18:14:22 +01:00
f91dc7fb6e Merge pull request #935 from alebastr/ignore-emulated-scroll-events
fix(sway/workspaces): ignore emulated scroll events
2020-12-05 21:02:24 +01:00
0d03c1d4da Fix waybar-pulseaudio with pipewire-pulse 2020-12-04 23:51:10 +01:00
68b6136989 fix(sway/workspaces): ignore emulated scroll events
GDK Wayland backend can emit two events for mouse scroll: one is a
GDK_SCROLL_SMOOTH and the other one is an emulated scroll event with
direction. We only receive emulated events on a window, thus it is not
possible to handle these in a module and stop propagation.

Ignoring emulated events should be safe since those are duplicates of
smooth scroll events anyways.

Fixes #386
2020-12-04 01:09:42 -08:00
930bb4ba97 Merge pull request #934 from l3nkz/wlr-taskbar-updates
Update to the latest version of the foreign toplevel manager protocol
2020-12-04 09:37:07 +01:00
18f129a712 Spit out a warning when trying to set/unset fullscreen without server supporting it
Previously we only checked when connecting to the server whether it had
the minimum required version but didn't act accordingly in the various
functions that use the functionality of later versions. If there were a
server in the wild, that actually would not have this functionality,
there would have been a crash. Fix this by checking the version before
using the functionality and gracefully abort it.
2020-12-04 08:04:02 +01:00
bb60e68b9d Update to the latest version of the foreign toplevel manager protocol
There was an update the of the toplevel manager protocol. Unfortunately,
there are no new interesting updates with regard to the taskbar
implementation. Nonetheless, update the protocol xml files to the latest
version so that the implementation is up-to-date.

While being there, also change the debug warning that is shown when
there is a version mismatch between the server and client version of the
protocol.
2020-12-04 08:03:43 +01:00
a70468a2ea Merge pull request #932 from pedrocr/speedup-battery-updates
Don't update battery list on every update
2020-12-03 11:35:51 +01:00
09c89bcd20 Don't update battery list on every update
Speedup battery state update by only updating the battery list when we
get a CREATE/DELETE event in the directory or whenever we do a full
refresh on the interval.
2020-12-03 09:52:33 +00:00
cc365a8175 Merge pull request #923 from pedrocr/fix-battery-calculations
Simplify and improve battery state calculations
2020-12-02 14:35:56 +01:00
ff0d3292a4 Merge pull request #844 from tchebb/add-sway-module-margins
style: add 4px margins to window and workspaces modules
2020-12-02 14:34:45 +01:00
1fe0bcacc0 style: add 4px margins to window and workspaces modules
These modules, unlike others, have no horizontal margins by default.
This means that they'll appear uncomfortably close together in any
config that puts them side-by-side. In general, the default style should
make configs with any module ordering look good. Add the same 4px
horizontal margins that other module have to these.

To preserve the current default appearance, exempt the workspace module
from a margin on the appropriate side when it's the leftmost or
rightmost module on the bar.
2020-12-02 05:12:48 -08:00
f74c22e851 Merge pull request #930 from tchebb/fix-compiler-warnings
Fix a few compiler warnings
2020-12-01 09:09:02 +01:00
1ea662a08d Merge pull request #928 from tchebb/fix-uninitialized-value
clock: initialize cached date
2020-12-01 09:08:19 +01:00
881bb62f88 Merge pull request #927 from tchebb/log-gtk-tree
Add debug log message to print each bar's widget tree
2020-12-01 09:07:33 +01:00
f6ef8b41df Merge pull request #929 from tchebb/update-fmt
Update fmt subproject to 7.1.3
2020-12-01 09:06:55 +01:00
c1640ed16c Merge pull request #931 from jtheoof/jtheoof/fix/service
fix(systemd): restart when service fails
2020-12-01 09:05:23 +01:00
dd596a5c6c fix(systemd): restart when service fails
The current service doesn't play too nice with Sway when it is started
from [sway service](https://github.com/xdbob/sway-services).

Waybar is started before the system has a display.

```
Nov 30 22:11:23 ansan waybar[1352]: Unable to init server: Could not
connect: Connection refused
Nov 30 22:11:23 ansan waybar[1352]: cannot open display:
Nov 30 22:11:23 ansan systemd[1306]: waybar.service: Main process
exited, code=exited, status=1/FAILURE
Nov 30 22:11:23 ansan systemd[1306]: waybar.service: Failed with result
'exit-code'.
```

Restarting the service after the system has been initialized works nicely,
so this restart rule should do the trick without tinkering with the
target.
2020-11-30 23:24:30 -05:00
29f78e0426 Fix a few compiler warnings
There was one uninitialized value warning and two mismatched-sign
compare warnings. They both appear valid, the first occurring when MPD's
"format-stopped" contains {songPosition} or {queueLength} and the second
occurring when the clock's "timezones" array is more than 2 billion
items long (not likely, I admit). Fix both issues.
2020-11-30 18:07:22 -08:00
407bf27401 Update fmt subproject to 7.1.3
There is no particular change in this update that we require. However,
our previous version, 5.3.0, is nearly two years old, so it seems
prudent to pull in all the upstream fixes that have been made since
then.

New wrap file taken from https://wrapdb.mesonbuild.com/fmt and modified
to download from GitHub as per commit 99dde1aff8 ("Download patch
files from Github instead of wrapdb").
2020-11-30 17:35:02 -08:00
85df7ce2da Add debug log message to print each bar's widget tree
This is very useful when writing CSS that affects more than just a
single widget. Pass `-l debug` to enable debug logging and show this
information.

Example output:

    [2020-11-30 12:38:51.141] [debug] GTK widget tree:
    window#waybar.background.bottom.eDP-1.:dir(ltr)
      decoration:dir(ltr)
      box.horizontal:dir(ltr)
        box.horizontal.modules-left:dir(ltr)
          widget:dir(ltr)
            box#workspaces.horizontal:dir(ltr)
          widget:dir(ltr)
            label#mode:dir(ltr)
          widget:dir(ltr)
            label#window:dir(ltr)
        box.horizontal.modules-center:dir(ltr)
        box.horizontal.modules-right:dir(ltr)
          widget:dir(ltr)
            box#tray.horizontal:dir(ltr)
          widget:dir(ltr)
            label#idle_inhibitor:dir(ltr)
          widget:dir(ltr)
            label#pulseaudio:dir(ltr)
          widget:dir(ltr)
            label#network:dir(ltr)
          widget:dir(ltr)
            label#cpu:dir(ltr)
          widget:dir(ltr)
            label#memory:dir(ltr)
          widget:dir(ltr)
            label#temperature:dir(ltr)
          widget:dir(ltr)
            label#backlight:dir(ltr)
          widget:dir(ltr)
            label#battery:dir(ltr)
          widget:dir(ltr)
            label#clock:dir(ltr)
2020-11-30 17:23:02 -08:00
ad40511358 Update spdlog subproject to 1.8.1
Among other changes, this adds spdlog::should_log(), which lets us
easily determine whether a log message will be printed so that we can
avoid extra computation when unnecessary.

New wrap file taken from https://wrapdb.mesonbuild.com/spdlog and
modified to download from GitHub as per commit 99dde1aff8 ("Download
patch files from Github instead of wrapdb").
2020-11-30 17:11:33 -08:00
e8dbdee238 Make spdlog use the same version of fmt we use
spdlog bundles a version of fmt. However, we also depend on and use fmt
directly. If we don't tell spdlog not to use its bundled version, we end
up with two versions of fmt in our include path, since both libraries
are header-only, meaning any slight API mismatches will cause build
failures and undesired behavior.

We seem to have gotten lucky so far, but I ran into all sorts of issues
when I tried to update to a newer version of spdlog. This change
prevents them.
2020-11-30 17:04:19 -08:00
c784e8170e clock: initialize cached date
We are currently using this value once before it's initialized, since we
check it before we set it in Clock::calendar_text(). This was caught by
Valgrind, producing the following error:

    ==8962== Conditional jump or move depends on uninitialised value(s)
    ==8962==    at 0x138285: date::operator==(date::year_month_day const&, date::year_month_day const&) (date.h:2793)
    ==8962==    by 0x135F11: waybar::modules::Clock::calendar_text[abi:cxx11](waybar::modules::waybar_time const&) (clock.cpp:111)
    ==8962==    by 0x13587C: waybar::modules::Clock::update() (clock.cpp:62)
    ==8962==    by 0x156EFA: waybar::Bar::getModules(waybar::Factory const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)::{lambda()#1}::operator()() const (bar.cpp:577)
    ==8962==    by 0x157F39: sigc::adaptor_functor<waybar::Bar::getModules(waybar::Factory const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)::{lambda()#1}>::operator()() const (adaptor_trait.h:256)
    ==8962==    by 0x157D94: sigc::internal::slot_call0<waybar::Bar::getModules(waybar::Factory const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)::{lambda()#1}, void>::call_it(sigc::internal::slot_rep*) (slot.h:136)
    ==8962==    by 0x5177B21: Glib::DispatchNotifier::pipe_io_handler(Glib::IOCondition) (in /usr/lib/libglibmm-2.4.so.1.3.0)
    ==8962==    by 0x517DB5B: Glib::IOSource::dispatch(sigc::slot_base*) (in /usr/lib/libglibmm-2.4.so.1.3.0)
    ==8962==    by 0x5188B96: Glib::Source::dispatch_vfunc(_GSource*, int (*)(void*), void*) (in /usr/lib/libglibmm-2.4.so.1.3.0)
    ==8962==    by 0x5CBC913: g_main_context_dispatch (in /usr/lib/libglib-2.0.so.0.6600.2)
    ==8962==    by 0x5D107D0: ??? (in /usr/lib/libglib-2.0.so.0.6600.2)
    ==8962==    by 0x5CBB120: g_main_context_iteration (in /usr/lib/libglib-2.0.so.0.6600.2)

Initialize the value to prevent the error.
2020-11-30 12:49:48 -08:00
31a4aff1f8 Don't show battery estimate at 0
If we think we're done might as well not show 0h 0min as the estimate
and just not show anything.
2020-11-27 14:23:37 +00:00
89ca155c43 Support hotplugging of batteries
Refresh the list of batteries on update to handle hotplug correctly.
Probably fixes #490.
2020-11-27 13:56:04 +00:00
908fa2c6c2 Make the battery full-at go to 100%
full-at was capped at the value instead of allowing the battery to show
100% when you were at the full-at value. Uncapping makes more sense as
it makes the full-at value the new 100% and the scale goes down from
there. Whereas before the battery would stay at the full-at value until
it went down enough which is unrealistic.
2020-11-27 11:02:11 +00:00
f45d582957 Always mark battery as full at 100%
Since we're now clamping at 100% and rounding, mark as full at that
point. Some batteries will stay in charging state for a long time while
stuck at the same charge level. Just mark them as full to not be
confusing.
2020-11-27 11:02:11 +00:00
eb3f4216d4 Show battery state as rounded number
Round the battery charge state so that 99.9% shows as 100%.
2020-11-27 11:02:11 +00:00
e0cdcb6e30 Handle charging above 100% gracefully
When calibrating a battery it's possible to go above 100%. Handle that
gracefully by just presenting the battery as full and 100%.
2020-11-27 11:02:11 +00:00
a7056f7cce Calculate battery state from just energy values
The energy values are all that's needed to calculate the battery state.
Using other values for the total capacity results in broken results in
some cases. This matches the output of TLP and i3status, while also
being more straightforward.
2020-11-27 11:02:11 +00:00
14a6cec6d1 Merge pull request #924 from alebastr/build-fixes
Build fixes
2020-11-27 09:01:50 +01:00
3b576ae12d Add "tooltip-format" to temperature module 2020-11-26 20:23:19 -05:00
2695985da0 fix: compilation error with gcc 11
../src/modules/network.cpp:22:6: error: ‘optional’ in namespace ‘std’ does not name a template type
   22 | std::optional<unsigned long long> read_netstat(std::string_view category, std::string_view key) {
      |      ^~~~~~~~
../src/modules/network.cpp:7:1: note: ‘std::optional’ is defined in header ‘<optional>’; did you forget to ‘#include <optional>’?
    6 | #include "util/format.hpp"
  +++ |+#include <optional>
    7 | #ifdef WANT_RFKILL
2020-11-26 15:42:44 -08:00
85e00b2aab fix: build fails with meson < 0.53.0
meson.build:12:0: ERROR: Module "fs" does not exist

Fixes #909
2020-11-26 15:40:24 -08:00
05b12602d4 fix: don't check against nullptr 2020-11-26 15:16:55 +01:00
374d5ae5a1 fix: check get_icon return non nullpt 2020-11-26 15:10:33 +01:00
fd11711673 Merge pull request #920 from Arnaudv6/patch-2
update actions on taskbar man page
2020-11-22 13:09:28 +01:00
8282385074 update actions on taskbar man page 2020-11-22 13:06:46 +01:00
ef7638d45a Merge pull request #918 from Arnaudv6/patch-1
add minimize-raise() action
2020-11-22 11:08:16 +01:00
d8dafa7ecc add minimize-raise() action 2020-11-18 20:12:07 +01:00
faacd76f62 Merge pull request #892 from alebastr/layer-surfaces-v3
Refactor layer surface implementations (again)
2020-11-02 10:17:57 +01:00
c21e0f6cf3 Merge pull request #906 from JordanL2/master
Fix for syncing idle inhibitor across outputs
2020-11-02 10:16:24 +01:00
9785a89013 Making active a bool 2020-11-01 18:25:41 +00:00
b015836e7b Ensure style class is removed from all IdleInhibitor instances by moving it to update(). 2020-11-01 18:17:51 +00:00
a9dae931c7 Renaming idle_inhibitor_modules and idle_inhibitor_status to shorter, more convenient names. 2020-11-01 17:14:05 +00:00
071cb86b45 Moving idle inhibitor shared stuff out of Client and into idle_inhibitor module as static members. 2020-11-01 17:09:48 +00:00
c6743988d3 Removing 'click_param' as it is no longer used. 2020-11-01 16:03:39 +00:00
bb33427f65 Making idle_inhibitor_ private and initialised in constructor, as it was before. 2020-11-01 13:38:58 +00:00
4889e655eb Since idle_inhibitor's have a surface, we should have one for each inhibitor module. Therefore, the status is stored on the Client, and all modules create or destroy their inhibitors depending on Client's idle_inhibitor_status. Also, when modules are destroyed they remove themselves from Client's idle_inhibitor_modules. 2020-11-01 13:33:28 +00:00
aa4fc3dd29 Idle inhibitor toggle no longer update all modules - a list of idle inhibitors is maintained on the Client. 2020-10-31 17:30:25 +00:00
188611a767 Merge from master 2020-10-31 16:37:20 +00:00
4872091442 Draft fix for syncing idle inhibitor across outputs. The idle_inhibitor surface has been moved to Client, all instances of idle inhibitor module now use one surface between them. Any time an idle inhibitor is clicked, currently it force updates ALL modules on all outputs, this needs work. 2020-10-31 16:31:27 +00:00
5600783151 Merge pull request #905 from JordanL2/master
Tooltips no longer completely break if a custom module updates too frequently
2020-10-31 14:04:41 +01:00
abe1fa5bd4 Custom module - only call label_.set_tooltip_markup if tooltip markup has actually changed - fixes tooltips not appearing at all if a custom module is updating too frequently. 2020-10-31 12:21:51 +00:00
96d965fe04 Add simpleclock as fallback when hhdate is not available
ref https://github.com/Alexays/Waybar/issues/668
2020-10-29 19:40:28 +01:00
9c566564e1 fix(bar): address some of RawSurfaceImpl resizing issues 2020-10-28 08:22:26 -07:00
fc5906dbd4 feat(bar): change layer to bottom when hidden
Invisible bar on a `top` layer would still intercept pointer events and
stop them from reaching windows below. Always changing the layer to
to `bottom` along with making bar invisible would prevent that.
2020-10-28 08:18:49 -07:00
fe3aeb36c5 refactor(bar): wrap layer_surface pointer in unique_ptr 2020-10-28 08:15:02 -07:00
591a417b7d fix(bar): rework surface commit calls for RawSurfaceImpl
wayland log shows that some changes were committed twice and some
weren't committed until the next redraw.
2020-10-28 08:14:57 -07:00
d4d35e6b2b chore(subprojects): update gtk-layer-shell to 0.4.0 2020-10-28 08:08:04 -07:00
f97de599dd refactor: header cleanup
Replace a couple of header includes with forward declarations.
2020-10-28 08:08:03 -07:00
f01996ae99 fix(bar): CamelCase SurfaceImpl method names 2020-10-28 08:07:40 -07:00
7735c80d0e refactor(bar): Split GLS and raw layer-shell implementations
Extract two surface implementations from the bar class: GLSSurfaceImpl
and RawSurfaceImpl. This change allowed to remove _all_ surface type
conditionals and significantly simplify the Bar code.

The change also applies PImpl pattern to the Bar, allowing to remove
some headers and fields from `bar.hpp`.
2020-10-28 07:53:37 -07:00
2b3d7be9cb feat(bar): let gtk-layer-shell manage exclusive zone
Previous attempts to use auto exclusive zone from gtk-layer-shell failed
because gls was expecting real booleans (`TRUE`/`FALSE`) as set_anchor
arguments. With that being fixed, gtk_layer_auto_exclusive_zone_enable
makes gls handle everything related to the bar resizing.

The only remaining purpose of onConfigureGLS is to log warnings and bar
size changes; gtk-layer-shell code path no longer needs saved width_ or
height_ values.
2020-10-28 07:53:32 -07:00
9fa2cc45d2 Merge pull request #901 from 1sixth/patch-1
Replace lowercase "k" with uppercase "K" to make it look more consistent
2020-10-28 13:53:15 +01:00
7a0c0ca613 replace lowercase "k" with uppercase "K"
Other units are all uppercased, so using an uppercased "K" makes it look more consistent (especially when {bandwidthUpBits} or something like that is used).
2020-10-28 19:39:50 +08:00
48a8dbece9 Merge pull request #898 from alebastr/fix-globals-versions
fix(wlr/taskbar): do not bind to unsupported protocol versions
2020-10-25 20:43:18 +01:00
67d54ef3d5 fix(wlr/taskbar): do not bind to unsupported protocol versions
It's not allowed to bind to a higher version of a wayland protocol than
supported by the client. Binding wlr-foreign-toplevel-manager-v1 v3 to
a generated code for v2 causes errors in libwayland due to a missing
handler for `zwlr_foreign_toplevel_handle_v1.parent` event.
2020-10-25 10:26:44 -07:00
d5fa20dd33 Merge pull request #895 from Flakebi/mpd
Fix various mpd bugs
2020-10-24 10:47:14 +02:00
be3f47b374 Fix various mpd bugs
- Add elapsedTime and totalTime to tooltip format arguments
- Catch format exceptions and print error
- Copy mpd connection error message before it gets freed
- Update display after connection to mpd was lost
2020-10-23 21:13:20 +02:00
9ea13e790d Merge pull request #879 from niktob560/sway-language-module
Feature: created sway language submodule
2020-10-20 12:43:26 +02:00
f13f49ccb5 Merge branch 'master' into sway-language-module 2020-10-20 12:22:22 +03:00
2cc00ab853 Merge branch 'sway-language-module' of https://github.com/niktob560/Waybar into sway-language-module 2020-10-20 12:21:38 +03:00
ed402d7583 feature: language submodule - created man page 2020-10-20 12:20:58 +03:00
acf990743e Merge pull request #888 from jbenden/mpd-password
mpd: support password protected MPD
2020-10-20 09:06:32 +02:00
587eb5fdb4 mpd: support password protected MPD
- Add MPD module option `password`, and document it.
- Add logic to send the password, directly after connecting to
  MPD.

Fixes: #576
Signed-off-by: Joseph Benden <joe@benden.us>
2020-10-19 11:54:36 -07:00
f151d435a8 Merge pull request #887 from jbenden/jbenden/mpd-module
mpd: revamped to event-driven, single-threaded v2
2020-10-19 09:05:54 +02:00
8f961ac397 mpd: revamped to event-driven, single-threaded
Fix MPD connection issues by converting/rewriting module into a
state-machine driven system. It is fully single-threaded and uses
events for transitioning between states. It supports all features
and functionality of the previous MPD module.

Signed-off-by: Joseph Benden <joe@benden.us>
2020-10-18 10:37:57 -07:00
cf5db8f663 Merge pull request #886 from Alexays/revert-877-jbenden/mpd-module
Revert "mpd: revamped to event-driven, single-threaded"
2020-10-18 10:45:44 +02:00
54beabb9dc Revert "mpd: revamped to event-driven, single-threaded" 2020-10-18 10:45:31 +02:00
41752ad5a2 Merge pull request #877 from jbenden/jbenden/mpd-module
mpd: revamped to event-driven, single-threaded
2020-10-18 10:39:12 +02:00
8349316fcd Merge branch 'master' into sway-language-module 2020-10-12 13:31:39 +03:00
4229e9b2ca Implemented format-{state} for cpu/disk/memory 2020-10-12 02:05:26 +02:00
5e86014443 Merge pull request #825 from Anakael/taskbar/remove-trim
Remove trim usage in format
2020-10-11 23:06:51 +02:00
d6381eeaff Merge branch 'master' into taskbar/remove-trim 2020-10-11 23:06:32 +02:00
45f7f9b07a Merge branch 'master' into config-reloading 2020-10-11 23:00:25 +02:00
6dc1892494 Merge pull request #880 from ChilloManiac/master
added module group selectors for styling
2020-10-11 22:58:09 +02:00
e9b2d275c8 added module group selectors for styling 2020-10-11 22:36:30 +02:00
7b78a29f3f Merge pull request #843 from tchebb/align-workspace-text
style(workspaces): align text with other modules
2020-10-11 14:40:45 +02:00
f270d317bb Merge pull request #869 from lrhel/master
Add format-icons for workspace's name entry in sway/workspaces module
2020-10-11 14:39:39 +02:00
cc3acf8102 feature: created sway language submodule; added styles & config part for a sway language submodule 2020-10-10 19:09:18 +03:00
21fdcf41c3 mpd: revamped to event-driven, single-threaded
Fix MPD connection issues by converting/rewriting module into a
state-machine driven system. It is fully single-threaded and uses
events for transitioning between states. It supports all features
and functionality of the previous MPD module.

Signed-off-by: Joseph Benden <joe@benden.us>
2020-10-08 16:43:22 -07:00
bcb63b8ccb Merge pull request #849 from ericonr/sndio
Add sndio module.
2020-10-04 20:51:00 +02:00
22e46ea6cc sndio: Add reconnection support. 2020-10-04 14:59:20 -03:00
aa625f5196 .travis.yml: add sndio to FreeBSD run.
Also add necessary environment variables and move to /latest, which has
sndio-1.7.0.
2020-10-04 02:54:57 -03:00
1f66b06f93 Dockerfiles/alpine: add sndio-dev. 2020-10-04 02:54:57 -03:00
e4427cb017 sndio: Add module.
- can control sndio: change volume, toggle mute
- appearance is somewhat dynamic: takes muted status into account
- uses polling inside sleeper thread to update values
- uses sioctl_* functions, requires sndio>=1.7.0.
2020-10-04 02:54:57 -03:00
93afe5113a Merge pull request #870 from minijackson/add-global-config-directory
Add back unprefixed global config directory
2020-10-02 20:17:06 +02:00
73681a30e5 man: add the prefixed path were config is loaded 2020-09-29 22:31:28 +02:00
e9b5be9adb fix: add global /etc/xdg/waybar back. fixes #714 2020-09-29 22:28:39 +02:00
83d679bf72 Add format-icons for workspace's name entry 2020-09-26 23:06:12 +00:00
b9f83dc77d Merge pull request #868 from danieldg/fix-backward-forward
Fix "on-click-backward" when "on-click-forward" is not present
2020-09-26 22:29:03 +02:00
7ba14c2097 Fix "on-click-backward" when "on-click-forward" is not present 2020-09-26 15:55:06 -04:00
3014082ba2 Merge pull request #862 from xenrox/disk-state
Add state to disk module
2020-09-23 14:18:12 +02:00
12016d35bb disk module: add state for percentage_used 2020-09-23 14:01:25 +02:00
6db795401a chore: v0.9.4 2020-09-21 12:18:42 +02:00
43ca8f7050 Merge pull request #861 from Alexays/fix-network
revert: restore eventfd
2020-09-21 12:10:01 +02:00
063c5a5ace Merge branch 'master' into fix-network 2020-09-21 12:09:54 +02:00
fcab026512 Merge pull request #850 from z3ntu/poll-h
Fix non-standard usage of <sys/poll.h>
2020-09-21 10:58:27 +02:00
95f505a457 revert: restore eventfd 2020-09-21 10:56:40 +02:00
577dc1fa00 Merge branch 'master' into poll-h 2020-09-21 10:40:46 +02:00
dc625490f8 Merge pull request #855 from Koffeinfriedhof/master
Added song position and queue length.
2020-09-14 09:36:37 +02:00
c651670222 Added song position and queue length. 2020-09-13 18:49:47 +02:00
459df4e0c9 Merge pull request #851 from tamirzb/exec-on-event
Add an "exec-on-event" config for the custom module
2020-09-08 09:18:32 +02:00
eb53fa8d0e Merge pull request #847 from MusiKid/memory-tooltip
Add format for memory tooltip
2020-09-07 12:15:26 +02:00
9e3e4368c7 custom: Add "exec-on-event" config
This config allows disabling the default behavior of re-executing the
script whenever an event that has a command set is triggered.

Fixes #841
2020-09-06 22:49:18 +03:00
98b6d7f283 Fix non-standard usage of <sys/poll.h>
Fixes the following build warning with musl libc:

In file included from ../src/util/rfkill.cpp:24:
/usr/include/sys/poll.h:1:2: warning: #warning redirecting incorrect #include <sys/poll.h> to <poll.h> [-Wcpp]
    1 | #warning redirecting incorrect #include <sys/poll.h> to <poll.h>
      |  ^~~~~~~
2020-09-06 21:48:42 +02:00
225a0eccdd Add support for memory tooltip 2020-09-02 14:35:39 +02:00
1b22e2b320 style(workspaces): align text with other modules
Currently, the bottom border on workspace buttons eats into the box size
and causes the text to sit higher than in other modules. This is ugly
when there are other modules (like the window title) right next to the
workspace module. To fix the issue, create the bottom border using an
inset box-shadow, which doesn't affect the box's content sizing.
2020-08-29 22:56:26 -07:00
44119db436 Merge pull request #840 from tamirzb/fix-restart-interval
Fix "restart-interval" in the custom module
2020-08-28 15:38:41 +02:00
943b6bc51b Implement support for reloading of config files.
Fixes #759.
2020-08-28 22:34:24 +10:00
4a22057138 Merge pull request #814 from danieldg/markup-fixes
Allow enabing pango markup in the taskbar string
2020-08-28 09:01:32 +02:00
ba78199dd1 custom: Fix "restart-interval"
This commit fixes the issue where the process would restart immediately
and the thread would sleep after the process has restarted, and not
before.

Fixes #621
2020-08-28 01:43:19 +03:00
9b51094743 Merge branch 'master' into battery-custom-tooltip 2020-08-20 13:52:25 +02:00
3c5fd4ba84 Merge pull request #828 from pedrocr/fix-multiline-labels
Prevent line breaks in ellipsized labels
2020-08-20 08:57:54 +02:00
ea722615c4 Allow enabing pango markup in the taskbar string
The fix for taskbar tooltips in 6a2d214b55 was incomplete: it causes the label
to contain escaped titles.  Use set_markup so that GTK decodes markup again,
but only if requested by the user (disabling markup is needed if using format
strings like "{title:.15}" to avoid terminating the string in the middle of an
XML entity).
2020-08-19 22:26:11 -04:00
6f7d7e645a Prevent line breaks in ellipsized labels
If a label is being ellipsized it doesn't make sense to allow it to use
line breaks to have multiple lines.

Fixes #827
2020-08-19 23:38:59 +01:00
8fb54f47ea battery: allow custom tooltip format 2020-08-19 23:13:03 +02:00
447fad34c7 Merge pull request #826 from Markaos/fix-typo
network: fix typo - update tooltip only when it changes
2020-08-19 11:02:49 +02:00
d263607b27 network: fix typo - update tooltip only when it changes 2020-08-18 23:09:35 +02:00
b54fb24745 Remove trim usage in format
Some clang-tidy fixes
2020-08-16 15:54:21 +03:00
0cf3b25d50 Merge pull request #824 from alebastr/gls-resize
Fix resizing of the bar with gtk-layer-shell
2020-08-15 10:59:12 +02:00
033f0b01b7 Fix rfkill condition 2020-08-15 10:36:15 +02:00
f4e15dd93d chore(subprojects): update gtk-layer-shell to 0.3.0
Fixes warning about xdg_wm_base.version mismatch.
Fixes potential crash when GTK does not expect wl_surface to be committed.
2020-08-14 23:53:44 -07:00
3663b9193d refactor(bar): separate GTK event handlers for gtk-layer-shell
Cleanly separate resizing logic for gtk-layer-shell and manually managed
layer surface code.
2020-08-14 22:47:57 -07:00
591eb2ea38 Merge pull request #821 from danieldg/new-clock-features
New clock features
2020-08-14 22:12:48 +02:00
69f5d19455 Merge pull request #823 from W-joe/rfkill_optional
Rfkill optional
2020-08-14 21:56:52 +02:00
4d775008df only return a bluetooth module from factory if the rfkill feature is enabled. 2020-08-14 20:59:30 +02:00
4565f7f8b9 only compile rfkill into the network module if the feature is enabled. 2020-08-14 20:58:48 +02:00
fdfb60c633 meson feature: make rfkill optional 2020-08-14 20:56:45 +02:00
62082bdb01 clock: scroll through multiple timezones 2020-08-13 18:53:18 -04:00
8cd6e13308 clock: allow custom formatting for today in calendar 2020-08-13 18:53:18 -04:00
31243cdc20 Merge pull request #820 from pedrocr/sway-workspaces-id
Add missing * in man page
2020-08-13 21:38:02 +02:00
0aa8c03bea Add missing * in man page 2020-08-13 20:11:55 +01:00
51a66d5919 Merge pull request #819 from pedrocr/sway-workspaces-id
Add IDs to sway workspace buttons for CSS styling
2020-08-13 13:17:30 +02:00
29fa74f621 Add IDs to sway workspace buttons for CSS styling
In case you want to style a specific workspace add IDs to the workspace
buttons. Styling is done by matching button#sway-workspace-${name}.
2020-08-13 11:36:19 +01:00
4c4691dc2e Merge pull request #817 from pedrocr/sway-workspaces-disable-click
Add option for no workspace switch on click
2020-08-12 20:45:04 +02:00
8f10c9056c Add option for no workspace switch on click
In sway/workspaces, just like disable-scroll turns on/off the ability to
change workspaces by scrolling the mouse add disable-click that turns
on/off the ability to change workspaces by clicking.
2020-08-12 11:38:48 +01:00
3bb04e82a5 Merge pull request #813 from xenrox/fmt-fix
Fix crash with fmt
2020-08-10 20:57:00 +02:00
9b41b95934 Fix crash with fmt 2020-08-10 20:53:29 +02:00
fb56f89ced Merge pull request #801 from maximbaz/systemd-use-standard-targets
systemd: use standard targets, update service type
2020-08-10 18:11:28 +02:00
56cbdd1403 Merge pull request #807 from nschloe/readme-ppa
add repo info to README
2020-08-08 16:35:09 +02:00
50e8f7ca86 add repo info to README 2020-08-08 13:17:56 +02:00
5ebd3594e4 Merge pull request #778 from excellentname/handle-sigchld
Handle SIGCHLD for exec/forkExec
2020-08-07 13:33:27 +02:00
d51adfe7bc systemd: use standard targets, update service type 2020-08-06 23:21:53 +02:00
a446cd692d Fix MPD, add missing while loop 2020-08-06 21:57:02 +02:00
4d6e20a96d Merge pull request #800 from NotAFile/patch-1
switch workspace on mouse-down to match swaybar
2020-08-06 16:14:57 +02:00
9ebfc54eb5 switch workspace on mouse-down to match swaybar
fixes #686
2020-08-06 16:04:30 +02:00
f5efb50871 Merge pull request #798 from danieldg/master
Minor string fixes to wlr/taskbar
2020-08-06 09:03:02 +02:00
4cd31cf3c3 Fix wlr/taskbar all-outputs config string 2020-08-05 20:39:12 -04:00
6a2d214b55 Fix titles containing & and other HTML entities 2020-08-05 20:39:12 -04:00
01c682c41e chore: v0.9.3 2020-08-05 23:27:06 +02:00
7d5da1df5e Merge pull request #797 from alebastr/update-subprojects
chore: update subprojects for date, gtk-layer-shell
2020-08-05 23:12:32 +02:00
2ca20f9050 chore: remove unwanted typo 2020-08-05 23:01:37 +02:00
99f3e37ccf chore: update archlinux, debian dockerfiles 2020-08-05 23:00:36 +02:00
dcc0201b45 chore(protocol): update wlr-layer-shell-unstable-v1 protocol.
Statically linked gtk-layer-shell would use layer-shell protocol object
file from waybar and print runtime warning if the version does not match
2020-08-05 09:46:21 -07:00
66aa3574d9 chore(subprojects): update gtk-layer-shell to 0.2.0
Fixes: #530, fixes #750
2020-08-05 09:46:10 -07:00
40d3f1c1fe chore(subprojects): update date to 3.0.0
Fixes #776, fixes #780
2020-08-05 09:46:01 -07:00
1e2ce29f57 Merge pull request #796 from ifreund/man-see-also
fix: add missing modules to list in waybar.5
2020-08-05 09:08:31 +02:00
74018167ff fix: add missing modules to list in waybar.5 2020-08-04 20:54:14 +02:00
0625bc7688 Merge pull request #793 from uggedal/non-fatal-missing-nl80211
network: make missing nl80211 non-fatal
2020-08-02 11:21:13 +02:00
fb8cda9d90 network: make missing nl80211 non-fatal
This will enable the networking module to be used for ethernet
interfaces on kernels without nl80211 support.

It should be reasonable to allow desktop systems without
wireless interfaces to run custom kenrel configs
without nl80211 compiled in.
2020-07-31 08:15:16 +02:00
a213aed4da Merge pull request #788 from JonasToth/fix_date_dependency
Fix 'date' dependency in meson
2020-07-28 21:05:20 +02:00
17967da676 Fix 'date' dependency in meson
The 'date' library dependency was incompletly imported with meson.
Only the target 'date::date' seemed to be caught by meson, but
'date::date-tz' not, which lead to build errors in gentoos sandbox
environment.

See this question, too:
https://stackoverflow.com/questions/62951569/meson-doesnt-link-library

Adding the modules in meson imports them all and the library builds
nice.
Note, that this did not happen with a standard checkout and local build
on my system, but only when creating an ebuild for the package.
2020-07-28 20:04:28 +02:00
1f6277e35b Merge pull request #783 from f0rki/duplicate-num-assignment
make waybar itself assign numbers to workspaces like sway
2020-07-27 11:44:39 +02:00
006850ea5e Changed helper function for workspace->num assignment to a static method of Workspaces class
and adapted comments/method name to be consistent with the rest
2020-07-27 10:56:49 +02:00
c3359dec1b Replace signal handler with signal handling thread 2020-07-25 21:02:59 +10:00
15fe73a252 duplicate the logic to assign numbers to workspaces from sway into waybar to handle perisstent workspaces 2020-07-22 16:13:24 +02:00
246f7bf555 Handle SIGCHLD for exec/forkExec
When forkExec is called it begins to ignore all SIGCHLD signals for
the rest of the progam's execution so that they are automatically
reaped. However, this means that subsequent waitpid calls in the exec
function will always fail. So instead handle SIGCHLD by reaping any
processes created by forkExec and ignoring all others so that they can be
handled directly by the exec function.
2020-07-21 12:36:48 +10:00
759602af64 Update mpd.cpp 2020-07-18 10:23:04 +02:00
273c2f3a8f Merge pull request #772 from jbeich/ci
CI: unbreak FreeBSD
2020-07-17 09:01:38 +02:00
a1f6e38624 CI: switch FreeBSD to /quarterly packages
devel/libffi on /latest was recently updated to 3.3, breaking ABI.
However, TravisCI has lang/python37 preinstalled likely from /quarterly.
Instead of calling `pkg upgrade -y` or `pkg upgrade -y python37` switch to
/quarterly until a newer dependency is required to justify /latest.

$ meson build -Dman-pages=enabled
[...]
ImportError: Shared object "libffi.so.6" not found, required by "_ctypes.so"
2020-07-16 17:34:55 +00:00
e3fdd6d94a fix: don't a warning for a width/height of 0 2020-07-12 11:57:47 +02:00
27f89bdcc3 Update .travis.yml 2020-07-08 22:02:47 +02:00
15623ec487 Merge pull request #766 from l3nkz/taskbar-changes
Support for multiple icon themes and XDG_DATA_DIRS
2020-07-08 21:54:27 +02:00
bc112eaae1 Convert icon-theme option to array|string
Use the config support of using arrays in its options instead of the
complicated and error-prone parsing of the string.
2020-07-07 11:22:08 +02:00
70e368a1c6 Refactor the lower_app_id logic
Move the lower_app_id lookup logic completely in the image_load_icon
method and use it also when looking up the icon from the desktop files
as well as icon themes.
2020-07-07 10:39:28 +02:00
7d9f6096fe Respect XDG_DATA_DIRS when looking for icons.
Use the base folders as defined in $XDG_DATA_DIRS and only fall back to
/usr/share and /usr/local/share if the environment variable does not
exist.
2020-07-05 13:16:41 +02:00
06ad35c42b Add support for multiple icon themes in the config
If there are multiple icon themes defined in the config option
'icon-theme' the module will try from left to right to find an icon.
The system default will always be added to this list.
2020-07-05 13:16:38 +02:00
b7e8275d90 Properly trim when splitting up the format string
Previously only single spaces would be trimmed and not multiple ones.
Now use a proper trim implementation for strings.
2020-07-05 13:13:34 +02:00
b9cf0a9c9a Merge pull request #763 from excellentname/manpage-typo
Fix mpd typo in man page
2020-07-03 13:50:33 +02:00
14bc842e77 Fix mpd typo in man page 2020-07-03 20:14:01 +10:00
7d9217b14a Merge pull request #761 from Anakael/pr/anakael/taskbar-icons
Change find icon priority and to_lower for app_id
2020-07-01 23:17:44 +02:00
8a13ed59bc Fix lower 2020-07-01 03:05:17 +03:00
43500a9983 Move lower to load_icon phase 2020-07-01 02:56:44 +03:00
8e6cbc1021 Change find icon priority and to_lower app_id 2020-07-01 02:10:22 +03:00
d35e92569d Merge pull request #755 from l3nkz/master
Fix and extend the wlr/taskbar example in the man page
2020-06-25 10:45:34 +02:00
b6067c7569 Fix and extend the wlr/taskbar example in the man page
The example incorrectly used 'on-middle-click' as option although it
should be 'on-click-middle'. Fix this and also add some other options.
2020-06-25 08:05:06 +02:00
8f684f703e Merge pull request #753 from spk/reproductible-builds
Remove date macro on version for reproducible builds
2020-06-24 18:22:36 +02:00
5662f80c43 Merge pull request #752 from spk/remove-if-meson
chore: always include sway module
2020-06-24 18:21:29 +02:00
5e044e5bba Remove date macro on version for reproducible builds
cf https://reproducible-builds.org/
2020-06-24 15:09:11 +02:00
732ce7a27c chore: always include sway module 2020-06-24 14:55:41 +02:00
fa6bf597cf Merge pull request #751 from l3nkz/tabkbar-fixes
Fixes for wlr/taskbar
2020-06-24 11:10:45 +02:00
b7a8e8e544 Fix format option for the taskbar module
When using additional format options in addition to {icon} the format is
separated into text before and text after the icon. Each of the texts is
displayed in a separate label one before and one after the image for the
icon.

The code updating the labels on changes used the wrong format strings
when updating the label after the icon.
2020-06-24 09:47:34 +02:00
7429d1f9cc Fix click events for the taskbar module
When only the option 'on-click-right' was set and no other 'on-click'
option than the taskbar module wouldn't register for click events and
hence those events were handled by the generic AModule::on-click code.
This code would try to start a shell with the specified command, which
wouldn't make any sense in this circumstances.

The taskbar code falsely checked for the 'on-click-left' option instead
for the 'on-click-right' when deciding to register for click events.
2020-06-24 09:36:28 +02:00
2c25153506 Merge pull request #747 from Jannusch/master
Only real batteries are counted as batteries
2020-06-22 11:31:37 +02:00
401ea05dd8 add check that all batteries from type battery 2020-06-16 12:30:21 +02:00
b23ba00cff Merge pull request #745 from ifreund/river-status
river/tags: add module
2020-06-14 12:21:37 +02:00
343a8bef22 river/tags: add module 2020-06-12 15:19:46 +02:00
181fde254f Merge pull request #736 from Xyene/sway-workspaces-no-auto-back-and-forth 2020-06-11 10:31:26 +02:00
0080feb9af sway/workspaces: make clicking on workspaces idempotent
Previously, clicking on the same workspace you were on would throw you
to another workspace if `workspace_auto_back_and_forth yes` was
specified in your sway config. This also fixes workspace output moving
misbehaving and doing the same.
2020-06-09 20:43:43 -04:00
91a2c4743e Merge pull request #737 from tbm/typos 2020-06-09 23:05:25 +02:00
a50c12b6ae Fix typos 2020-06-08 11:01:06 +08:00
4b2e6b54a7 Update FUNDING.yml 2020-06-01 13:29:41 +02:00
adaf843048 foreign-toplevel-manager based taskbar module (#692)
Co-authored-by: Alex <alexisr245@gmail.com>
2020-05-30 12:07:38 +02:00
e96a0bf799 refactor(custom): tooltip markup 2020-05-30 11:59:22 +02:00
6e7f22ac3a fix: cancel thread and fix window close 2020-05-27 09:10:38 +02:00
3b16946c25 fix(custom): avoid hide outside update 2020-05-25 09:21:04 +02:00
1d92d78de7 refactor: prefer spdlog 2020-05-24 22:14:17 +02:00
eb624c929d fix: fmt format 2020-05-24 22:08:10 +02:00
e72b153f87 fix: ignore gtk unknown options 2020-05-24 22:03:41 +02:00
7b4ded306b fix: restart-interval 2020-05-24 21:33:38 +02:00
49ae944d65 fix: check git root 2020-05-24 19:14:46 +02:00
2585360a3e Simplify .travis.yml (#719) 2020-05-24 19:05:44 +02:00
2ffc96d0b2 fix: hide box by default 2020-05-24 19:02:52 +02:00
9a123052a0 refactor: check conn 2020-05-24 18:47:50 +02:00
9b9d13ab0d feat: execNoRead 2020-05-24 18:27:21 +02:00
6ca4e14b29 Feat icons vector (#716) 2020-05-22 21:23:04 +02:00
6b32aca094 feat: debug cmd 2020-05-22 20:57:41 +02:00
b251c51936 fix: spdlog 2020-05-22 20:02:09 +02:00
a9b17681b0 Merge pull request #715 from Alexays/fix-clock 2020-05-22 19:10:11 +02:00
60bad8279e feat: add comment 2020-05-22 19:09:50 +02:00
a871dcaebe fix: type 2020-05-22 18:56:32 +02:00
cef5b27b48 fix: prefer default fmt date formatting unless timezone specified 2020-05-22 18:52:26 +02:00
c5bbedfabb Merge pull request #665 from BlueGone/layout-dockerfiles 2020-05-19 17:50:55 +02:00
e7367c75aa Merge pull request #425 from jbeich/freebsd 2020-05-19 13:55:47 +02:00
8d8c048924 CI: Switch FreeBSD to manual install due to pkg addon not working 2020-05-19 10:49:56 +00:00
4f646543fc CI: add FreeBSD job 2020-05-19 10:44:33 +00:00
1885ecc958 bluetooth: limit to Linux due to missing /dev/rfkill
../src/modules/bluetooth.cpp:3:10: fatal error: 'linux/rfkill.h' file not found
 #include <linux/rfkill.h>
          ^~~~~~~~~~~~~~~~
2020-05-19 10:44:31 +00:00
71b7b4e0f4 disk: properly calculate free/total size
On most Unix systems `f_blocks`, `f_bfree`, and `f_bavail` are
reported in units of `f_frsize`.
2020-05-19 10:43:45 +00:00
4e567d0483 cpu: port parseCpuinfo to BSDs 2020-05-19 10:43:42 +00:00
c4f7cdeec4 memory: port parseMeminfo to BSDs 2020-05-19 10:42:21 +00:00
c844d7ac2e tray: drop std::filesystem dependency 2020-05-19 10:37:27 +00:00
496e782544 battery: limit to Linux due to /sys/class/power_supply
../include/modules/battery.hpp:9:10: fatal error: 'sys/inotify.h' file not found
 #include <sys/inotify.h>
          ^~~~~~~~~~~~~~~
2020-05-19 10:37:27 +00:00
cfd7577e1b backlight: require libepoll on non-Linux
../src/modules/backlight.cpp:9:10: fatal error: 'sys/epoll.h' file not found
 #include <sys/epoll.h>
          ^~~~~~~~~~~~~
2020-05-19 10:37:27 +00:00
d5df185ac6 cpu: make getCpuLoad more portable
../include/modules/cpu.hpp:4:10: fatal error: 'sys/sysinfo.h' file not found
 #include <sys/sysinfo.h>
          ^~~~~~~~~~~~~~~
2020-05-19 10:37:27 +00:00
c94ef092ff build: drop -Dout in favor of --prefix
$ meson --prefix=/tmp/foo _build
$ ninja install -C _build
[49/50] Installing files.
Installing waybar to /tmp/foo/bin
Traceback (most recent call last):
  File "/usr/lib/python3.6/site-packages/mesonbuild/mesonmain.py", line 127, in run
    return options.run_func(options)
  File "/usr/lib/python3.6/site-packages/mesonbuild/minstall.py", line 514, in run
    installer.do_install(datafilename)
  File "/usr/lib/python3.6/site-packages/mesonbuild/minstall.py", line 346, in do_install
    self.install_data(d)
  File "/usr/lib/python3.6/site-packages/mesonbuild/minstall.py", line 375, in install_data
    d.dirmaker.makedirs(outdir, exist_ok=True)
  File "/usr/lib/python3.6/site-packages/mesonbuild/minstall.py", line 55, in makedirs
    os.makedirs(path, exist_ok=exist_ok)
  File "/usr/lib/python3.6/os.py", line 210, in makedirs
    makedirs(head, mode, exist_ok)
  File "/usr/lib/python3.6/os.py", line 220, in makedirs
    mkdir(name, mode)
PermissionError: [Errno 13] Permission denied: '/etc/xdg'
FAILED: meson-install
2020-05-19 10:37:27 +00:00
74db69dcb7 build: drop libinput as it was never used 2020-05-19 10:37:27 +00:00
fe2dd1e843 build: handle systems where libc++ is default
ld: error: unable to find library -lstdc++fs
ld: error: unable to find library -lc++abi
2020-05-19 10:37:27 +00:00
1dc557456e Add missing includes for libc++
In file included from ../src/modules/custom.cpp:1:
In file included from ../include/modules/custom.hpp:7:
../include/util/command.hpp:15:25: error: implicit instantiation of undefined template 'std::__1::array<char, 128>'
  std::array<char, 128> buffer = {0};
                        ^
../src/modules/pulseaudio.cpp:175:41: error: implicit instantiation of undefined template 'std::__1::array<std::__1::basic_string<char>, 9>'
static const std::array<std::string, 9> ports = {
                                        ^
/usr/include/c++/v1/__tuple:223:64: note: template is declared here
template <class _Tp, size_t _Size> struct _LIBCPP_TEMPLATE_VIS array;
                                                               ^
In file included from ../src/factory.cpp:1:
In file included from ../include/factory.hpp:8:
../include/modules/sway/workspaces.hpp:39:8: error: no template named 'unordered_map' in namespace 'std'
  std::unordered_map<std::string, Gtk::Button> buttons_;
  ~~~~~^
../src/factory.cpp:20:14: error: cannot initialize return object of type 'waybar::AModule *' with an rvalue of type 'waybar::modules::sway::Workspaces *'
      return new waybar::modules::sway::Workspaces(id, bar_, config_[name]);
             ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2020-05-19 10:37:27 +00:00
45deb2472c fix: use strcmp 2020-05-19 12:14:59 +02:00
8fbaf06cbe refactor(mpd): prefer debug over warn 2020-05-16 09:29:37 +02:00
08dce576bd refactor(mpd): prefer debug over info 2020-05-16 09:28:30 +02:00
4b28518cd5 Merge pull request #701 from josteink/patch-2
Confirm build-instructions for Ubuntu 20.04 too.
2020-05-07 08:58:58 +02:00
c9ee528880 Confirm build-instructions for Ubuntu 20.04 too. 2020-05-07 07:01:20 +02:00
0e869e3aab Merge pull request #696 from f0rki/sort_by_sway_num
sway/workspaces: sort by the "num" property provided by sway
2020-05-06 12:38:12 +02:00
6fe764540c sway/workspaces: sort by the "num" property provided by sway, configurable whether numeric workspace names come first
Sway provides the workspace "num" property which is an integer number of
the workspace, i.e., workspace "3" -> 3 and also "3dev" -> "3". This
commit uses this property to sort the workspaces, which makes sense when
persistent workspaces or all-output is specified. This commit also adds
a new configuration option, whether the numeric workspaces come in front
or after workspaces that have non-numeric name.
2020-05-05 09:15:20 +02:00
81abd9bb67 Merge pull request #693 from f0rki/current_output
sway/workspaces added current_output CSS class to buttons.
2020-05-04 17:36:36 +02:00
c602d38c8e sway/workspaces added current_output CSS class to buttons.
All workspace buttons that are visible on the same output as the current waybar can be styled with the `current_output` css class.

This is really only useful in combination with the `"all-outputs":
true`. Then the workspaces that are on the current output can be styled
differently than the workspace on other outputs, while all are visible
in the waybar.
2020-05-04 16:16:09 +02:00
c45adddd8e Merge pull request #673 from maximbaz/patch-1
Add missing dependencies to README
2020-05-02 21:01:45 +02:00
25648ed9c0 Merge branch 'master' into patch-1 2020-05-02 21:01:31 +02:00
c8daa48e66 Add build dependencies 2020-05-02 13:06:15 +02:00
6a95179456 Merge pull request #684 from DiegoGuidaF/fix_bluetoothman
Build bluetooth module man page
2020-05-01 20:35:56 +02:00
ea4dec96e6 Build bluetooth module man page 2020-04-28 17:40:44 +02:00
44ed61b2c3 Merge pull request #680 from akobel/master
Fix round to 0 or 1 in capacity computation with given full-at
2020-04-27 08:47:22 +02:00
64f10f8f69 Merge pull request #683 from phosit/master
Add kelvin-scale
2020-04-27 08:46:00 +02:00
7b18bfd1a7 Add kelvin in man 2020-04-25 18:57:56 +02:00
6e946bf872 Add kelvin-scale 2020-04-25 18:44:48 +02:00
e01a081f2f fix(brightness): amd brightness 2020-04-21 09:11:56 +02:00
df0d34dbd4 Fix round to 0 or 1 in capacity computation with given full-at 2020-04-21 00:58:17 +02:00
b18262f6d0 Merge pull request #677 from mastertinner/master
Make bluetooth example valid
2020-04-20 15:27:49 +02:00
d30b775d25 Make bluetooth example valid 2020-04-20 15:23:03 +02:00
82c3cccd72 fix(typo): man, remove trailing dot 2020-04-20 14:04:02 +02:00
1814b3b593 chore(arch): update Dockerfile 2020-04-19 19:52:03 +02:00
ff7a28274f chore(debian): update Dockerfile 2020-04-19 19:19:21 +02:00
8909086b58 Add wayland-protocols 2020-04-19 02:46:21 +02:00
a851dd1198 Add missing dependencies to README 2020-04-19 02:30:08 +02:00
9817955fef Merge pull request #596 from vesim987/output-exclusion
Add a way to exclude specific output
2020-04-17 23:58:25 +02:00
774d8ffdba Merge branch 'master' into output-exclusion 2020-04-17 23:42:58 +02:00
4a80874da9 Update client.cpp 2020-04-17 23:41:32 +02:00
4e19ec93dc Update README.md 2020-04-17 09:09:30 +02:00
976d3332d4 Merge pull request #568 from marcplustwo/master
Add rfkill status to network module and new bluetooth module
2020-04-17 09:09:07 +02:00
2d02ae5e97 Merge branch 'master' into master 2020-04-16 14:43:10 +02:00
967001d9d3 Merge pull request #667 from DanySpin97/memory-missing-header
fix(memory): add missing unordered_map include
2020-04-13 20:48:21 +02:00
09ec40e38d fix(memory): add missing unordered_map include 2020-04-13 18:02:50 +02:00
345c7da384 chore: update fedora base 2020-04-13 17:07:16 +02:00
9350595158 fix(layout): fix opensuse dockerfile pugixml dep 2020-04-13 09:50:58 +02:00
a0d5826cbc fix(layout): fix alpine dockerfile pugixml dep 2020-04-13 09:39:48 +02:00
7859bf2c60 Merge pull request #402 from 0xdec/on-update
feat(modules): call user on-update if configured
2020-04-12 18:49:00 +02:00
acc3ae6e62 refactor(man): add missing : 2020-04-12 18:41:44 +02:00
d1c4897f31 feat: update man 2020-04-12 18:38:51 +02:00
4a7dd400fe Merge branch 'master' into on-update 2020-04-12 18:32:19 +02:00
687c50dc13 refactor: remove old stuff 2020-04-12 18:31:07 +02:00
b40cdcb5bd refactor: call parent update 2020-04-12 18:30:21 +02:00
f34163a065 Merge pull request #9 from Alexays/master
Changes
2020-04-06 12:20:31 +02:00
8a5c3af949 Merge pull request #8 from Alexays/master
Merge Alexays:master into marcplustwo:master
2020-04-05 16:13:56 +02:00
4eb462426d Merge branch 'master' into output-exclusion 2020-02-28 07:53:20 +01:00
1209022492 Return true when outputs are not specified 2020-02-28 07:54:00 +01:00
bb88038c17 Dont exclude outputs in arrays 2020-02-27 21:49:09 +01:00
dd7d78cd60 changes requested 2020-02-23 23:09:05 +01:00
9abe1e2790 Merge branch 'master' into master 2020-02-23 23:00:09 +01:00
7bebfebe5f Merge branch 'master' into output-exclusion 2020-02-20 22:17:13 +01:00
9525663014 Update src/client.cpp
Co-Authored-By: Alex <alexisr245@gmail.com>
2020-02-18 22:40:21 +01:00
3ff1f28533 Update man page 2020-02-18 22:24:59 +01:00
4fcf06a164 Add a way to exclude specific output 2020-02-18 22:24:59 +01:00
6ae9f436a9 add copyright notice for rfkill util 2020-01-30 00:25:37 +01:00
58eb8ad11f Merge branch 'master' of github.com:marcplustwo/Waybar 2020-01-26 05:35:34 +01:00
c045288ce4 add man page for bluetooth, fix bluetooth race-condition 2020-01-26 05:34:31 +01:00
f9618d30f3 Merge pull request #7 from Alexays/master
Merge latest changes from upstream
2020-01-23 17:30:33 +01:00
e3bf6b968c bluetooth module handles rfkill events instantly 2020-01-23 17:17:29 +01:00
d85f0e1060 Merge pull request #3 from marcplustwo/addbluetoothmodule
Add bluetooth module
2020-01-22 11:44:25 +01:00
89cb9673d4 bluetooth module working 2020-01-22 11:37:47 +01:00
f0dbd8b78d properly structure rfkill util 2020-01-21 17:48:45 +01:00
626af1ddc1 add rudimentary bluetooth module functionality 2020-01-21 17:04:54 +01:00
2c4369a653 add basis for bluetooth module implementation 2020-01-21 15:46:08 +01:00
b8aeda794c Merge pull request #2 from marcplustwo/airplane_mode
distinguish between wifi disabled and disconnected
2020-01-20 10:46:59 +01:00
2dc4ae78fc distinguish between wifi disabled and disconnected 2020-01-20 00:35:37 +01:00
bd67c9e620 Merge pull request #1 from Alexays/master
Integrate changes from Alexays/Waybar
2020-01-19 16:08:46 +01:00
0f0765e517 feat(modules): call user on-update if configured 2019-12-05 14:42:36 -08:00
114 changed files with 5885 additions and 1043 deletions

1
.github/FUNDING.yml vendored
View File

@ -1,3 +1,4 @@
# These are supported funding model platforms
github: Alexays
custom: https://paypal.me/ARouillard

22
.github/workflows/freebsd.yml vendored Normal file
View File

@ -0,0 +1,22 @@
name: freebsd
on: [ push, pull_request ]
jobs:
clang:
runs-on: macos-latest # until https://github.com/actions/runner/issues/385
steps:
- uses: actions/checkout@v2
- name: Test in FreeBSD VM
uses: vmactions/freebsd-vm@v0.0.9 # aka FreeBSD 12.2
with:
usesh: true
prepare: |
export CPPFLAGS=-isystem/usr/local/include LDFLAGS=-L/usr/local/lib # sndio
sed -i '' 's/quarterly/latest/' /etc/pkg/FreeBSD.conf
pkg install -y git # subprojects/date
pkg install -y gtk-layer-shell gtkmm30 jsoncpp libdbusmenu sndio \
libfmt libmpdclient libudev-devd meson pkgconf pulseaudio scdoc spdlog
run: |
meson build -Dman-pages=enabled
ninja -C build

View File

@ -1,4 +1,4 @@
sudo: false
language: cpp
services:
- docker
@ -11,6 +11,7 @@ env:
- distro: archlinux
- distro: fedora
- distro: alpine
- distro: opensuse
before_install:
- docker pull alexays/waybar:${distro}
@ -21,3 +22,17 @@ script:
- echo ADD . /root >> Dockerfile
- docker build -t waybar .
- docker run waybar /bin/sh -c "cd /root && meson build -Dman-pages=enabled && ninja -C build"
jobs:
include:
- os: freebsd
compiler: clang
env:
before_install:
- export CPPFLAGS+=-isystem/usr/local/include LDFLAGS+=-L/usr/local/lib # sndio
- sudo sed -i '' 's/quarterly/latest/' /etc/pkg/FreeBSD.conf
- sudo pkg install -y gtk-layer-shell gtkmm30 jsoncpp libdbusmenu sndio
libfmt libmpdclient libudev-devd meson pulseaudio scdoc spdlog
script:
- meson build -Dman-pages=enabled
- ninja -C build

View File

@ -2,4 +2,4 @@
FROM alpine:latest
RUN apk add --no-cache git meson alpine-sdk libinput-dev wayland-dev wayland-protocols mesa-dev libxkbcommon-dev eudev-dev pixman-dev gtkmm3-dev jsoncpp-dev pugixml libnl3-dev pulseaudio-dev libmpdclient-dev scdoc
RUN apk add --no-cache git meson alpine-sdk libinput-dev wayland-dev wayland-protocols mesa-dev libxkbcommon-dev eudev-dev pixman-dev gtkmm3-dev jsoncpp-dev pugixml-dev libnl3-dev pulseaudio-dev libmpdclient-dev sndio-dev scdoc

View File

@ -3,4 +3,4 @@
FROM archlinux/base:latest
RUN pacman -Syu --noconfirm && \
pacman -S git meson base-devel libinput wayland wayland-protocols pixman libxkbcommon mesa gtkmm3 jsoncpp pugixml scdoc --noconfirm
pacman -S git meson base-devel libinput wayland wayland-protocols pixman libxkbcommon mesa gtkmm3 jsoncpp pugixml scdoc libpulse libdbusmenu-gtk3 libmpdclient gobject-introspection --noconfirm

View File

@ -3,5 +3,5 @@
FROM debian:sid
RUN apt-get update && \
apt-get install -y build-essential meson ninja-build git pkg-config libinput10 libpugixml-dev 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 scdoc && \
apt-get install -y build-essential meson ninja-build git pkg-config libinput10 libpugixml-dev 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 scdoc libdbusmenu-gtk3-dev libnl-3-dev libnl-genl-3-dev libpulse-dev libmpdclient-dev gobject-introspection libgirepository1.0-dev && \
apt-get clean

View File

@ -1,6 +1,6 @@
# vim: ft=Dockerfile
FROM fedora:30
FROM fedora:32
RUN dnf install sway meson git libinput-devel wayland-devel wayland-protocols-devel pugixml-devel egl-wayland-devel mesa-libEGL-devel mesa-libGLES-devel mesa-libgbm-devel libxkbcommon-devel libudev-devel pixman-devel gtkmm30-devel jsoncpp-devel scdoc -y && \
dnf group install "C Development Tools and Libraries" -y && \

View File

@ -4,4 +4,4 @@ FROM opensuse/tumbleweed:latest
RUN zypper -n up && \
zypper -n install -t pattern devel_C_C++ && \
zypper -n install git meson clang libinput10 libinput-devel libpugixml1 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 scdoc
zypper -n install git meson clang libinput10 libinput-devel pugixml-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 scdoc

View File

@ -5,12 +5,13 @@
[AUR](https://aur.archlinux.org/packages/waybar-git/), [openSUSE](https://build.opensuse.org/package/show/X11:Wayland/waybar), and [Alpine Linux](https://pkgs.alpinelinux.org/packages?name=waybar)<br>
> *Waybar [examples](https://github.com/Alexays/Waybar/wiki/Examples)*
**Current features**
#### Current features
- Sway (Workspaces, Binding mode, Focused window name)
- Tray [#21](https://github.com/Alexays/Waybar/issues/21)
- Local time
- Battery
- Network
- Bluetooth
- Pulseaudio
- Disk
- Memory
@ -21,11 +22,21 @@
- Multiple output configuration
- And much more customizations
**Configuration and Styling**
#### Configuration and Styling
[See the wiki for more details](https://github.com/Alexays/Waybar/wiki).
**How to build**
### Installation
Waybar is available from a number of Linux distributions:
[![Packaging status](https://repology.org/badge/vertical-allrepos/waybar.svg)](https://repology.org/project/waybar/versions)
An Ubuntu PPA with more recent versions is available
[here](https://launchpad.net/~nschloe/+archive/ubuntu/waybar).
#### Building from source
```bash
$ git clone https://github.com/Alexays/Waybar
@ -43,23 +54,32 @@ $ waybar
```
gtkmm3
jsoncpp
libinput
libsigc++
fmt
wayland
wlroots
chrono-date
spdlog
libgtk-3-dev [gtk-layer-shell]
gobject-introspection [gtk-layer-shell]
libgirepository1.0-dev [gtk-layer-shell]
libpulse [Pulseaudio module]
libnl [Network module]
sway [Sway modules]
libappindicator-gtk3 [Tray module]
libdbusmenu-gtk3 [Tray module]
libmpdclient [MPD module]
libsndio [sndio module]
```
On Ubuntu 19.10 you can install all the relevant dependencies using this command:
**Build dependencies**
```
cmake
meson
scdoc
wayland-protocols
```
On Ubuntu you can install all the relevant dependencies using this command (tested with 19.10 and 20.04):
```
sudo apt install \

View File

@ -10,15 +10,15 @@ namespace waybar {
class ALabel : public AModule {
public:
ALabel(const Json::Value &, const std::string &, const std::string &, const std::string &format,
uint16_t interval = 0, bool ellipsize = false);
uint16_t interval = 0, bool ellipsize = false, bool enable_click = false, bool enable_scroll = false);
virtual ~ALabel() = default;
virtual auto update() -> void;
virtual std::string getIcon(uint16_t, const std::string &alt = "", uint16_t max = 0);
virtual std::string getIcon(uint16_t, std::vector<std::string> &alts, uint16_t max = 0);
protected:
Gtk::Label label_;
std::string format_;
std::string click_param;
const std::chrono::seconds interval_;
bool alt_ = false;
std::string default_format_;

View File

@ -4,14 +4,15 @@
#include <glibmm/markup.h>
#include <gtkmm/eventbox.h>
#include <json/json.h>
#include "IModule.hpp"
namespace waybar {
class AModule : public IModule {
public:
AModule(const Json::Value &, const std::string &, const std::string &,
bool enable_click = false, bool enable_scroll = false);
AModule(const Json::Value &, const std::string &, const std::string &, bool enable_click = false,
bool enable_scroll = false);
virtual ~AModule();
virtual auto update() -> void;
virtual operator Gtk::Widget &();
@ -24,6 +25,7 @@ class AModule : public IModule {
SCROLL_DIR getScrollDir(GdkEventScroll *e);
bool tooltipEnabled();
const std::string name_;
const Json::Value &config_;
Gtk::EventBox event_box_;

View File

@ -7,9 +7,8 @@
#include <gtkmm/main.h>
#include <gtkmm/window.h>
#include <json/json.h>
#include "AModule.hpp"
#include "idle-inhibit-unstable-v1-client-protocol.h"
#include "wlr-layer-shell-unstable-v1-client-protocol.h"
#include "xdg-output-unstable-v1-client-protocol.h"
namespace waybar {
@ -18,65 +17,66 @@ class Factory;
struct waybar_output {
Glib::RefPtr<Gdk::Monitor> monitor;
std::string name;
std::string identifier;
std::unique_ptr<struct zxdg_output_v1, decltype(&zxdg_output_v1_destroy)> xdg_output = {
nullptr, &zxdg_output_v1_destroy};
};
enum class bar_layer : uint8_t {
BOTTOM,
TOP,
OVERLAY,
};
struct bar_margins {
int top = 0;
int right = 0;
int bottom = 0;
int left = 0;
};
class BarSurface {
protected:
BarSurface() = default;
public:
virtual void setExclusiveZone(bool enable) = 0;
virtual void setLayer(bar_layer layer) = 0;
virtual void setMargins(const struct bar_margins &margins) = 0;
virtual void setPosition(const std::string_view &position) = 0;
virtual void setSize(uint32_t width, uint32_t height) = 0;
virtual void commit(){};
virtual ~BarSurface() = default;
};
class Bar {
public:
Bar(struct waybar_output *w_output, const Json::Value &);
Bar(const Bar &) = delete;
~Bar() = default;
auto toggle() -> void;
void setVisible(bool visible);
void toggle();
void handleSignal(int);
struct waybar_output *output;
Json::Value config;
Gtk::Window window;
struct wl_surface * surface;
bool visible = true;
bool vertical = false;
Gtk::Window window;
private:
static constexpr const char *MIN_HEIGHT_MSG =
"Requested height: {} exceeds the minimum height: {} required by the modules";
static constexpr const char *MIN_WIDTH_MSG =
"Requested width: {} exceeds the minimum width: {} required by the modules";
static constexpr const char *BAR_SIZE_MSG =
"Bar configured (width: {}, height: {}) for output: {}";
static constexpr const char *SIZE_DEFINED =
"{} size is defined in the config file so it will stay like that";
static void layerSurfaceHandleConfigure(void *, struct zwlr_layer_surface_v1 *, uint32_t,
uint32_t, uint32_t);
static void layerSurfaceHandleClosed(void *, struct zwlr_layer_surface_v1 *);
#ifdef HAVE_GTK_LAYER_SHELL
void initGtkLayerShell();
#endif
void onConfigure(GdkEventConfigure *ev);
void onRealize();
void onMap(GdkEventAny *ev);
void setExclusiveZone(uint32_t width, uint32_t height);
void setSurfaceSize(uint32_t width, uint32_t height);
void onMap(GdkEventAny *);
auto setupWidgets() -> void;
void getModules(const Factory &, const std::string &);
void setupAltFormatKeyForModule(const std::string &module_name);
void setupAltFormatKeyForModuleList(const char *module_list_name);
struct margins {
int top = 0;
int right = 0;
int bottom = 0;
int left = 0;
} margins_;
struct zwlr_layer_surface_v1 *layer_surface_;
// use gtk-layer-shell instead of handling layer surfaces directly
bool use_gls_ = false;
uint32_t width_ = 0;
uint32_t height_ = 1;
uint8_t anchor_;
std::unique_ptr<BarSurface> surface_impl_;
bar_layer layer_;
Gtk::Box left_;
Gtk::Box center_;
Gtk::Box right_;

View File

@ -6,14 +6,20 @@
#include <unistd.h>
#include <wayland-client.h>
#include <wordexp.h>
#include "bar.hpp"
struct zwlr_layer_shell_v1;
struct zwp_idle_inhibitor_v1;
struct zwp_idle_inhibit_manager_v1;
namespace waybar {
class Client {
public:
static Client *inst();
int main(int argc, char *argv[]);
void reset();
Glib::RefPtr<Gtk::Application> gtk_app;
Glib::RefPtr<Gdk::Display> gdk_display;
@ -29,20 +35,23 @@ class Client {
std::tuple<const std::string, const std::string> getConfigs(const std::string &config,
const std::string &style) const;
void bindInterfaces();
const std::string getValidPath(const std::vector<std::string> &paths) const;
void handleOutput(struct waybar_output &output);
bool isValidOutput(const Json::Value &config, struct waybar_output &output);
auto setupConfig(const std::string &config_file) -> void;
auto setupCss(const std::string &css_file) -> void;
struct waybar_output &getOutput(void *);
const std::string getValidPath(const std::vector<std::string> &paths) const;
void handleOutput(struct waybar_output &output);
bool isValidOutput(const Json::Value &config, struct waybar_output &output);
auto setupConfig(const std::string &config_file) -> void;
auto setupCss(const std::string &css_file) -> void;
struct waybar_output & getOutput(void *);
std::vector<Json::Value> getOutputConfigs(struct waybar_output &output);
static void handleGlobal(void *data, struct wl_registry *registry, uint32_t name,
const char *interface, uint32_t version);
static void handleGlobalRemove(void *data, struct wl_registry *registry, uint32_t name);
static void handleOutputDone(void *, struct zxdg_output_v1 *);
static void handleOutputName(void *, struct zxdg_output_v1 *, const char *);
static void handleOutputDescription(void *, struct zxdg_output_v1 *, const char *);
void handleMonitorAdded(Glib::RefPtr<Gdk::Monitor> monitor);
void handleMonitorRemoved(Glib::RefPtr<Gdk::Monitor> monitor);
void handleDeferredMonitorRemoval(Glib::RefPtr<Gdk::Monitor> monitor);
Json::Value config_;
Glib::RefPtr<Gtk::StyleContext> style_context_;

View File

@ -1,20 +1,35 @@
#pragma once
#include <json/json.h>
#ifdef HAVE_LIBDATE
#include "modules/clock.hpp"
#else
#include "modules/simpleclock.hpp"
#endif
#ifdef HAVE_SWAY
#include "modules/sway/mode.hpp"
#include "modules/sway/window.hpp"
#include "modules/sway/workspaces.hpp"
#include "modules/sway/language.hpp"
#endif
#ifndef NO_FILESYSTEM
#ifdef HAVE_WLR
#include "modules/wlr/taskbar.hpp"
#endif
#ifdef HAVE_RIVER
#include "modules/river/tags.hpp"
#endif
#if defined(__linux__) && !defined(NO_FILESYSTEM)
#include "modules/battery.hpp"
#endif
#if defined(HAVE_CPU_LINUX) || defined(HAVE_CPU_BSD)
#include "modules/cpu.hpp"
#endif
#include "modules/idle_inhibitor.hpp"
#if defined(HAVE_MEMORY_LINUX) || defined(HAVE_MEMORY_BSD)
#include "modules/memory.hpp"
#endif
#include "modules/disk.hpp"
#if defined(HAVE_DBUSMENU) && !defined(NO_FILESYSTEM)
#ifdef HAVE_DBUSMENU
#include "modules/sni/tray.hpp"
#endif
#ifdef HAVE_LIBNL
@ -27,11 +42,19 @@
#include "modules/pulseaudio.hpp"
#endif
#ifdef HAVE_LIBMPDCLIENT
#include "modules/mpd.hpp"
#include "modules/mpd/mpd.hpp"
#endif
#ifdef HAVE_LIBSNDIO
#include "modules/sndio.hpp"
#endif
#include "bar.hpp"
#include "modules/custom.hpp"
#include "modules/temperature.hpp"
#if defined(__linux__)
# ifdef WANT_RFKILL
# include "modules/bluetooth.hpp"
# endif
#endif
namespace waybar {

View File

@ -31,19 +31,22 @@ class Battery : public ALabel {
private:
static inline const fs::path data_dir_ = "/sys/class/power_supply/";
void getBatteries();
void worker();
const std::string getAdapterStatus(uint8_t capacity) const;
const std::tuple<uint8_t, float, std::string> getInfos() const;
const std::string formatTimeRemaining(float hoursRemaining);
void refreshBatteries();
void worker();
const std::string getAdapterStatus(uint8_t capacity) const;
const std::tuple<uint8_t, float, std::string, float> getInfos();
const std::string formatTimeRemaining(float hoursRemaining);
std::vector<fs::path> batteries_;
int global_watch;
std::map<fs::path,int> batteries_;
fs::path adapter_;
int fd_;
std::vector<int> wds_;
int battery_watch_fd_;
int global_watch_fd_;
std::mutex battery_list_mutex_;
std::string old_status_;
util::SleeperThread thread_;
util::SleeperThread thread_battery_update_;
util::SleeperThread thread_timer_;
};

View File

@ -0,0 +1,18 @@
#pragma once
#include "ALabel.hpp"
#include "util/rfkill.hpp"
namespace waybar::modules {
class Bluetooth : public ALabel {
public:
Bluetooth(const std::string&, const Json::Value&);
~Bluetooth() = default;
auto update() -> void;
private:
util::Rfkill rfkill_;
};
} // namespace waybar::modules

View File

@ -28,9 +28,12 @@ class Clock : public ALabel {
std::locale locale_;
const date::time_zone* time_zone_;
bool fixed_time_zone_;
date::year_month_day cached_calendar_ymd_;
int time_zone_idx_;
date::year_month_day cached_calendar_ymd_ = date::January/1/0;
std::string cached_calendar_text_;
bool handleScroll(GdkEventScroll* e);
auto calendar_text(const waybar_time& wtime) -> std::string;
auto weekdays_header(const date::weekday& first_dow, std::ostream& os) -> void;
auto first_day_of_week() -> date::weekday;

View File

@ -1,7 +1,6 @@
#pragma once
#include <fmt/format.h>
#include <sys/sysinfo.h>
#include <cstdint>
#include <fstream>
#include <numeric>
@ -20,10 +19,11 @@ class Cpu : public ALabel {
auto update() -> void;
private:
static inline const std::string data_dir_ = "/proc/stat";
uint16_t getCpuLoad();
double getCpuLoad();
std::tuple<uint16_t, std::string> getCpuUsage();
std::tuple<float, float, float> getCpuFrequency();
std::vector<std::tuple<size_t, size_t>> parseCpuinfo();
std::vector<float> parseCpuFrequencies();
std::vector<std::tuple<size_t, size_t>> prev_times_;

View File

@ -22,6 +22,7 @@ class Custom : public ALabel {
void continuousWorker();
void parseOutputRaw();
void parseOutputJson();
void handleEvent();
bool handleScroll(GdkEventScroll* e);
bool handleToggle(GdkEventButton* const& e);

View File

@ -12,12 +12,13 @@ class IdleInhibitor : public ALabel {
IdleInhibitor(const std::string&, const waybar::Bar&, const Json::Value&);
~IdleInhibitor();
auto update() -> void;
static std::list<waybar::AModule*> modules;
static bool status;
private:
bool handleToggle(GdkEventButton* const& e);
const Bar& bar_;
std::string status_;
struct zwp_idle_inhibitor_v1* idle_inhibitor_;
int pid_;
};

View File

@ -2,6 +2,7 @@
#include <fmt/format.h>
#include <fstream>
#include <unordered_map>
#include "ALabel.hpp"
#include "util/sleeper_thread.hpp"
@ -14,7 +15,6 @@ class Memory : public ALabel {
auto update() -> void;
private:
static inline const std::string data_dir_ = "/proc/meminfo";
void parseMeminfo();
std::unordered_map<std::string, unsigned long> meminfo_;

View File

@ -1,74 +0,0 @@
#pragma once
#include <fmt/format.h>
#include <mpd/client.h>
#include <condition_variable>
#include <thread>
#include "ALabel.hpp"
namespace waybar::modules {
class MPD : public ALabel {
public:
MPD(const std::string&, const Json::Value&);
auto update() -> void;
private:
std::thread periodic_updater();
std::string getTag(mpd_tag_type type, unsigned idx = 0);
void setLabel();
std::string getStateIcon();
std::string getOptionIcon(std::string optionName, bool activated);
std::thread event_listener();
// Assumes `connection_lock_` is locked
void tryConnect();
// If checking errors on the main connection, make sure to lock it using
// `connection_lock_` before calling checkErrors
void checkErrors(mpd_connection* conn);
// Assumes `connection_lock_` is locked
void fetchState();
void waitForEvent();
bool handlePlayPause(GdkEventButton* const&);
bool stopped();
bool playing();
bool paused();
const std::string module_name_;
using unique_connection = std::unique_ptr<mpd_connection, decltype(&mpd_connection_free)>;
using unique_status = std::unique_ptr<mpd_status, decltype(&mpd_status_free)>;
using unique_song = std::unique_ptr<mpd_song, decltype(&mpd_song_free)>;
// Not using unique_ptr since we don't manage the pointer
// (It's either nullptr, or from the config)
const char* server_;
const unsigned port_;
unsigned timeout_;
// We need a mutex here because we can trigger updates from multiple thread:
// the event based updates, the periodic updates needed for the elapsed time,
// and the click play/pause feature
std::mutex connection_lock_;
unique_connection connection_;
// The alternate connection will be used to wait for events: since it will
// be blocking (idle) we can't send commands via this connection
//
// No lock since only used in the event listener thread
unique_connection alternate_connection_;
// Protect them using the `connection_lock_`
unique_status status_;
mpd_state state_;
unique_song song_;
// To make sure the previous periodic_updater stops before creating a new one
std::mutex periodic_lock_;
};
} // namespace waybar::modules

View File

@ -0,0 +1,67 @@
#pragma once
#include <fmt/format.h>
#include <mpd/client.h>
#include <spdlog/spdlog.h>
#include <condition_variable>
#include <thread>
#include "ALabel.hpp"
#include "modules/mpd/state.hpp"
namespace waybar::modules {
class MPD : public ALabel {
friend class detail::Context;
// State machine
detail::Context context_{this};
const std::string module_name_;
// Not using unique_ptr since we don't manage the pointer
// (It's either nullptr, or from the config)
const char* server_;
const unsigned port_;
const std::string password_;
unsigned timeout_;
detail::unique_connection connection_;
detail::unique_status status_;
mpd_state state_;
detail::unique_song song_;
public:
MPD(const std::string&, const Json::Value&);
virtual ~MPD() noexcept = default;
auto update() -> void;
private:
std::string getTag(mpd_tag_type type, unsigned idx = 0) const;
void setLabel();
std::string getStateIcon() const;
std::string getOptionIcon(std::string optionName, bool activated) const;
// GUI-side methods
bool handlePlayPause(GdkEventButton* const&);
void emit() { dp.emit(); }
// MPD-side, Non-GUI methods.
void tryConnect();
void checkErrors(mpd_connection* conn);
void fetchState();
void queryMPD();
inline bool stopped() const { return connection_ && state_ == MPD_STATE_STOP; }
inline bool playing() const { return connection_ && state_ == MPD_STATE_PLAY; }
inline bool paused() const { return connection_ && state_ == MPD_STATE_PAUSE; }
};
#if !defined(MPD_NOINLINE)
#include "modules/mpd/state.inl.hpp"
#endif
} // namespace waybar::modules

View File

@ -0,0 +1,217 @@
#pragma once
#include <mpd/client.h>
#include <fmt/format.h>
#include <spdlog/spdlog.h>
#include <condition_variable>
#include <thread>
#include "ALabel.hpp"
namespace waybar::modules {
class MPD;
} // namespace waybar::modules
namespace waybar::modules::detail {
using unique_connection = std::unique_ptr<mpd_connection, decltype(&mpd_connection_free)>;
using unique_status = std::unique_ptr<mpd_status, decltype(&mpd_status_free)>;
using unique_song = std::unique_ptr<mpd_song, decltype(&mpd_song_free)>;
class Context;
/// This state machine loosely follows a non-hierarchical, statechart
/// pattern, and includes ENTRY and EXIT actions.
///
/// The State class is the base class for all other states. The
/// entry and exit methods are automatically called when entering
/// into a new state and exiting from the current state. This
/// includes initially entering (Disconnected class) and exiting
/// Waybar.
///
/// The following nested "top-level" states are represented:
/// 1. Idle - await notification of MPD activity.
/// 2. All Non-Idle states:
/// 1. Playing - An active song is producing audio output.
/// 2. Paused - The current song is paused.
/// 3. Stopped - No song is actively playing.
/// 3. Disconnected - periodically attempt MPD (re-)connection.
///
/// NOTE: Since this statechart is non-hierarchical, the above
/// states are flattened into a set.
class State {
public:
virtual ~State() noexcept = default;
virtual void entry() noexcept { spdlog::debug("mpd: ignore entry action"); }
virtual void exit() noexcept { spdlog::debug("mpd: ignore exit action"); }
virtual void play() { spdlog::debug("mpd: ignore play state transition"); }
virtual void stop() { spdlog::debug("mpd: ignore stop state transition"); }
virtual void pause() { spdlog::debug("mpd: ignore pause state transition"); }
/// Request state update the GUI.
virtual void update() noexcept { spdlog::debug("mpd: ignoring update method request"); }
};
class Idle : public State {
Context* const ctx_;
sigc::connection idle_connection_;
public:
Idle(Context* const ctx) : ctx_{ctx} {}
virtual ~Idle() noexcept { this->exit(); };
void entry() noexcept override;
void exit() noexcept override;
void play() override;
void stop() override;
void pause() override;
void update() noexcept override;
private:
Idle(const Idle&) = delete;
Idle& operator=(const Idle&) = delete;
bool on_io(Glib::IOCondition const&);
};
class Playing : public State {
Context* const ctx_;
sigc::connection timer_connection_;
public:
Playing(Context* const ctx) : ctx_{ctx} {}
virtual ~Playing() noexcept { this->exit(); }
void entry() noexcept override;
void exit() noexcept override;
void pause() override;
void stop() override;
void update() noexcept override;
private:
Playing(Playing const&) = delete;
Playing& operator=(Playing const&) = delete;
bool on_timer();
};
class Paused : public State {
Context* const ctx_;
sigc::connection timer_connection_;
public:
Paused(Context* const ctx) : ctx_{ctx} {}
virtual ~Paused() noexcept { this->exit(); }
void entry() noexcept override;
void exit() noexcept override;
void play() override;
void stop() override;
void update() noexcept override;
private:
Paused(Paused const&) = delete;
Paused& operator=(Paused const&) = delete;
bool on_timer();
};
class Stopped : public State {
Context* const ctx_;
sigc::connection timer_connection_;
public:
Stopped(Context* const ctx) : ctx_{ctx} {}
virtual ~Stopped() noexcept { this->exit(); }
void entry() noexcept override;
void exit() noexcept override;
void play() override;
void pause() override;
void update() noexcept override;
private:
Stopped(Stopped const&) = delete;
Stopped& operator=(Stopped const&) = delete;
bool on_timer();
};
class Disconnected : public State {
Context* const ctx_;
sigc::connection timer_connection_;
public:
Disconnected(Context* const ctx) : ctx_{ctx} {}
virtual ~Disconnected() noexcept { this->exit(); }
void entry() noexcept override;
void exit() noexcept override;
void update() noexcept override;
private:
Disconnected(Disconnected const&) = delete;
Disconnected& operator=(Disconnected const&) = delete;
void arm_timer(int interval) noexcept;
void disarm_timer() noexcept;
bool on_timer();
};
class Context {
std::unique_ptr<State> state_;
waybar::modules::MPD* mpd_module_;
friend class State;
friend class Playing;
friend class Paused;
friend class Stopped;
friend class Disconnected;
friend class Idle;
protected:
void setState(std::unique_ptr<State>&& new_state) noexcept {
if (state_.get() != nullptr) {
state_->exit();
}
state_ = std::move(new_state);
state_->entry();
}
bool is_connected() const;
bool is_playing() const;
bool is_paused() const;
bool is_stopped() const;
constexpr std::size_t interval() const;
void tryConnect() const;
void checkErrors(mpd_connection*) const;
void do_update();
void queryMPD() const;
void fetchState() const;
constexpr mpd_state state() const;
void emit() const;
[[nodiscard]] unique_connection& connection();
public:
explicit Context(waybar::modules::MPD* const mpd_module)
: state_{std::make_unique<Disconnected>(this)}, mpd_module_{mpd_module} {
state_->entry();
}
void play() { state_->play(); }
void stop() { state_->stop(); }
void pause() { state_->pause(); }
void update() noexcept { state_->update(); }
};
} // namespace waybar::modules::detail

View File

@ -0,0 +1,24 @@
#pragma once
namespace detail {
inline bool Context::is_connected() const { return mpd_module_->connection_ != nullptr; }
inline bool Context::is_playing() const { return mpd_module_->playing(); }
inline bool Context::is_paused() const { return mpd_module_->paused(); }
inline bool Context::is_stopped() const { return mpd_module_->stopped(); }
constexpr inline std::size_t Context::interval() const { return mpd_module_->interval_.count(); }
inline void Context::tryConnect() const { mpd_module_->tryConnect(); }
inline unique_connection& Context::connection() { return mpd_module_->connection_; }
constexpr inline mpd_state Context::state() const { return mpd_module_->state_; }
inline void Context::do_update() {
mpd_module_->setLabel();
}
inline void Context::checkErrors(mpd_connection* conn) const { mpd_module_->checkErrors(conn); }
inline void Context::queryMPD() const { mpd_module_->queryMPD(); }
inline void Context::fetchState() const { mpd_module_->fetchState(); }
inline void Context::emit() const { mpd_module_->emit(); }
} // namespace detail

View File

@ -11,6 +11,9 @@
#include <sys/epoll.h>
#include "ALabel.hpp"
#include "util/sleeper_thread.hpp"
#ifdef WANT_RFKILL
#include "util/rfkill.hpp"
#endif
namespace waybar::modules {
@ -71,6 +74,9 @@ class Network : public ALabel {
util::SleeperThread thread_;
util::SleeperThread thread_timer_;
#ifdef WANT_RFKILL
util::Rfkill rfkill_;
#endif
};
} // namespace waybar::modules

View File

@ -4,6 +4,7 @@
#include <pulse/pulseaudio.h>
#include <pulse/volume.h>
#include <algorithm>
#include <array>
#include "ALabel.hpp"
namespace waybar::modules {

View File

@ -0,0 +1,31 @@
#pragma once
#include <gtkmm/button.h>
#include <wayland-client.h>
#include "AModule.hpp"
#include "bar.hpp"
#include "river-status-unstable-v1-client-protocol.h"
#include "xdg-output-unstable-v1-client-protocol.h"
namespace waybar::modules::river {
class Tags : public waybar::AModule {
public:
Tags(const std::string &, const waybar::Bar &, const Json::Value &);
~Tags();
// Handlers for wayland events
void handle_focused_tags(uint32_t tags);
void handle_view_tags(struct wl_array *tags);
struct zriver_status_manager_v1 *status_manager_;
private:
const waybar::Bar & bar_;
Gtk::Box box_;
std::vector<Gtk::Button> buttons_;
struct zriver_output_status_v1 *output_status_;
};
} /* namespace waybar::modules::river */

View File

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

30
include/modules/sndio.hpp Normal file
View File

@ -0,0 +1,30 @@
#pragma once
#include <sndio.h>
#include <vector>
#include "ALabel.hpp"
#include "util/sleeper_thread.hpp"
namespace waybar::modules {
class Sndio : public ALabel {
public:
Sndio(const std::string&, const Json::Value&);
~Sndio();
auto update() -> void;
auto set_desc(struct sioctl_desc *, unsigned int) -> void;
auto put_val(unsigned int, unsigned int) -> void;
bool handleScroll(GdkEventScroll *);
bool handleToggle(GdkEventButton* const&);
private:
auto connect_to_sndio() -> void;
util::SleeperThread thread_;
struct sioctl_hdl *hdl_;
std::vector<struct pollfd> pfds_;
unsigned int addr_;
unsigned int volume_, old_volume_, maxval_;
bool muted_;
};
} // namespace waybar::modules

View File

@ -10,11 +10,6 @@
#include <json/json.h>
#include <libdbusmenu-gtk/dbusmenu-gtk.h>
#include <sigc++/trackable.h>
#ifdef FILESYSTEM_EXPERIMENTAL
#include <experimental/filesystem>
#else
#include <filesystem>
#endif
namespace waybar::modules::SNI {

View File

@ -0,0 +1,28 @@
#pragma once
#include <fmt/format.h>
#include "ALabel.hpp"
#include "bar.hpp"
#include "client.hpp"
#include "modules/sway/ipc/client.hpp"
#include "util/json.hpp"
namespace waybar::modules::sway {
class Language : public ALabel, public sigc::trackable {
public:
Language(const std::string& id, const Json::Value& config);
~Language() = default;
auto update() -> void;
private:
void onEvent(const struct Ipc::ipc_response&);
void onCmd(const struct Ipc::ipc_response&);
std::string lang_;
util::JsonParser parser_;
std::mutex mutex_;
Ipc ipc_;
};
} // namespace waybar::modules::sway

View File

@ -4,6 +4,7 @@
#include <fmt/format.h>
#include <gtkmm/button.h>
#include <gtkmm/label.h>
#include <unordered_map>
#include "AModule.hpp"
#include "bar.hpp"
#include "client.hpp"
@ -19,6 +20,10 @@ class Workspaces : public AModule, public sigc::trackable {
auto update() -> void;
private:
static inline const std::string workspace_switch_cmd_ = "workspace {} \"{}\"";
static int convertWorkspaceNameToNum(std::string name);
void onCmd(const struct Ipc::ipc_response&);
void onEvent(const struct Ipc::ipc_response&);
bool filterButtons();

View File

@ -14,8 +14,8 @@ class Temperature : public ALabel {
auto update() -> void;
private:
std::tuple<uint16_t, uint16_t> getTemperature();
bool isCritical(uint16_t);
float getTemperature();
bool isCritical(uint16_t);
std::string file_path_;
util::SleeperThread thread_;

View File

@ -0,0 +1,160 @@
#pragma once
#include "AModule.hpp"
#include "bar.hpp"
#include "client.hpp"
#include "util/json.hpp"
#include <memory>
#include <string>
#include <vector>
#include <gdk/gdk.h>
#include <glibmm/refptr.h>
#include <gtkmm/box.h>
#include <gtkmm/button.h>
#include <gtkmm/image.h>
#include <gtkmm/label.h>
#include <gtkmm/icontheme.h>
#include <wayland-client.h>
#include "wlr-foreign-toplevel-management-unstable-v1-client-protocol.h"
namespace waybar::modules::wlr {
class Taskbar;
class Task
{
public:
Task(const waybar::Bar&, const Json::Value&, Taskbar*,
struct zwlr_foreign_toplevel_handle_v1 *, struct wl_seat*);
~Task();
public:
enum State {
MAXIMIZED = (1 << 0),
MINIMIZED = (1 << 1),
ACTIVE = (1 << 2),
FULLSCREEN = (1 << 3),
INVALID = (1 << 4)
};
private:
static uint32_t global_id;
private:
const waybar::Bar &bar_;
const Json::Value &config_;
Taskbar *tbar_;
struct zwlr_foreign_toplevel_handle_v1 *handle_;
struct wl_seat *seat_;
uint32_t id_;
Gtk::Button button_;
Gtk::Box content_;
Gtk::Image icon_;
Gtk::Label text_before_;
Gtk::Label text_after_;
bool button_visible_;
bool with_icon_;
std::string format_before_;
std::string format_after_;
std::string format_tooltip_;
std::string title_;
std::string app_id_;
uint32_t state_ = 0;
private:
std::string repr() const;
std::string state_string(bool = false) const;
public:
/* Getter functions */
uint32_t id() const { return id_; }
std::string title() const { return title_; }
std::string app_id() const { return app_id_; }
uint32_t state() const { return state_; }
bool maximized() const { return state_ & MAXIMIZED; }
bool minimized() const { return state_ & MINIMIZED; }
bool active() const { return state_ & ACTIVE; }
bool fullscreen() const { return state_ & FULLSCREEN; }
public:
/* Callbacks for the wlr protocol */
void handle_title(const char *);
void handle_app_id(const char *);
void handle_output_enter(struct wl_output *);
void handle_output_leave(struct wl_output *);
void handle_state(struct wl_array *);
void handle_done();
void handle_closed();
/* Callbacks for Gtk events */
bool handle_clicked(GdkEventButton *);
public:
bool operator==(const Task&) const;
bool operator!=(const Task&) const;
public:
void update();
public:
/* Interaction with the tasks */
void maximize(bool);
void minimize(bool);
void activate();
void fullscreen(bool);
void close();
};
using TaskPtr = std::unique_ptr<Task>;
class Taskbar : public waybar::AModule
{
public:
Taskbar(const std::string&, const waybar::Bar&, const Json::Value&);
~Taskbar();
void update();
private:
const waybar::Bar &bar_;
Gtk::Box box_;
std::vector<TaskPtr> tasks_;
std::vector<Glib::RefPtr<Gtk::IconTheme>> icon_themes_;
struct zwlr_foreign_toplevel_manager_v1 *manager_;
struct wl_seat *seat_;
public:
/* Callbacks for global registration */
void register_manager(struct wl_registry*, uint32_t name, uint32_t version);
void register_seat(struct wl_registry*, uint32_t name, uint32_t version);
/* Callbacks for the wlr protocol */
void handle_toplevel_create(struct zwlr_foreign_toplevel_handle_v1 *);
void handle_finished();
public:
void add_button(Gtk::Button &);
void move_button(Gtk::Button &, int);
void remove_button(Gtk::Button &);
void remove_task(uint32_t);
bool show_output(struct wl_output *) const;
bool all_outputs() const;
std::vector<Glib::RefPtr<Gtk::IconTheme>> icon_themes() const;
};
} /* namespace waybar::modules::wlr */

View File

@ -1,9 +1,22 @@
#pragma once
#include <giomm.h>
#include <spdlog/spdlog.h>
#include <sys/wait.h>
#include <unistd.h>
#ifdef __linux__
#include <sys/prctl.h>
#endif
#ifdef __FreeBSD__
#include <sys/procctl.h>
#endif
#include <array>
extern std::mutex reap_mtx;
extern std::list<pid_t> reap;
namespace waybar::util::command {
struct res {
@ -28,20 +41,31 @@ inline std::string read(FILE* fp) {
}
inline int close(FILE* fp, pid_t pid) {
int stat;
int stat = -1;
pid_t ret;
fclose(fp);
while (waitpid(pid, &stat, 0) == -1) {
if (errno != EINTR) {
stat = 0;
do {
ret = waitpid(pid, &stat, WCONTINUED | WUNTRACED);
if (WIFEXITED(stat)) {
spdlog::debug("Cmd exited with code {}", WEXITSTATUS(stat));
} else if (WIFSIGNALED(stat)) {
spdlog::debug("Cmd killed by {}", WTERMSIG(stat));
} else if (WIFSTOPPED(stat)) {
spdlog::debug("Cmd stopped by {}", WSTOPSIG(stat));
} else if (WIFCONTINUED(stat)) {
spdlog::debug("Cmd continued");
} else if (ret == -1) {
spdlog::debug("waitpid failed: {}", strerror(errno));
} else {
break;
}
}
} while (!WIFEXITED(stat) && !WIFSIGNALED(stat));
return stat;
}
inline FILE* open(const std::string cmd, int& pid) {
inline FILE* open(const std::string& cmd, int& pid) {
if (cmd == "") return nullptr;
int fd[2];
pipe(fd);
@ -49,15 +73,33 @@ inline FILE* open(const std::string cmd, int& pid) {
pid_t child_pid = fork();
if (child_pid < 0) {
printf("Unable to exec cmd %s, error %s", cmd.c_str(), strerror(errno));
spdlog::error("Unable to exec cmd {}, error {}", cmd.c_str(), strerror(errno));
return nullptr;
}
if (!child_pid) {
int err;
sigset_t mask;
sigfillset(&mask);
// Reset sigmask
err = pthread_sigmask(SIG_UNBLOCK, &mask, nullptr);
if (err != 0) spdlog::error("pthread_sigmask in open failed: {}", strerror(err));
// Kill child if Waybar exits
int deathsig = SIGTERM;
#ifdef __linux__
if (prctl(PR_SET_PDEATHSIG, deathsig) != 0) {
spdlog::error("prctl(PR_SET_PDEATHSIG) in open failed: {}", strerror(errno));
}
#endif
#ifdef __FreeBSD__
if (procctl(P_PID, 0, PROC_PDEATHSIG_CTL, reinterpret_cast<void*>(&deathsig)) == -1) {
spdlog::error("procctl(PROC_PDEATHSIG_CTL) in open failed: {}", strerror(errno));
}
#endif
::close(fd[0]);
dup2(fd[1], 1);
setpgid(child_pid, child_pid);
execl("/bin/sh", "sh", "-c", cmd.c_str(), (char*)0);
execlp("/bin/sh", "sh", "-c", cmd.c_str(), (char*)0);
exit(0);
} else {
::close(fd[1]);
@ -66,36 +108,49 @@ inline FILE* open(const std::string cmd, int& pid) {
return fdopen(fd[0], "r");
}
inline struct res exec(std::string cmd) {
inline struct res exec(const std::string& cmd) {
int pid;
auto fp = command::open(cmd, pid);
if (!fp) return {-1, ""};
auto output = command::read(fp);
auto stat = command::close(fp, pid);
if (WIFEXITED(stat)) {
return {WEXITSTATUS(stat), output};
}
return {-1, output};
return {WEXITSTATUS(stat), output};
}
inline int32_t forkExec(std::string cmd) {
inline struct res execNoRead(const std::string& cmd) {
int pid;
auto fp = command::open(cmd, pid);
if (!fp) return {-1, ""};
auto stat = command::close(fp, pid);
return {WEXITSTATUS(stat), ""};
}
inline int32_t forkExec(const std::string& cmd) {
if (cmd == "") return -1;
int32_t pid = fork();
pid_t pid = fork();
if (pid < 0) {
printf("Unable to exec cmd %s, error %s", cmd.c_str(), strerror(errno));
spdlog::error("Unable to exec cmd {}, error {}", cmd.c_str(), strerror(errno));
return pid;
}
// Child executes the command
if (!pid) {
int err;
sigset_t mask;
sigfillset(&mask);
// Reset sigmask
err = pthread_sigmask(SIG_UNBLOCK, &mask, nullptr);
if (err != 0) spdlog::error("pthread_sigmask in forkExec failed: {}", strerror(err));
setpgid(pid, pid);
signal(SIGCHLD, SIG_DFL);
execl("/bin/sh", "sh", "-c", cmd.c_str(), (char*)0);
exit(0);
} else {
signal(SIGCHLD,SIG_IGN);
reap_mtx.lock();
reap.push_back(pid);
reap_mtx.unlock();
spdlog::debug("Added child to reap list: {}", pid);
}
return pid;

View File

@ -23,7 +23,7 @@ namespace fmt {
constexpr auto parse(ParseContext& ctx) -> decltype (ctx.begin()) {
auto it = ctx.begin(), end = ctx.end();
if (it != end && *it == ':') ++it;
if (*it == '>' || *it == '<' || *it == '=') {
if (it && (*it == '>' || *it == '<' || *it == '=')) {
spec = *it;
++it;
}

26
include/util/rfkill.hpp Normal file
View File

@ -0,0 +1,26 @@
#pragma once
#include <glibmm/iochannel.h>
#include <linux/rfkill.h>
#include <sigc++/signal.h>
#include <sigc++/trackable.h>
namespace waybar::util {
class Rfkill : public sigc::trackable {
public:
Rfkill(enum rfkill_type rfkill_type);
~Rfkill();
bool getState() const;
sigc::signal<void(struct rfkill_event&)> on_update;
private:
enum rfkill_type rfkill_type_;
bool state_ = false;
int fd_ = -1;
bool on_event(Glib::IOCondition cond);
};
} // namespace waybar::util

View File

@ -59,6 +59,11 @@ class SleeperThread {
do_run_ = false;
}
condvar_.notify_all();
auto handle = thread_.native_handle();
if (handle != 0) {
// TODO: find a proper way to terminate thread...
pthread_cancel(handle);
}
}
~SleeperThread() {

View File

@ -0,0 +1,5 @@
#pragma once
#include <glibmm/ustring.h>
// calculate column width of ustring
int ustring_clen(const Glib::ustring &str);

View File

@ -10,29 +10,37 @@ The *backlight* module displays the current backlight level.
# CONFIGURATION
*interval* ++
*interval*: ++
typeof: integer ++
default: 2 ++
The interval in which information gets polled.
*format* ++
*format*: ++
typeof: string ++
default: {percent}% ++
The format, how information should be displayed. On {} data gets inserted.
*max-length* ++
*max-length*: ++
typeof: integer ++
The maximum length in characters the module should display.
*rotate* ++
*min-length*: ++
typeof: integer ++
The minimum length in characters the module should take up.
*align*: ++
typeof: float ++
The alignment of the text, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text.
*rotate*: ++
typeof: integer ++
Positive value to rotate the text label.
*states* ++
*states*: ++
typeof: array ++
A number of backlight states which get activated on certain brightness levels.
*on-click* ++
*on-click*: ++
typeof: string ++
Command to execute when the module is clicked.
@ -40,19 +48,23 @@ The *backlight* module displays the current backlight level.
typeof: string ++
Command to execute when middle-clicked on the module using mousewheel.
*on-click-right* ++
*on-click-right*: ++
typeof: string ++
Command to execute when the module is right clicked.
*on-scroll-up* ++
*on-update*: ++
typeof: string ++
Command to execute when the module is updated.
*on-scroll-up*: ++
typeof: string ++
Command to execute when performing a scroll up on the module.
*on-scroll-down* ++
*on-scroll-down*: ++
typeof: string
Command to execute when performing a scroll down on the module.
*smooth-scrolling-threshold* ++
*smooth-scrolling-threshold*: ++
typeof: double
Threshold to be used when scrolling.

View File

@ -10,33 +10,38 @@ The *battery* module displays the current capacity and state (eg. charging) of y
# CONFIGURATION
*bat* ++
*bat*: ++
typeof: string ++
The battery to monitor, as in /sys/class/power_supply/ instead of auto detect.
*adapter* ++
*adapter*: ++
typeof: string ++
The adapter to monitor, as in /sys/class/power_supply/ instead of auto detect.
*full-at* ++
*full-at*: ++
typeof: integer ++
Define the max percentage of the battery, usefull for an old battery, e.g. 96
Define the max percentage of the battery, for when you've set the battery to stop charging at a lower level to save it. For example, if you've set the battery to stop at 80% that will become the new 100%.
*interval* ++
*design-capacity*: ++
typeof: bool ++
default: false ++
Option to use the battery design capacity instead of it's current maximal capacity.
*interval*: ++
typeof: integer ++
default: 60 ++
The interval in which the information gets polled.
*states* ++
*states*: ++
typeof: array ++
A number of battery states which get activated on certain capacity levels. See *waybar-states(5)*.
*format* ++
*format*: ++
typeof: string ++
default: {capacity}% ++
The format, how the time should be displayed.
*format-time* ++
*format-time*: ++
typeof: string ++
default: {H} h {M} min ++
The format, how the time should be displayed.
@ -50,6 +55,14 @@ The *battery* module displays the current capacity and state (eg. charging) of y
typeof: integer++
The maximum length in character the module should display.
*min-length*: ++
typeof: integer ++
The minimum length in characters the module should take up.
*align*: ++
typeof: float ++
The alignment of the text, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text.
*rotate*: ++
typeof: integer++
Positive value to rotate the text label.
@ -62,10 +75,14 @@ The *battery* module displays the current capacity and state (eg. charging) of y
typeof: string ++
Command to execute when middle-clicked on the module using mousewheel.
*on-click-right* ++
*on-click-right*: ++
typeof: string ++
Command to execute when you right clicked on the module.
*on-update*: ++
typeof: string ++
Command to execute when the module is updated.
*on-scroll-up*: ++
typeof: string ++
Command to execute when scrolling up on the module.
@ -74,11 +91,11 @@ The *battery* module displays the current capacity and state (eg. charging) of y
typeof: string ++
Command to execute when scrolling down on the module.
*smooth-scrolling-threshold* ++
*smooth-scrolling-threshold*: ++
typeof: double ++
Threshold to be used when scrolling.
*tooltip* ++
*tooltip*: ++
typeof: bool ++
default: true ++
Option to disable tooltip on hover.
@ -87,6 +104,8 @@ The *battery* module displays the current capacity and state (eg. charging) of y
*{capacity}*: Capacity in percentage
*{power}*: Power in watts
*{icon}*: Icon, as defined in *format-icons*.
*{time}*: Estimate of time until full or empty. Note that this is based on the power draw at the last refresh time, not an average.

View File

@ -0,0 +1,96 @@
waybar-bluetooth(5)
# NAME
waybar - bluetooth module
# DESCRIPTION
The *bluetooth* module displays information about the status of the device's bluetooth device.
# CONFIGURATION
Addressed by *bluetooth*
*format*: ++
typeof: string ++
default: *{icon}* ++
The format, how information should be displayed. This format is used when other formats aren't specified.
*format-icons*: ++
typeof: array/object ++
Based on the device status, the corresponding icon gets selected. ++
The order is *low* to *high*. Or by the state if it is an object.
*rotate*: ++
typeof: integer ++
Positive value to rotate the text label.
*max-length*: ++
typeof: integer ++
The maximum length in character the module should display.
*min-length*: ++
typeof: integer ++
The minimum length in characters the module should take up.
*align*: ++
typeof: float ++
The alignment of the text, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text.
*on-click*: ++
typeof: string ++
Command to execute when clicked on the module.
*on-click-middle*: ++
typeof: string ++
Command to execute when middle-clicked on the module using mousewheel.
*on-click-right*: ++
typeof: string ++
Command to execute when you right clicked on the module.
*on-scroll-up*: ++
typeof: string ++
Command to execute when scrolling up on the module.
*on-scroll-down*: ++
typeof: string ++
Command to execute when scrolling down on the module.
*smooth-scrolling-threshold*: ++
typeof: double ++
Threshold to be used when scrolling.
*tooltip*: ++
typeof: bool ++
default: *true* ++
Option to disable tooltip on hover.
*tooltip-format*: ++
typeof: string ++
The format, how information should be displayed in the tooltip. This format is used when other formats aren't specified.
# FORMAT REPLACEMENTS
*{status}*: Status of the bluetooth device.
*{icon}*: Icon, as defined in *format-icons*.
# EXAMPLES
```
"bluetooth": {
"format": "{icon}",
"format-alt": "bluetooth: {status}",
"format-icons": {
"enabled": "",
"disabled": ""
},
"tooltip-format": "{}"
}
```
# STYLE
- *#bluetooth*

View File

@ -26,15 +26,33 @@ The *clock* module displays the current date and time.
default: inferred local timezone ++
The timezone to display the time in, e.g. America/New_York.
*timezones*: ++
typeof: list of strings ++
A list of timezones to use for time display, changed using the scroll wheel. ++
Use "" to represent the system's local timezone. Using %Z in the format or tooltip format is useful to track which time zone is currently displayed.
*locale*: ++
typeof: string ++
default: inferred from current locale ++
A locale to be used to display the time. Intended to render times in custom timezones with the proper language and format.
*today-format*: ++
typeof: string ++
default: <b><u>{}</u></b> ++
The format of today's date in the calendar.
*max-length*: ++
typeof: integer ++
The maximum length in character the module should display.
*min-length*: ++
typeof: integer ++
The minimum length in characters the module should take up.
*align*: ++
typeof: float ++
The alignment of the text, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text.
*rotate*: ++
typeof: integer ++
Positive value to rotate the text label.
@ -51,6 +69,10 @@ The *clock* module displays the current date and time.
typeof: string ++
Command to execute when you right clicked on the module.
*on-update*: ++
typeof: string ++
Command to execute when the module is updated.
*on-scroll-up*: ++
typeof: string ++
Command to execute when scrolling up on the module.

View File

@ -24,6 +24,14 @@ The *cpu* module displays the current cpu utilization.
typeof: integer ++
The maximum length in character the module should display.
*min-length*: ++
typeof: integer ++
The minimum length in characters the module should take up.
*align*: ++
typeof: float ++
The alignment of the text, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text.
*rotate*: ++
typeof: integer ++
Positive value to rotate the text label.
@ -44,6 +52,10 @@ The *cpu* module displays the current cpu utilization.
typeof: string ++
Command to execute when you right clicked on the module.
*on-update*: ++
typeof: string ++
Command to execute when the module is updated.
*on-scroll-up*: ++
typeof: string ++
Command to execute when scrolling up on the module.
@ -67,6 +79,12 @@ The *cpu* module displays the current cpu utilization.
*{usage}*: Current cpu usage.
*{avg_frequency}*: Current cpu average frequency (based on all cores) in GHz.
*{max_frequency}*: Current cpu max frequency (based on the core with the highest frequency) in GHz.
*{min_frequency}*: Current cpu min frequency (based on the core with the lowest frequency) in GHz.
# EXAMPLE
```

View File

@ -22,6 +22,12 @@ Addressed by *custom/<name>*
The path to a script, which determines if the script in *exec* should be executed.
*exec* will be executed if the exit code of *exec-if* equals 0.
*exec-on-event*: ++
typeof: bool ++
default: true ++
If an event command is set (e.g. *on-click* or *on-scroll-up*) then re-execute the script after
executing the event command.
*return-type*: ++
typeof: string ++
See *return-type*
@ -61,6 +67,14 @@ Addressed by *custom/<name>*
typeof: integer ++
The maximum length in character the module should display.
*min-length*: ++
typeof: integer ++
The minimum length in characters the module should take up.
*align*: ++
typeof: float ++
The alignment of the text, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text.
*on-click*: ++
typeof: string ++
Command to execute when clicked on the module.
@ -73,6 +87,10 @@ Addressed by *custom/<name>*
typeof: string ++
Command to execute when you right clicked on the module.
*on-update*: ++
typeof: string ++
Command to execute when the module is updated.
*on-scroll-up*: ++
typeof: string ++
Command to execute when scrolling up on the module.

View File

@ -31,10 +31,22 @@ Addressed by *disk*
typeof: integer ++
Positive value to rotate the text label.
*states*: ++
typeof: array ++
A number of disk utilization states which get activated on certain percentage thresholds (percentage_used). See *waybar-states(5)*.
*max-length*: ++
typeof: integer ++
The maximum length in character the module should display.
*min-length*: ++
typeof: integer ++
The minimum length in characters the module should take up.
*align*: ++
typeof: float ++
The alignment of the text, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text.
*on-click*: ++
typeof: string ++
Command to execute when clicked on the module.
@ -47,6 +59,10 @@ Addressed by *disk*
typeof: string ++
Command to execute when you right clicked on the module.
*on-update*: ++
typeof: string ++
Command to execute when the module is updated.
*on-scroll-up*: ++
typeof: string ++
Command to execute when scrolling up on the module.

View File

@ -27,6 +27,14 @@ screensaving, also known as "presentation mode".
typeof: integer ++
The maximum length in character the module should display.
*min-length*: ++
typeof: integer ++
The minimum length in characters the module should take up.
*align*: ++
typeof: float ++
The alignment of the text, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text.
*on-click*: ++
typeof: string ++
Command to execute when clicked on the module. A click also toggles the state
@ -39,6 +47,10 @@ screensaving, also known as "presentation mode".
typeof: string ++
Command to execute when you right clicked on the module.
*on-update*: ++
typeof: string ++
Command to execute when the module is updated.
*on-scroll-up*: ++
typeof: string ++
Command to execute when scrolling up on the module.

View File

@ -34,6 +34,14 @@ Addressed by *memory*
typeof: integer ++
The maximum length in character the module should display.
*min-length*: ++
typeof: integer ++
The minimum length in characters the module should take up.
*align*: ++
typeof: float ++
The alignment of the text, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text.
*on-click*: ++
typeof: string ++
Command to execute when clicked on the module.
@ -46,6 +54,10 @@ Addressed by *memory*
typeof: string ++
Command to execute when you right clicked on the module.
*on-update*: ++
typeof: string ++
Command to execute when the module is updated.
*on-scroll-up*: ++
typeof: string ++
Command to execute when scrolling up on the module.

View File

@ -20,6 +20,10 @@ Addressed by *mpd*
typeof: integer ++
The port MPD listens to. If empty, use the default port.
*password*: ++
typeof: string ++
The password required to connect to the MPD server. If empty, no password is sent to MPD.
*interval*: ++
typeof: integer++
default: 5 ++
@ -69,6 +73,22 @@ Addressed by *mpd*
default: "MPD (disconnected)" ++
Tooltip information displayed when the MPD server can't be reached.
*artist-len*: ++
typeof: integer ++
Maximum length of the Artist tag.
*album-len*: ++
typeof: integer ++
Maximum length of the Album tag.
*album-artist-len*: ++
typeof: integer ++
Maximum length of the Album Artist tag.
*title-len*: ++
typeof: integer ++
Maximum length of the Title tag.
*rotate*: ++
typeof: integer ++
Positive value to rotate the text label.
@ -77,6 +97,14 @@ Addressed by *mpd*
typeof: integer ++
The maximum length in character the module should display.
*min-length*: ++
typeof: integer ++
The minimum length in characters the module should take up.
*align*: ++
typeof: float ++
The alignment of the text, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text.
*on-click*: ++
typeof: string ++
Command to execute when clicked on the module.
@ -89,6 +117,10 @@ Addressed by *mpd*
typeof: string ++
Command to execute when you right clicked on the module.
*on-update*: ++
typeof: string ++
Command to execute when the module is updated.
*on-scroll-up*: ++
typeof: string ++
Command to execute when scrolling up on the module.
@ -144,6 +176,10 @@ Addressed by *mpd*
*{totalTime}*: The length of the current song. To format as a date/time (see example configuration)
*{songPosition}*: The position of the current song.
*{queueLength}*: The length of the current queue.
*{stateIcon}*: The icon corresponding the playing or paused status of the player (see *state-icons* option)
*{consumeIcon}*: The icon corresponding the "consume" option (see *consume-icons* option)

View File

@ -33,7 +33,7 @@ Addressed by *network*
*format-ethernet*: ++
typeof: string ++
This format is used when a ethernet interface is displayed.
This format is used when an ethernet interface is displayed.
*format-wifi*: ++
typeof: string ++
@ -47,6 +47,10 @@ Addressed by *network*
typeof: string ++
This format is used when the displayed interface is disconnected.
*format-disabled*: ++
typeof: string ++
This format is used when the displayed interface is disabled.
*format-icons*: ++
typeof: array/object ++
Based on the current signal strength, the corresponding icon gets selected. ++
@ -60,6 +64,14 @@ Addressed by *network*
typeof: integer ++
The maximum length in character the module should display.
*min-length*: ++
typeof: integer ++
The minimum length in characters the module should take up.
*align*: ++
typeof: float ++
The alignment of the text, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text.
*on-click*: ++
typeof: string ++
Command to execute when clicked on the module.
@ -72,6 +84,10 @@ Addressed by *network*
typeof: string ++
Command to execute when you right clicked on the module.
*on-update*: ++
typeof: string ++
Command to execute when the module is updated.
*on-scroll-up*: ++
typeof: string ++
Command to execute when scrolling up on the module.
@ -95,7 +111,7 @@ Addressed by *network*
*tooltip-format-ethernet*: ++
typeof: string ++
This format is used when a ethernet interface is displayed.
This format is used when an ethernet interface is displayed.
*tooltip-format-wifi*: ++
typeof: string ++
@ -105,6 +121,10 @@ Addressed by *network*
typeof: string ++
This format is used when the displayed interface is disconnected.
*tooltip-format-disabled*: ++
typeof: string ++
This format is used when the displayed interface is disabled.
# FORMAT REPLACEMENTS
*{ifname}*: Name of the network interface.
@ -142,6 +162,7 @@ Addressed by *network*
"format-wifi": "{essid} ({signalStrength}%) ",
"format-ethernet": "{ifname} ",
"format-disconnected": "", //An empty format will hide the module.
"format-disconnected": "",
"tooltip-format": "{ifname}",
"tooltip-format-wifi": "{essid} ({signalStrength}%) ",
"tooltip-format-ethernet": "{ifname} ",
@ -154,6 +175,7 @@ Addressed by *network*
- *#network*
- *#network.disconnected*
- *#network.disabled*
- *#network.linked*
- *#network.ethernet*
- *#network.wifi*

View File

@ -50,6 +50,14 @@ Additionally you can control the volume by scrolling *up* or *down* while the cu
typeof: integer ++
The maximum length in character the module should display.
*min-length*: ++
typeof: integer ++
The minimum length in characters the module should take up.
*align*: ++
typeof: float ++
The alignment of the text, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text.
*scroll-step*: ++
typeof: float ++
default: 1.0 ++
@ -67,6 +75,10 @@ Additionally you can control the volume by scrolling *up* or *down* while the cu
typeof: string ++
Command to execute when you right clicked on the module.
*on-update*: ++
typeof: string ++
Command to execute when the module is updated.
*on-scroll-up*: ++
typeof: string ++
Command to execute when scrolling up on the module. This replaces the default behaviour of volume control.

View File

@ -0,0 +1,42 @@
waybar-river-tags(5)
# NAME
waybar - river tags module
# DESCRIPTION
The *tags* module displays the current state of tags in river.
# CONFIGURATION
Addressed by *river/tags*
*num-tags*: ++
typeof: uint ++
default: 9 ++
The number of tags that should be displayed.
*tag-labels*: ++
typeof: array ++
The label to display for each tag.
# EXAMPLE
```
"river/tags": {
"num-tags": 5
}
```
# STYLE
- *#tags button*
- *#tags button.occupied*
- *#tags button.focused*
Note that a tag can be both occupied and focused at the same time.
# SEE ALSO
waybar(5), river(1)

91
man/waybar-sndio.5.scd Normal file
View File

@ -0,0 +1,91 @@
waybar-sndio(5)
# NAME
waybar - sndio module
# DESCRIPTION
The *sndio* module displays the current volume reported by sndio(7).
Additionally, you can control the volume by scrolling *up* or *down* while the
cursor is over the module, and clicking on the module toggles mute.
# CONFIGURATION
*format*: ++
typeof: string ++
default: {volume}% ++
The format for how information should be displayed.
*rotate*: ++
typeof: integer ++
Positive value to rotate the text label.
*max-length*: ++
typeof: integer ++
The maximum length in character the module should display.
*min-length*: ++
typeof: integer ++
The minimum length in characters the module should take up.
*align*: ++
typeof: float ++
The alignment of the text, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text.
*scroll-step*: ++
typeof: int ++
default: 5 ++
The speed in which to change the volume when scrolling.
*on-click*: ++
typeof: string ++
Command to execute when clicked on the module.
This replaces the default behaviour of toggling mute.
*on-click-middle*: ++
typeof: string ++
Command to execute when middle-clicked on the module using mousewheel.
*on-click-right*: ++
typeof: string ++
Command to execute when you right clicked on the module.
*on-update*: ++
typeof: string ++
Command to execute when the module is updated.
*on-scroll-up*: ++
typeof: string ++
Command to execute when scrolling up on the module.
This replaces the default behaviour of volume control.
*on-scroll-down*: ++
typeof: string ++
Command to execute when scrolling down on the module.
This replaces the default behaviour of volume control.
*smooth-scrolling-threshold*: ++
typeof: double ++
Threshold to be used when scrolling.
# FORMAT REPLACEMENTS
*{volume}*: Volume in percentage.
*{raw_value}*: Volume as value reported by sndio.
# EXAMPLES
```
"sndio": {
"format": "{raw_value} 🎜",
"scroll-step": 3
}
```
# STYLE
- *#sndio*
- *#sndio.muted*

View File

@ -13,7 +13,7 @@ apply a class when the value matches the declared state value.
Each class gets activated when the current capacity is equal or below the configured *<value>*.
- Also each state can have its own *format*.
Those con be configured via *format-<name>*.
Those can be configured via *format-<name>*.
Or if you want to differentiate a bit more even as *format-<status>-<state>*.
# EXAMPLE

View File

@ -0,0 +1,80 @@
waybar-sway-language(5)
# NAME
waybar - sway language module
# DESCRIPTION
The *language* module displays the current keyboard layout in Sway
# CONFIGURATION
Addressed by *sway/language*
*format*: ++
typeof: string ++
default: {} ++
The format, how information should be displayed. On {} data gets inserted.
*rotate*: ++
typeof: integer ++
Positive value to rotate the text label.
*max-length*: ++
typeof: integer ++
The maximum length in character the module should display.
*min-length*: ++
typeof: integer ++
The minimum length in characters the module should take up.
*align*: ++
typeof: float ++
The alignment of the text, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text.
*on-click*: ++
typeof: string ++
Command to execute when clicked on the module.
*on-click-middle*: ++
typeof: string ++
Command to execute when middle-clicked on the module using mousewheel.
*on-click-right*: ++
typeof: string ++
Command to execute when you right clicked on the module.
*on-update*: ++
typeof: string ++
Command to execute when the module is updated.
*on-scroll-up*: ++
typeof: string ++
Command to execute when scrolling up on the module.
*on-scroll-down*: ++
typeof: string ++
Command to execute when scrolling down on the module.
*smooth-scrolling-threshold*: ++
typeof: double ++
Threshold to be used when scrolling.
*tooltip*: ++
typeof: bool ++
default: true ++
Option to disable tooltip on hover.
# EXAMPLES
```
"sway/language": {
"format": "{}",
"max-length": 50
}
```
# STYLE
- *#language*

View File

@ -25,6 +25,14 @@ Addressed by *sway/mode*
typeof: integer ++
The maximum length in character the module should display.
*min-length*: ++
typeof: integer ++
The minimum length in characters the module should take up.
*align*: ++
typeof: float ++
The alignment of the text, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text.
*on-click*: ++
typeof: string ++
Command to execute when clicked on the module.
@ -37,6 +45,10 @@ Addressed by *sway/mode*
typeof: string ++
Command to execute when you right clicked on the module.
*on-update*: ++
typeof: string ++
Command to execute when the module is updated.
*on-scroll-up*: ++
typeof: string ++
Command to execute when scrolling up on the module.

View File

@ -25,6 +25,14 @@ Addressed by *sway/window*
typeof: integer ++
The maximum length in character the module should display.
*min-length*: ++
typeof: integer ++
The minimum length in characters the module should take up.
*align*: ++
typeof: float ++
The alignment of the text, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text.
*on-click*: ++
typeof: string ++
Command to execute when clicked on the module.
@ -37,6 +45,10 @@ Addressed by *sway/window*
typeof: string ++
Command to execute when you right clicked on the module.
*on-update*: ++
typeof: string ++
Command to execute when the module is updated.
*on-scroll-up*: ++
typeof: string ++
Command to execute when scrolling up on the module.

View File

@ -31,6 +31,11 @@ Addressed by *sway/workspaces*
default: false ++
If set to false, you can scroll to cycle through workspaces. If set to true this behaviour is disabled.
*disable-click*: ++
typeof: bool ++
default: false ++
If set to false, you can click to change workspace. If set to true this behaviour is disabled.
*smooth-scrolling-threshold*: ++
typeof: double ++
Threshold to be used when scrolling.
@ -60,6 +65,18 @@ Addressed by *sway/workspaces*
default: empty ++
Lists workspaces that should always be shown, even when non existent
*on-update*: ++
typeof: string ++
Command to execute when the module is updated.
*numeric-first*: ++
typeof: bool ++
Whether to put workspaces starting with numbers before workspaces that do not start with a number.
*disable-auto-back-and-forth*: ++
typeof: bool ++
Whether to disable *workspace_auto_back_and_forth* when clicking on workspaces. If this is set to *true*, clicking on a workspace you are already on won't do anything, even if *workspace_auto_back_and_forth* is enabled in the Sway configuration.
# FORMAT REPLACEMENTS
*{value}*: Name of the workspace, as defined by sway.
@ -103,6 +120,7 @@ n.b.: the list of outputs can be obtained from command line using *swaymsg -t ge
"sway/workspaces": {
"disable-scroll": true,
"all-outputs": true,
"numeric-first": false,
"format": "{name}: {icon}",
"format-icons": {
"1": "",
@ -124,3 +142,5 @@ n.b.: the list of outputs can be obtained from command line using *swaymsg -t ge
- *#workspaces button.focused*
- *#workspaces button.urgent*
- *#workspaces button.persistent*
- *#workspaces button.current_output*
- *#workspaces button#sway-workspace-${name}*

View File

@ -44,12 +44,17 @@ Addressed by *temperature*
*format*: ++
typeof: string ++
default: {temperatureC}°C ++
The format (Celsius/Fahrenheit) in which the temperature should be displayed.
The format (Celsius/Fahrenheit/Kelvin) in which the temperature should be displayed.
*format-icons*: ++
typeof: array ++
Based on the current temperature (Celsius) and *critical-threshold* if available, the corresponding icon gets selected. The order is *low* to *high*.
*tooltip-format*: ++
typeof: string ++
default: {temperatureC}°C ++
The format for the tooltip
*rotate*: ++
typeof: integer ++
Positive value to rotate the text label.
@ -58,6 +63,14 @@ Addressed by *temperature*
typeof: integer ++
The maximum length in characters the module should display.
*min-length*: ++
typeof: integer ++
The minimum length in characters the module should take up.
*align*: ++
typeof: float ++
The alignment of the text, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text.
*on-click*: ++
typeof: string ++
Command to execute when you clicked on the module.
@ -70,6 +83,10 @@ Addressed by *temperature*
typeof: string ++
Command to execute when you right clicked on the module.
*on-update*: ++
typeof: string ++
Command to execute when the module is updated.
*on-scroll-up*: ++
typeof: string ++
Command to execute when scrolling up on the module.
@ -93,6 +110,8 @@ Addressed by *temperature*
*{temperatureF}*: Temperature in Fahrenheit.
*{temperatureK}*: Temperature in Kelvin.
# EXAMPLES
```

View File

@ -20,6 +20,10 @@ Addressed by *tray*
typeof: integer ++
Defines the spacing between the tray icons.
*on-update*: ++
typeof: string ++
Command to execute when the module is updated.
# EXAMPLES
```

View File

@ -0,0 +1,112 @@
waybar-wlr-taskbar(5)
# NAME
wlroots - Taskbar module
# DESCRIPTION
The *taskbar* module displays the currently open applications. This module requires
a compositor that implements the foreign-toplevel-manager interface.
# CONFIGURATION
Addressed by *wlr/taskbar*
*all-outputs*: ++
typeof: bool ++
default: false ++
If set to false applications on the waybar's current output will be shown. Otherwise all applications are shown.
*format*: ++
typeof: string ++
default: {icon} ++
The format, how information should be displayed.
*icon-theme*: ++
typeof: array|string ++
The names of the icon-themes that should be used to find an icon. The list will be traversed from left to right. If omitted, the system default will be used.
*icon-size*: ++
typeof: integer ++
default: 16 ++
The size of the icon.
*markup*: ++
typeof: bool ++
default: false ++
If set to true, pango markup will be accepted in format and tooltip-format.
*tooltip*: ++
typeof: bool ++
default: true ++
If set to false no tooltip will be shown.
*tooltip-format*: ++
typeof: string ++
default: {title} ++
The format, how information in the tooltip should be displayed.
*active-first*: ++
typeof: bool ++
default: false ++
If set to true, always reorder the tasks in the taskbar so that the currently active one is first. Otherwise don't reorder.
*on-click*: ++
typeof: string ++
The action which should be triggered when clicking on the application button with the left mouse button.
*on-click-middle*: ++
typeof: string ++
The action which should be triggered when clicking on the application button with the middle mouse button.
*on-click-right*: ++
typeof: string ++
The action which should be triggered when clicking on the application button with the right mouse button.
*on-update*: ++
typeof: string ++
Command to execute when the module is updated.
# FORMAT REPLACEMENTS
*{icon}*: The icon of the application.
*{title}*: The title of the application.
*{app_id}*: The app_id (== application name) of the application.
*{state}*: The state (minimized, maximized, active, fullscreen) of the application.
*{short_state}*: The state (minimize == m, maximized == M, active == A, fullscreen == F) represented as one character of the application.
# CLICK ACTIONS
*activate*: Bring the application into foreground.
*minimize*: Toggle application's minimized state.
*minimize-raise*: Bring the application into foreground or toggle its minimized state.
*maximize*: Toggle application's maximized state.
*fullscreen*: Toggle application's fullscreen state.
*close*: Close the application.
# EXAMPLES
```
"wlr/taskbar": {
"format": "{icon}",
"icon-size": 14,
"icon-theme": "Numix-Circle",
"tooltip-format": "{title}",
"on-click": "activate",
"on-click-middle": "close"
}
```
# Style
- *#taskbar*
- *#taskbar button*
- *#taskbar button.maximized*
- *#taskbar button.minimized*
- *#taskbar button.active*
- *#taskbar button.fullscreen*

View File

@ -14,8 +14,9 @@ Valid locations for this file are:
- *~/.config/waybar/config*
- *~/waybar/config*
- */etc/xdg/waybar/config*
- *@sysconfdir@/xdg/waybar/config*
A good starting point is the default configuration found at https://github.com/Alexays/Waybar/blob/master/resources/config.
A good starting point is the default configuration found at https://github.com/Alexays/Waybar/blob/master/resources/config
Also a minimal example configuration can be found on the at the bottom of this man page.
# BAR CONFIGURATION
@ -28,7 +29,7 @@ Also a minimal example configuration can be found on the at the bottom of this m
*output* ++
typeof: string|array ++
Specifies on which screen this bar will be displayed.
Specifies on which screen this bar will be displayed. Exclamation mark(*!*) can be used to exclude specific output.
*position* ++
typeof: string ++
@ -185,16 +186,21 @@ Valid options for the "rotate" property are: 0, 90, 180 and 270.
- *waybar-backlight(5)*
- *waybar-battery(5)*
- *waybar-bluetooth(5)*
- *waybar-clock(5)*
- *waybar-cpu(5)*
- *waybar-custom(5)*
- *waybar-disk(5)*
- *waybar-idle-inhibitor(5)*
- *waybar-memory(5)*
- *waybar-mdp(5)*
- *waybar-mpd(5)*
- *waybar-network(5)*
- *waybar-pulseaudio(5)*
- *waybar-river-tags(5)*
- *waybar-states(5)*
- *waybar-sway-mode(5)*
- *waybar-sway-window(5)*
- *waybar-sway-workspaces(5)*
- *waybar-wlr-taskbar(5)*
- *waybar-temperature(5)*
- *waybar-tray(5)*

View File

@ -1,7 +1,8 @@
project(
'waybar', 'cpp', 'c',
version: '0.9.2',
version: '0.9.7',
license: 'MIT',
meson_version: '>= 0.49.0',
default_options : [
'cpp_std=c++17',
'buildtype=release',
@ -17,11 +18,13 @@ cpp_link_args = []
if get_option('libcxx')
cpp_args += ['-stdlib=libc++']
cpp_link_args += ['-stdlib=libc++', '-lc++abi']
endif
if compiler.has_link_argument('-lc++fs')
cpp_link_args += ['-lc++fs']
endif
else
if compiler.has_link_argument('-lc++fs')
cpp_link_args += ['-lc++fs']
elif compiler.has_link_argument('-lc++experimental')
cpp_link_args += ['-lc++experimental']
elif compiler.has_link_argument('-lstdc++fs')
cpp_link_args += ['-lstdc++fs']
endif
@ -30,10 +33,15 @@ git = find_program('git', native: true, required: false)
if not git.found()
add_project_arguments('-DVERSION="@0@"'.format(meson.project_version()), language: 'cpp')
else
git_commit_hash = run_command([git.path(), 'describe', '--always', '--tags']).stdout().strip()
git_branch = run_command([git.path(), 'rev-parse', '--abbrev-ref', 'HEAD']).stdout().strip()
version = '"@0@ (" __DATE__ ", branch \'@1@\')"'.format(git_commit_hash, git_branch)
add_project_arguments('-DVERSION=@0@'.format(version), language: 'cpp')
git_path = run_command([git.path(), 'rev-parse', '--show-toplevel']).stdout().strip()
if meson.source_root() == git_path
git_commit_hash = run_command([git.path(), 'describe', '--always', '--tags']).stdout().strip()
git_branch = run_command([git.path(), 'rev-parse', '--abbrev-ref', 'HEAD']).stdout().strip()
version = '"@0@ (branch \'@1@\')"'.format(git_commit_hash, git_branch)
add_project_arguments('-DVERSION=@0@'.format(version), language: 'cpp')
else
add_project_arguments('-DVERSION="@0@"'.format(meson.project_version()), language: 'cpp')
endif
endif
if not compiler.has_header('filesystem')
@ -64,10 +72,15 @@ endif
add_global_arguments(cpp_args, language : 'cpp')
add_global_link_arguments(cpp_link_args, language : 'cpp')
is_linux = host_machine.system() == 'linux'
is_dragonfly = host_machine.system() == 'dragonfly'
is_freebsd = host_machine.system() == 'freebsd'
is_netbsd = host_machine.system() == 'netbsd'
is_openbsd = host_machine.system() == 'openbsd'
thread_dep = dependency('threads')
libinput = dependency('libinput')
fmt = dependency('fmt', version : ['>=5.3.0'], fallback : ['fmt', 'fmt_dep'])
spdlog = dependency('spdlog', version : ['>=1.3.1'], fallback : ['spdlog', 'spdlog_dep'])
spdlog = dependency('spdlog', version : ['>=1.8.0'], fallback : ['spdlog', 'spdlog_dep'], default_options : ['external_fmt=true'])
wayland_client = dependency('wayland-client')
wayland_cursor = dependency('wayland-cursor')
wayland_protos = dependency('wayland-protocols')
@ -76,21 +89,42 @@ dbusmenu_gtk = dependency('dbusmenu-gtk3-0.4', required: get_option('dbusmenu-gt
giounix = dependency('gio-unix-2.0', required: get_option('dbusmenu-gtk'))
jsoncpp = dependency('jsoncpp')
sigcpp = dependency('sigc++-2.0')
libepoll = dependency('epoll-shim', required: false)
libnl = dependency('libnl-3.0', required: get_option('libnl'))
libnlgen = dependency('libnl-genl-3.0', required: get_option('libnl'))
libpulse = dependency('libpulse', required: get_option('pulseaudio'))
libudev = dependency('libudev', required: get_option('libudev'))
libmpdclient = dependency('libmpdclient', required: get_option('mpd'))
libsndio = compiler.find_library('sndio', required: get_option('sndio'))
if libsndio.found()
if not compiler.has_function('sioctl_open', prefix: '#include <sndio.h>', dependencies: libsndio)
if get_option('sndio').enabled()
error('libsndio is too old, required >=1.7.0')
else
warning('libsndio is too old, required >=1.7.0')
libsndio = dependency('', required: false)
endif
endif
endif
gtk_layer_shell = dependency('gtk-layer-shell-0',
required: get_option('gtk-layer-shell'),
fallback : ['gtk-layer-shell', 'gtk_layer_shell_dep'])
systemd = dependency('systemd', required: get_option('systemd'))
tz_dep = dependency('date', default_options : [ 'use_system_tzdb=true' ], fallback: [ 'date', 'tz_dep' ])
tz_dep = dependency('date',
required: false,
default_options : [ 'use_system_tzdb=true' ],
modules : [ 'date::date', 'date::date-tz' ],
fallback: [ 'date', 'tz_dep' ])
prefix = get_option('prefix')
sysconfdir = get_option('sysconfdir')
conf_data = configuration_data()
conf_data.set('prefix', prefix)
add_project_arguments('-DSYSCONFDIR="/@0@"'.format(join_paths(prefix, sysconfdir)), language : 'cpp')
if systemd.found()
user_units_dir = systemd.get_pkgconfig_variable('systemduserunitdir')
@ -106,27 +140,54 @@ src_files = files(
'src/factory.cpp',
'src/AModule.cpp',
'src/ALabel.cpp',
'src/modules/memory.cpp',
'src/modules/battery.cpp',
'src/modules/clock.cpp',
'src/modules/custom.cpp',
'src/modules/cpu.cpp',
'src/modules/disk.cpp',
'src/modules/idle_inhibitor.cpp',
'src/modules/temperature.cpp',
'src/main.cpp',
'src/bar.cpp',
'src/client.cpp'
'src/client.cpp',
'src/util/ustring_clen.cpp'
)
if true # find_program('sway', required : false).found()
add_project_arguments('-DHAVE_SWAY', language: 'cpp')
src_files += [
'src/modules/sway/ipc/client.cpp',
'src/modules/sway/mode.cpp',
'src/modules/sway/window.cpp',
'src/modules/sway/workspaces.cpp'
]
if is_linux
add_project_arguments('-DHAVE_CPU_LINUX', language: 'cpp')
add_project_arguments('-DHAVE_MEMORY_LINUX', language: 'cpp')
src_files += files(
'src/modules/battery.cpp',
'src/modules/cpu/common.cpp',
'src/modules/cpu/linux.cpp',
'src/modules/memory/common.cpp',
'src/modules/memory/linux.cpp',
)
elif is_dragonfly or is_freebsd or is_netbsd or is_openbsd
add_project_arguments('-DHAVE_CPU_BSD', language: 'cpp')
add_project_arguments('-DHAVE_MEMORY_BSD', language: 'cpp')
src_files += files(
'src/modules/cpu/bsd.cpp',
'src/modules/cpu/common.cpp',
'src/modules/memory/bsd.cpp',
'src/modules/memory/common.cpp',
)
endif
add_project_arguments('-DHAVE_SWAY', language: 'cpp')
src_files += [
'src/modules/sway/ipc/client.cpp',
'src/modules/sway/mode.cpp',
'src/modules/sway/language.cpp',
'src/modules/sway/window.cpp',
'src/modules/sway/workspaces.cpp'
]
if true
add_project_arguments('-DHAVE_WLR', language: 'cpp')
src_files += 'src/modules/wlr/taskbar.cpp'
endif
if true
add_project_arguments('-DHAVE_RIVER', language: 'cpp')
src_files += 'src/modules/river/tags.cpp'
endif
if libnl.found() and libnlgen.found()
@ -149,20 +210,43 @@ if dbusmenu_gtk.found()
)
endif
if libudev.found()
if libudev.found() and (is_linux or libepoll.found())
add_project_arguments('-DHAVE_LIBUDEV', language: 'cpp')
src_files += 'src/modules/backlight.cpp'
endif
if libmpdclient.found()
add_project_arguments('-DHAVE_LIBMPDCLIENT', language: 'cpp')
src_files += 'src/modules/mpd.cpp'
src_files += 'src/modules/mpd/mpd.cpp'
src_files += 'src/modules/mpd/state.cpp'
endif
if gtk_layer_shell.found()
add_project_arguments('-DHAVE_GTK_LAYER_SHELL', language: 'cpp')
endif
if libsndio.found()
add_project_arguments('-DHAVE_LIBSNDIO', language: 'cpp')
src_files += 'src/modules/sndio.cpp'
endif
if get_option('rfkill').enabled()
if is_linux
add_project_arguments('-DWANT_RFKILL', language: 'cpp')
src_files += files(
'src/modules/bluetooth.cpp',
'src/util/rfkill.cpp'
)
endif
endif
if tz_dep.found()
add_project_arguments('-DHAVE_LIBDATE', language: 'cpp')
src_files += 'src/modules/clock.cpp'
else
src_files += 'src/modules/simpleclock.cpp'
endif
subdir('protocol')
executable(
@ -176,7 +260,6 @@ executable(
spdlog,
sigcpp,
jsoncpp,
libinput,
wayland_cursor,
gtkmm,
dbusmenu_gtk,
@ -185,8 +268,10 @@ executable(
libnlgen,
libpulse,
libudev,
libepoll,
libmpdclient,
gtk_layer_shell,
libsndio,
tz_dep
],
include_directories: [include_directories('include')],
@ -196,7 +281,7 @@ executable(
install_data(
'./resources/config',
'./resources/style.css',
install_dir: join_paths(get_option('out'), 'etc/xdg/waybar')
install_dir: sysconfdir + '/xdg/waybar'
)
scdoc = dependency('scdoc', version: '>=1.9.2', native: true, required: get_option('man-pages'))
@ -204,9 +289,20 @@ scdoc = dependency('scdoc', version: '>=1.9.2', native: true, required: get_opti
if scdoc.found()
scdoc_prog = find_program(scdoc.get_pkgconfig_variable('scdoc'), native: true)
sh = find_program('sh', native: true)
main_manpage = configure_file(
input: 'man/waybar.5.scd.in',
output: 'waybar.5.scd',
configuration: {
'sysconfdir': join_paths(prefix, sysconfdir)
}
)
main_manpage_path = join_paths(meson.build_root(), '@0@'.format(main_manpage))
mandir = get_option('mandir')
man_files = [
'waybar.5.scd',
main_manpage_path,
'waybar-backlight.5.scd',
'waybar-battery.5.scd',
'waybar-clock.5.scd',
@ -218,22 +314,31 @@ if scdoc.found()
'waybar-mpd.5.scd',
'waybar-network.5.scd',
'waybar-pulseaudio.5.scd',
'waybar-river-tags.5.scd',
'waybar-sway-language.5.scd',
'waybar-sway-mode.5.scd',
'waybar-sway-window.5.scd',
'waybar-sway-workspaces.5.scd',
'waybar-temperature.5.scd',
'waybar-tray.5.scd',
'waybar-states.5.scd',
'waybar-wlr-taskbar.5.scd',
'waybar-bluetooth.5.scd',
'waybar-sndio.5.scd',
]
foreach filename : man_files
topic = filename.split('.')[-3].split('/')[-1]
section = filename.split('.')[-2]
foreach file : man_files
path = '@0@'.format(file)
basename = path.split('/')[-1]
topic = basename.split('.')[-3]
section = basename.split('.')[-2]
output = '@0@.@1@'.format(topic, section)
custom_target(
output,
input: 'man/@0@'.format(filename),
# drops the 'man' if `path` is an absolute path
input: join_paths('man', path),
output: output,
command: [
sh, '-c', '@0@ < @INPUT@ > @1@'.format(scdoc_prog.path(), output)

View File

@ -6,5 +6,6 @@ option('systemd', type: 'feature', value: 'auto', description: 'Install systemd
option('dbusmenu-gtk', type: 'feature', value: 'auto', description: 'Enable support for tray')
option('man-pages', type: 'feature', value: 'auto', description: 'Generate and install man pages')
option('mpd', type: 'feature', value: 'auto', description: 'Enable support for the Music Player Daemon')
option('out', type: 'string', value : '/', description: 'output prefix directory')
option('gtk-layer-shell', type: 'feature', value: 'auto', description: 'Use gtk-layer-shell library for popups support')
option('rfkill', type: 'feature', value: 'auto', description: 'Enable support for RFKILL')
option('sndio', type: 'feature', value: 'auto', description: 'Enable support for sndio')

View File

@ -26,6 +26,8 @@ client_protocols = [
[wl_protocol_dir, 'unstable/xdg-output/xdg-output-unstable-v1.xml'],
[wl_protocol_dir, 'unstable/idle-inhibit/idle-inhibit-unstable-v1.xml'],
['wlr-layer-shell-unstable-v1.xml'],
['wlr-foreign-toplevel-management-unstable-v1.xml'],
['river-status-unstable-v1.xml'],
]
client_protos_src = []

View File

@ -0,0 +1,116 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="river_status_unstable_v1">
<copyright>
Copyright 2020 Isaac Freund
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
</copyright>
<interface name="zriver_status_manager_v1" version="1">
<description summary="manage river status objects">
A global factory for objects that receive status information specific
to river. It could be used to implement, for example, a status bar.
</description>
<request name="destroy" type="destructor">
<description summary="destroy the river_status_manager object">
This request indicates that the client will not use the
river_status_manager object any more. Objects that have been created
through this instance are not affected.
</description>
</request>
<request name="get_river_output_status">
<description summary="create an output status object">
This creates a new river_output_status object for the given wl_output.
</description>
<arg name="id" type="new_id" interface="zriver_output_status_v1"/>
<arg name="output" type="object" interface="wl_output"/>
</request>
<request name="get_river_seat_status">
<description summary="create a seat status object">
This creates a new river_seat_status object for the given wl_seat.
</description>
<arg name="id" type="new_id" interface="zriver_seat_status_v1"/>
<arg name="seat" type="object" interface="wl_seat"/>
</request>
</interface>
<interface name="zriver_output_status_v1" version="1">
<description summary="track output tags and focus">
This interface allows clients to receive information about the current
windowing state of an output.
</description>
<request name="destroy" type="destructor">
<description summary="destroy the river_output_status object">
This request indicates that the client will not use the
river_output_status object any more.
</description>
</request>
<event name="focused_tags">
<description summary="focused tags of the output">
Sent once binding the interface and again whenever the tag focus of
the output changes.
</description>
<arg name="tags" type="uint" summary="32-bit bitfield"/>
</event>
<event name="view_tags">
<description summary="tag state of an output's views">
Sent once on binding the interface and again whenever the tag state
of the output changes.
</description>
<arg name="tags" type="array" summary="array of 32-bit bitfields"/>
</event>
</interface>
<interface name="zriver_seat_status_v1" version="1">
<description summary="track seat focus">
This interface allows clients to receive information about the current
focus of a seat.
</description>
<request name="destroy" type="destructor">
<description summary="destroy the river_seat_status object">
This request indicates that the client will not use the
river_seat_status object any more.
</description>
</request>
<event name="focused_output">
<description summary="the seat focused an output">
Sent on binding the interface and again whenever an output gains focus.
</description>
<arg name="output" type="object" interface="wl_output"/>
</event>
<event name="unfocused_output">
<description summary="the seat unfocused an output">
Sent whenever an output loses focus.
</description>
<arg name="output" type="object" interface="wl_output"/>
</event>
<event name="focused_view">
<description summary="information on the focused view">
Sent once on binding the interface and again whenever the focused
view or a property thereof changes. The title may be an empty string
if no view is focused or the focused view did not set a title.
</description>
<arg name="title" type="string" summary="title of the focused view"/>
</event>
</interface>
</protocol>

View File

@ -0,0 +1,270 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="wlr_foreign_toplevel_management_unstable_v1">
<copyright>
Copyright © 2018 Ilia Bozhinov
Permission to use, copy, modify, distribute, and sell this
software and its documentation for any purpose is hereby granted
without fee, provided that the above copyright notice appear in
all copies and that both that copyright notice and this permission
notice appear in supporting documentation, and that the name of
the copyright holders not be used in advertising or publicity
pertaining to distribution of the software without specific,
written prior permission. The copyright holders make no
representations about the suitability of this software for any
purpose. It is provided "as is" without express or implied
warranty.
THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.
</copyright>
<interface name="zwlr_foreign_toplevel_manager_v1" version="3">
<description summary="list and control opened apps">
The purpose of this protocol is to enable the creation of taskbars
and docks by providing them with a list of opened applications and
letting them request certain actions on them, like maximizing, etc.
After a client binds the zwlr_foreign_toplevel_manager_v1, each opened
toplevel window will be sent via the toplevel event
</description>
<event name="toplevel">
<description summary="a toplevel has been created">
This event is emitted whenever a new toplevel window is created. It
is emitted for all toplevels, regardless of the app that has created
them.
All initial details of the toplevel(title, app_id, states, etc.) will
be sent immediately after this event via the corresponding events in
zwlr_foreign_toplevel_handle_v1.
</description>
<arg name="toplevel" type="new_id" interface="zwlr_foreign_toplevel_handle_v1"/>
</event>
<request name="stop">
<description summary="stop sending events">
Indicates the client no longer wishes to receive events for new toplevels.
However the compositor may emit further toplevel_created events, until
the finished event is emitted.
The client must not send any more requests after this one.
</description>
</request>
<event name="finished">
<description summary="the compositor has finished with the toplevel manager">
This event indicates that the compositor is done sending events to the
zwlr_foreign_toplevel_manager_v1. The server will destroy the object
immediately after sending this request, so it will become invalid and
the client should free any resources associated with it.
</description>
</event>
</interface>
<interface name="zwlr_foreign_toplevel_handle_v1" version="3">
<description summary="an opened toplevel">
A zwlr_foreign_toplevel_handle_v1 object represents an opened toplevel
window. Each app may have multiple opened toplevels.
Each toplevel has a list of outputs it is visible on, conveyed to the
client with the output_enter and output_leave events.
</description>
<event name="title">
<description summary="title change">
This event is emitted whenever the title of the toplevel changes.
</description>
<arg name="title" type="string"/>
</event>
<event name="app_id">
<description summary="app-id change">
This event is emitted whenever the app-id of the toplevel changes.
</description>
<arg name="app_id" type="string"/>
</event>
<event name="output_enter">
<description summary="toplevel entered an output">
This event is emitted whenever the toplevel becomes visible on
the given output. A toplevel may be visible on multiple outputs.
</description>
<arg name="output" type="object" interface="wl_output"/>
</event>
<event name="output_leave">
<description summary="toplevel left an output">
This event is emitted whenever the toplevel stops being visible on
the given output. It is guaranteed that an entered-output event
with the same output has been emitted before this event.
</description>
<arg name="output" type="object" interface="wl_output"/>
</event>
<request name="set_maximized">
<description summary="requests that the toplevel be maximized">
Requests that the toplevel be maximized. If the maximized state actually
changes, this will be indicated by the state event.
</description>
</request>
<request name="unset_maximized">
<description summary="requests that the toplevel be unmaximized">
Requests that the toplevel be unmaximized. If the maximized state actually
changes, this will be indicated by the state event.
</description>
</request>
<request name="set_minimized">
<description summary="requests that the toplevel be minimized">
Requests that the toplevel be minimized. If the minimized state actually
changes, this will be indicated by the state event.
</description>
</request>
<request name="unset_minimized">
<description summary="requests that the toplevel be unminimized">
Requests that the toplevel be unminimized. If the minimized state actually
changes, this will be indicated by the state event.
</description>
</request>
<request name="activate">
<description summary="activate the toplevel">
Request that this toplevel be activated on the given seat.
There is no guarantee the toplevel will be actually activated.
</description>
<arg name="seat" type="object" interface="wl_seat"/>
</request>
<enum name="state">
<description summary="types of states on the toplevel">
The different states that a toplevel can have. These have the same meaning
as the states with the same names defined in xdg-toplevel
</description>
<entry name="maximized" value="0" summary="the toplevel is maximized"/>
<entry name="minimized" value="1" summary="the toplevel is minimized"/>
<entry name="activated" value="2" summary="the toplevel is active"/>
<entry name="fullscreen" value="3" summary="the toplevel is fullscreen" since="2"/>
</enum>
<event name="state">
<description summary="the toplevel state changed">
This event is emitted immediately after the zlw_foreign_toplevel_handle_v1
is created and each time the toplevel state changes, either because of a
compositor action or because of a request in this protocol.
</description>
<arg name="state" type="array"/>
</event>
<event name="done">
<description summary="all information about the toplevel has been sent">
This event is sent after all changes in the toplevel state have been
sent.
This allows changes to the zwlr_foreign_toplevel_handle_v1 properties
to be seen as atomic, even if they happen via multiple events.
</description>
</event>
<request name="close">
<description summary="request that the toplevel be closed">
Send a request to the toplevel to close itself. The compositor would
typically use a shell-specific method to carry out this request, for
example by sending the xdg_toplevel.close event. However, this gives
no guarantees the toplevel will actually be destroyed. If and when
this happens, the zwlr_foreign_toplevel_handle_v1.closed event will
be emitted.
</description>
</request>
<request name="set_rectangle">
<description summary="the rectangle which represents the toplevel">
The rectangle of the surface specified in this request corresponds to
the place where the app using this protocol represents the given toplevel.
It can be used by the compositor as a hint for some operations, e.g
minimizing. The client is however not required to set this, in which
case the compositor is free to decide some default value.
If the client specifies more than one rectangle, only the last one is
considered.
The dimensions are given in surface-local coordinates.
Setting width=height=0 removes the already-set rectangle.
</description>
<arg name="surface" type="object" interface="wl_surface"/>
<arg name="x" type="int"/>
<arg name="y" type="int"/>
<arg name="width" type="int"/>
<arg name="height" type="int"/>
</request>
<enum name="error">
<entry name="invalid_rectangle" value="0"
summary="the provided rectangle is invalid"/>
</enum>
<event name="closed">
<description summary="this toplevel has been destroyed">
This event means the toplevel has been destroyed. It is guaranteed there
won't be any more events for this zwlr_foreign_toplevel_handle_v1. The
toplevel itself becomes inert so any requests will be ignored except the
destroy request.
</description>
</event>
<request name="destroy" type="destructor">
<description summary="destroy the zwlr_foreign_toplevel_handle_v1 object">
Destroys the zwlr_foreign_toplevel_handle_v1 object.
This request should be called either when the client does not want to
use the toplevel anymore or after the closed event to finalize the
destruction of the object.
</description>
</request>
<!-- Version 2 additions -->
<request name="set_fullscreen" since="2">
<description summary="request that the toplevel be fullscreened">
Requests that the toplevel be fullscreened on the given output. If the
fullscreen state and/or the outputs the toplevel is visible on actually
change, this will be indicated by the state and output_enter/leave
events.
The output parameter is only a hint to the compositor. Also, if output
is NULL, the compositor should decide which output the toplevel will be
fullscreened on, if at all.
</description>
<arg name="output" type="object" interface="wl_output" allow-null="true"/>
</request>
<request name="unset_fullscreen" since="2">
<description summary="request that the toplevel be unfullscreened">
Requests that the toplevel be unfullscreened. If the fullscreen state
actually changes, this will be indicated by the state event.
</description>
</request>
<!-- Version 3 additions -->
<event name="parent" since="3">
<description summary="parent change">
This event is emitted whenever the parent of the toplevel changes.
No event is emitted when the parent handle is destroyed by the client.
</description>
<arg name="parent" type="object" interface="zwlr_foreign_toplevel_handle_v1" allow-null="true"/>
</event>
</interface>
</protocol>

View File

@ -25,7 +25,7 @@
THIS SOFTWARE.
</copyright>
<interface name="zwlr_layer_shell_v1" version="1">
<interface name="zwlr_layer_shell_v1" version="3">
<description summary="create surfaces that are layers of the desktop">
Clients can use this interface to assign the surface_layer role to
wl_surfaces. Such surfaces are assigned to a "layer" of the output and
@ -82,17 +82,27 @@
<entry name="top" value="2"/>
<entry name="overlay" value="3"/>
</enum>
<!-- Version 3 additions -->
<request name="destroy" type="destructor" since="3">
<description summary="destroy the layer_shell object">
This request indicates that the client will not use the layer_shell
object any more. Objects that have been created through this instance
are not affected.
</description>
</request>
</interface>
<interface name="zwlr_layer_surface_v1" version="1">
<interface name="zwlr_layer_surface_v1" version="3">
<description summary="layer metadata interface">
An interface that may be implemented by a wl_surface, for surfaces that
are designed to be rendered as a layer of a stacked desktop-like
environment.
Layer surface state (size, anchor, exclusive zone, margin, interactivity)
is double-buffered, and will be applied at the time wl_surface.commit of
the corresponding wl_surface is called.
Layer surface state (layer, size, anchor, exclusive zone,
margin, interactivity) is double-buffered, and will be applied at the
time wl_surface.commit of the corresponding wl_surface is called.
</description>
<request name="set_size">
@ -115,7 +125,7 @@
<request name="set_anchor">
<description summary="configures the anchor point of the surface">
Requests that the compositor anchor the surface to the specified edges
and corners. If two orthoginal edges are specified (e.g. 'top' and
and corners. If two orthogonal edges are specified (e.g. 'top' and
'left'), then the anchor point will be the intersection of the edges
(e.g. the top left corner of the output); otherwise the anchor point
will be centered on that edge, or in the center if none is specified.
@ -127,20 +137,25 @@
<request name="set_exclusive_zone">
<description summary="configures the exclusive geometry of this surface">
Requests that the compositor avoids occluding an area of the surface
with other surfaces. The compositor's use of this information is
Requests that the compositor avoids occluding an area with other
surfaces. The compositor's use of this information is
implementation-dependent - do not assume that this region will not
actually be occluded.
A positive value is only meaningful if the surface is anchored to an
edge, rather than a corner. The zone is the number of surface-local
coordinates from the edge that are considered exclusive.
A positive value is only meaningful if the surface is anchored to one
edge or an edge and both perpendicular edges. If the surface is not
anchored, anchored to only two perpendicular edges (a corner), anchored
to only two parallel edges or anchored to all edges, a positive value
will be treated the same as zero.
A positive zone is the distance from the edge in surface-local
coordinates to consider exclusive.
Surfaces that do not wish to have an exclusive zone may instead specify
how they should interact with surfaces that do. If set to zero, the
surface indicates that it would like to be moved to avoid occluding
surfaces with a positive excluzive zone. If set to -1, the surface
indicates that it would not like to be moved to accomodate for other
surfaces with a positive exclusive zone. If set to -1, the surface
indicates that it would not like to be moved to accommodate for other
surfaces, and the compositor should extend it all the way to the edges
it is anchored to.
@ -281,5 +296,16 @@
<entry name="left" value="4" summary="the left edge of the anchor rectangle"/>
<entry name="right" value="8" summary="the right edge of the anchor rectangle"/>
</enum>
<!-- Version 2 additions -->
<request name="set_layer" since="2">
<description summary="change the layer of the surface">
Change the layer that the surface is rendered on.
Layer is double-buffered, see wl_surface.commit.
</description>
<arg name="layer" type="uint" enum="zwlr_layer_shell_v1.layer" summary="layer to move this surface to"/>
</request>
</interface>
</protocol>

View File

@ -6,7 +6,7 @@
// Choose the order of the modules
"modules-left": ["sway/workspaces", "sway/mode", "custom/media"],
"modules-center": ["sway/window"],
"modules-right": ["mpd", "idle_inhibitor", "pulseaudio", "network", "cpu", "memory", "temperature", "backlight", "battery", "battery#bat2", "clock", "tray"],
"modules-right": ["mpd", "idle_inhibitor", "pulseaudio", "network", "cpu", "memory", "temperature", "backlight", "sway/language", "battery", "battery#bat2", "clock", "tray"],
// Modules configuration
// "sway/workspaces": {
// "disable-scroll": true,
@ -27,7 +27,7 @@
"format": "<span style=\"italic\">{}</span>"
},
"mpd": {
"format": "{stateIcon} {consumeIcon}{randomIcon}{repeatIcon}{singleIcon}{artist} - {album} - {title} ({elapsedTime:%M:%S}/{totalTime:%M:%S}) ",
"format": "{stateIcon} {consumeIcon}{randomIcon}{repeatIcon}{singleIcon}{artist} - {album} - {title} ({elapsedTime:%M:%S}/{totalTime:%M:%S}) ⸨{songPosition}|{queueLength}⸩ ",
"format-disconnected": "Disconnected ",
"format-stopped": "{consumeIcon}{randomIcon}{repeatIcon}{singleIcon}Stopped ",
"unknown-tag": "N/A",

View File

@ -41,19 +41,19 @@ window#waybar.chromium {
padding: 0 5px;
background-color: transparent;
color: #ffffff;
border-bottom: 3px solid transparent;
/* Use box-shadow instead of border so the text isn't offset */
box-shadow: inset 0 -3px transparent;
}
/* https://github.com/Alexays/Waybar/wiki/FAQ#the-workspace-buttons-have-a-strange-hover-effect */
#workspaces button:hover {
background: rgba(0, 0, 0, 0.2);
box-shadow: inherit;
border-bottom: 3px solid #ffffff;
box-shadow: inset 0 -3px #ffffff;
}
#workspaces button.focused {
background-color: #64727D;
border-bottom: 3px solid #ffffff;
box-shadow: inset 0 -3px #ffffff;
}
#workspaces button.urgent {
@ -69,6 +69,7 @@ window#waybar.chromium {
#battery,
#cpu,
#memory,
#disk,
#temperature,
#backlight,
#network,
@ -83,6 +84,21 @@ window#waybar.chromium {
color: #ffffff;
}
#window,
#workspaces {
margin: 0 4px;
}
/* If workspaces is the leftmost module, omit left margin */
.modules-left > widget:first-child > #workspaces {
margin-left: 0;
}
/* If workspaces is the rightmost module, omit right margin */
.modules-right > widget:last-child > #workspaces {
margin-right: 0;
}
#clock {
background-color: #64727D;
}
@ -92,7 +108,7 @@ window#waybar.chromium {
color: #000000;
}
#battery.charging {
#battery.charging, #battery.plugged {
color: #ffffff;
background-color: #26A65B;
}
@ -127,6 +143,10 @@ label:focus {
background-color: #9b59b6;
}
#disk {
background-color: #964B00;
}
#backlight {
background-color: #90b1b1;
}
@ -200,3 +220,11 @@ label:focus {
#mpd.paused {
background-color: #51a37a;
}
#language {
background: #00b093;
color: #740864;
padding: 0 5px;
margin: 0 5px;
min-width: 16px;
}

View File

@ -1,13 +1,13 @@
[Unit]
Description=Highly customizable Wayland bar for Sway and Wlroots based compositors.
Documentation=https://github.com/Alexays/Waybar/wiki/
PartOf=wayland-session.target
After=wayland-session.target
PartOf=graphical-session.target
After=graphical-session.target
[Service]
Type=dbus
BusName=fr.arouillard.waybar
ExecStart=@prefix@/bin/waybar
ExecReload=kill -SIGUSR2 $MAINPID
Restart=on-failure
[Install]
WantedBy=wayland-session.target
WantedBy=graphical-session.target

View File

@ -5,8 +5,9 @@
namespace waybar {
ALabel::ALabel(const Json::Value& config, const std::string& name, const std::string& id,
const std::string& format, uint16_t interval, bool ellipsize)
: AModule(config, name, id, config["format-alt"].isString()),
const std::string& format, uint16_t interval, bool ellipsize, bool enable_click,
bool enable_scroll)
: AModule(config, name, id, config["format-alt"].isString() || enable_click, enable_scroll),
format_(config_["format"].isString() ? config_["format"].asString() : format),
interval_(config_["interval"] == "once"
? std::chrono::seconds(100000000)
@ -19,19 +20,40 @@ ALabel::ALabel(const Json::Value& config, const std::string& name, const std::st
}
event_box_.add(label_);
if (config_["max-length"].isUInt()) {
label_.set_max_width_chars(config_["max-length"].asUInt());
label_.set_max_width_chars(config_["max-length"].asInt());
label_.set_ellipsize(Pango::EllipsizeMode::ELLIPSIZE_END);
label_.set_single_line_mode(true);
} else if (ellipsize && label_.get_max_width_chars() == -1) {
label_.set_ellipsize(Pango::EllipsizeMode::ELLIPSIZE_END);
label_.set_single_line_mode(true);
}
if (config_["rotate"].isUInt()) {
label_.set_angle(config["rotate"].asUInt());
if (config_["min-length"].isUInt()) {
label_.set_width_chars(config_["min-length"].asUInt());
}
uint rotate = 0;
if (config_["rotate"].isUInt()) {
rotate = config["rotate"].asUInt();
label_.set_angle(rotate);
}
if (config_["align"].isDouble()) {
auto align = config_["align"].asFloat();
if (rotate == 90 || rotate == 270) {
label_.set_yalign(align);
} else {
label_.set_xalign(align);
}
}
}
auto ALabel::update() -> void {
// Nothing here
AModule::update();
}
std::string ALabel::getIcon(uint16_t percentage, const std::string& alt, uint16_t max) {
@ -54,6 +76,29 @@ std::string ALabel::getIcon(uint16_t percentage, const std::string& alt, uint16_
return "";
}
std::string ALabel::getIcon(uint16_t percentage, std::vector<std::string>& alts, uint16_t max) {
auto format_icons = config_["format-icons"];
if (format_icons.isObject()) {
std::string _alt = "default";
for (const auto& alt : alts) {
if (!alt.empty() && (format_icons[alt].isString() || format_icons[alt].isArray())) {
_alt = alt;
break;
}
}
format_icons = format_icons[_alt];
}
if (format_icons.isArray()) {
auto size = format_icons.size();
auto idx = std::clamp(percentage / ((max == 0 ? 100 : max) / size), 0U, size - 1);
format_icons = format_icons[idx];
}
if (format_icons.isString()) {
return format_icons.asString();
}
return "";
}
bool waybar::ALabel::handleToggle(GdkEventButton* const& e) {
if (config_["format-alt-click"].isUInt() && e->button == config_["format-alt-click"].asUInt()) {
alt_ = !alt_;

View File

@ -6,7 +6,7 @@ namespace waybar {
AModule::AModule(const Json::Value& config, const std::string& name, const std::string& id,
bool enable_click, bool enable_scroll)
: config_(std::move(config)) {
: name_(std::move(name)), config_(std::move(config)) {
// configure events' user commands
if (config_["on-click"].isString() || config_["on-click-middle"].isString() ||
config_["on-click-backward"].isString() || config_["on-click-forward"].isString() ||
@ -23,13 +23,17 @@ AModule::AModule(const Json::Value& config, const std::string& name, const std::
AModule::~AModule() {
for (const auto& pid : pid_) {
if (pid != -1) {
kill(-pid, 9);
killpg(pid, SIGTERM);
}
}
}
auto AModule::update() -> void {
// Nothing here
// Run user-provided update handler if configured
if (config_["on-update"].isString()) {
pid_.push_back(util::command::forkExec(config_["on-update"].asString()));
}
}
bool AModule::handleToggle(GdkEventButton* const& e) {
@ -40,9 +44,9 @@ bool AModule::handleToggle(GdkEventButton* const& e) {
format = config_["on-click-middle"].asString();
} else if (config_["on-click-right"].isString() && e->button == 3) {
format = config_["on-click-right"].asString();
} else if (config_["on-click-forward"].isString() && e->button == 8) {
} else if (config_["on-click-backward"].isString() && e->button == 8) {
format = config_["on-click-backward"].asString();
} else if (config_["on-click-backward"].isString() && e->button == 9) {
} else if (config_["on-click-forward"].isString() && e->button == 9) {
format = config_["on-click-forward"].asString();
}
if (!format.empty()) {

View File

@ -2,18 +2,364 @@
#include <gtk-layer-shell.h>
#endif
#include <spdlog/spdlog.h>
#include <type_traits>
#include "bar.hpp"
#include "client.hpp"
#include "factory.hpp"
#include <spdlog/spdlog.h>
#include "wlr-layer-shell-unstable-v1-client-protocol.h"
namespace waybar {
static constexpr const char* MIN_HEIGHT_MSG =
"Requested height: {} exceeds the minimum height: {} required by the modules";
static constexpr const char* MIN_WIDTH_MSG =
"Requested width: {} exceeds the minimum width: {} required by the modules";
static constexpr const char* BAR_SIZE_MSG = "Bar configured (width: {}, height: {}) for output: {}";
static constexpr const char* SIZE_DEFINED =
"{} size is defined in the config file so it will stay like that";
#ifdef HAVE_GTK_LAYER_SHELL
struct GLSSurfaceImpl : public BarSurface, public sigc::trackable {
GLSSurfaceImpl(Gtk::Window& window, struct waybar_output& output) : window_{window} {
output_name_ = output.name;
// this has to be executed before GtkWindow.realize
gtk_layer_init_for_window(window_.gobj());
gtk_layer_set_keyboard_interactivity(window.gobj(), FALSE);
gtk_layer_set_monitor(window_.gobj(), output.monitor->gobj());
gtk_layer_set_namespace(window_.gobj(), "waybar");
window.signal_configure_event().connect_notify(
sigc::mem_fun(*this, &GLSSurfaceImpl::onConfigure));
}
void setExclusiveZone(bool enable) override {
if (enable) {
gtk_layer_auto_exclusive_zone_enable(window_.gobj());
} else {
gtk_layer_set_exclusive_zone(window_.gobj(), 0);
}
}
void setMargins(const struct bar_margins& margins) override {
gtk_layer_set_margin(window_.gobj(), GTK_LAYER_SHELL_EDGE_LEFT, margins.left);
gtk_layer_set_margin(window_.gobj(), GTK_LAYER_SHELL_EDGE_RIGHT, margins.right);
gtk_layer_set_margin(window_.gobj(), GTK_LAYER_SHELL_EDGE_TOP, margins.top);
gtk_layer_set_margin(window_.gobj(), GTK_LAYER_SHELL_EDGE_BOTTOM, margins.bottom);
}
void setLayer(bar_layer value) override {
auto layer = GTK_LAYER_SHELL_LAYER_BOTTOM;
if (value == bar_layer::TOP) {
layer = GTK_LAYER_SHELL_LAYER_TOP;
} else if (value == bar_layer::OVERLAY) {
layer = GTK_LAYER_SHELL_LAYER_OVERLAY;
}
gtk_layer_set_layer(window_.gobj(), layer);
}
void setPosition(const std::string_view& position) override {
auto unanchored = GTK_LAYER_SHELL_EDGE_BOTTOM;
vertical_ = false;
if (position == "bottom") {
unanchored = GTK_LAYER_SHELL_EDGE_TOP;
} else if (position == "left") {
unanchored = GTK_LAYER_SHELL_EDGE_RIGHT;
vertical_ = true;
} else if (position == "right") {
vertical_ = true;
unanchored = GTK_LAYER_SHELL_EDGE_LEFT;
}
for (auto edge : {GTK_LAYER_SHELL_EDGE_LEFT,
GTK_LAYER_SHELL_EDGE_RIGHT,
GTK_LAYER_SHELL_EDGE_TOP,
GTK_LAYER_SHELL_EDGE_BOTTOM}) {
gtk_layer_set_anchor(window_.gobj(), edge, unanchored != edge);
}
}
void setSize(uint32_t width, uint32_t height) override {
width_ = width;
height_ = height;
window_.set_size_request(width_, height_);
};
private:
Gtk::Window& window_;
std::string output_name_;
uint32_t width_;
uint32_t height_;
bool vertical_ = false;
void onConfigure(GdkEventConfigure* ev) {
/*
* GTK wants new size for the window.
* Actual resizing and management of the exclusve zone is handled within the gtk-layer-shell
* code. This event handler only updates stored size of the window and prints some warnings.
*
* Note: forced resizing to a window smaller than required by GTK would not work with
* gtk-layer-shell.
*/
if (vertical_) {
if (width_ > 1 && ev->width > static_cast<int>(width_)) {
spdlog::warn(MIN_WIDTH_MSG, width_, ev->width);
}
} else {
if (height_ > 1 && ev->height > static_cast<int>(height_)) {
spdlog::warn(MIN_HEIGHT_MSG, height_, ev->height);
}
}
width_ = ev->width;
height_ = ev->height;
spdlog::info(BAR_SIZE_MSG, width_, height_, output_name_);
}
};
#endif
struct RawSurfaceImpl : public BarSurface, public sigc::trackable {
RawSurfaceImpl(Gtk::Window& window, struct waybar_output& output) : window_{window} {
output_ = gdk_wayland_monitor_get_wl_output(output.monitor->gobj());
output_name_ = output.name;
window.signal_realize().connect_notify(sigc::mem_fun(*this, &RawSurfaceImpl::onRealize));
window.signal_map_event().connect_notify(sigc::mem_fun(*this, &RawSurfaceImpl::onMap));
window.signal_configure_event().connect_notify(
sigc::mem_fun(*this, &RawSurfaceImpl::onConfigure));
if (window.get_realized()) {
onRealize();
}
}
void setExclusiveZone(bool enable) override {
exclusive_zone_ = enable;
if (layer_surface_) {
auto zone = 0;
if (enable) {
// exclusive zone already includes margin for anchored edge,
// only opposite margin should be added
if ((anchor_ & VERTICAL_ANCHOR) == VERTICAL_ANCHOR) {
zone += width_;
zone += (anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT) ? margins_.right : margins_.left;
} else {
zone += height_;
zone += (anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP) ? margins_.bottom : margins_.top;
}
}
spdlog::debug("Set exclusive zone {} for output {}", zone, output_name_);
zwlr_layer_surface_v1_set_exclusive_zone(layer_surface_.get(), zone);
}
}
void setLayer(bar_layer layer) override {
layer_ = ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM;
if (layer == bar_layer::TOP) {
layer_ = ZWLR_LAYER_SHELL_V1_LAYER_TOP;
} else if (layer == bar_layer::OVERLAY) {
layer_ = ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY;
}
// updating already mapped window
if (layer_surface_) {
if (zwlr_layer_surface_v1_get_version(layer_surface_.get()) >=
ZWLR_LAYER_SURFACE_V1_SET_LAYER_SINCE_VERSION) {
zwlr_layer_surface_v1_set_layer(layer_surface_.get(), layer_);
} else {
spdlog::warn("Unable to change layer: layer-shell implementation is too old");
}
}
}
void setMargins(const struct bar_margins& margins) override {
margins_ = margins;
// updating already mapped window
if (layer_surface_) {
zwlr_layer_surface_v1_set_margin(
layer_surface_.get(), margins_.top, margins_.right, margins_.bottom, margins_.left);
}
}
void setPosition(const std::string_view& position) override {
anchor_ = HORIZONTAL_ANCHOR | ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP;
if (position == "bottom") {
anchor_ = HORIZONTAL_ANCHOR | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM;
} else if (position == "left") {
anchor_ = VERTICAL_ANCHOR | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT;
} else if (position == "right") {
anchor_ = VERTICAL_ANCHOR | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
}
// updating already mapped window
if (layer_surface_) {
zwlr_layer_surface_v1_set_anchor(layer_surface_.get(), anchor_);
}
}
void setSize(uint32_t width, uint32_t height) override {
configured_width_ = width_ = width;
configured_height_ = height_ = height;
// layer_shell.configure handler should update exclusive zone if size changes
window_.set_size_request(width, height);
};
void commit() override {
if (surface_) {
wl_surface_commit(surface_);
}
}
private:
constexpr static uint8_t VERTICAL_ANCHOR =
ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM;
constexpr static uint8_t HORIZONTAL_ANCHOR =
ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
template <auto fn>
using deleter_fn = std::integral_constant<decltype(fn), fn>;
using layer_surface_ptr =
std::unique_ptr<zwlr_layer_surface_v1, deleter_fn<zwlr_layer_surface_v1_destroy>>;
Gtk::Window& window_;
std::string output_name_;
uint32_t configured_width_ = 0;
uint32_t configured_height_ = 0;
uint32_t width_ = 0;
uint32_t height_ = 0;
uint8_t anchor_ = HORIZONTAL_ANCHOR | ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP;
bool exclusive_zone_ = true;
struct bar_margins margins_;
zwlr_layer_shell_v1_layer layer_ = ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM;
struct wl_output* output_ = nullptr; // owned by GTK
struct wl_surface* surface_ = nullptr; // owned by GTK
layer_surface_ptr layer_surface_;
void onRealize() {
auto gdk_window = window_.get_window()->gobj();
gdk_wayland_window_set_use_custom_surface(gdk_window);
}
void onMap(GdkEventAny* ev) {
static const struct zwlr_layer_surface_v1_listener layer_surface_listener = {
.configure = onSurfaceConfigure,
.closed = onSurfaceClosed,
};
auto client = Client::inst();
auto gdk_window = window_.get_window()->gobj();
surface_ = gdk_wayland_window_get_wl_surface(gdk_window);
layer_surface_.reset(zwlr_layer_shell_v1_get_layer_surface(
client->layer_shell, surface_, output_, layer_, "waybar"));
zwlr_layer_surface_v1_add_listener(layer_surface_.get(), &layer_surface_listener, this);
zwlr_layer_surface_v1_set_keyboard_interactivity(layer_surface_.get(), false);
zwlr_layer_surface_v1_set_anchor(layer_surface_.get(), anchor_);
zwlr_layer_surface_v1_set_margin(
layer_surface_.get(), margins_.top, margins_.right, margins_.bottom, margins_.left);
setSurfaceSize(width_, height_);
setExclusiveZone(exclusive_zone_);
commit();
wl_display_roundtrip(client->wl_display);
}
void onConfigure(GdkEventConfigure* ev) {
/*
* GTK wants new size for the window.
*
* Prefer configured size if it's non-default.
* If the size is not set and the window is smaller than requested by GTK, request resize from
* layer surface.
*/
auto tmp_height = height_;
auto tmp_width = width_;
if (ev->height > static_cast<int>(height_)) {
// Default minimal value
if (height_ > 1) {
spdlog::warn(MIN_HEIGHT_MSG, height_, ev->height);
}
if (configured_height_ > 1) {
spdlog::info(SIZE_DEFINED, "Height");
} else {
tmp_height = ev->height;
}
}
if (ev->width > static_cast<int>(width_)) {
// Default minimal value
if (width_ > 1) {
spdlog::warn(MIN_WIDTH_MSG, width_, ev->width);
}
if (configured_width_ > 1) {
spdlog::info(SIZE_DEFINED, "Width");
} else {
tmp_width = ev->width;
}
}
if (tmp_width != width_ || tmp_height != height_) {
setSurfaceSize(tmp_width, tmp_height);
commit();
}
}
void setSurfaceSize(uint32_t width, uint32_t height) {
/* If the client is anchored to two opposite edges, layer_surface.configure will return
* size without margins for the axis.
* layer_surface.set_size, however, expects size with margins for the anchored axis.
* This is not specified by wlr-layer-shell and based on actual behavior of sway.
*
* If the size for unanchored axis is not set (0), change request to 1 to avoid automatic
* assignment by the compositor.
*/
if ((anchor_ & VERTICAL_ANCHOR) == VERTICAL_ANCHOR) {
width = width > 0 ? width : 1;
if (height > 1) {
height += margins_.top + margins_.bottom;
}
} else {
height = height > 0 ? height : 1;
if (width > 1) {
width += margins_.right + margins_.left;
}
}
spdlog::debug("Set surface size {}x{} for output {}", width, height, output_name_);
zwlr_layer_surface_v1_set_size(layer_surface_.get(), width, height);
}
static void onSurfaceConfigure(void* data, struct zwlr_layer_surface_v1* surface, uint32_t serial,
uint32_t width, uint32_t height) {
auto o = static_cast<RawSurfaceImpl*>(data);
if (width != o->width_ || height != o->height_) {
o->width_ = width;
o->height_ = height;
o->window_.set_size_request(o->width_, o->height_);
o->window_.resize(o->width_, o->height_);
o->setExclusiveZone(o->exclusive_zone_);
spdlog::info(BAR_SIZE_MSG,
o->width_ == 1 ? "auto" : std::to_string(o->width_),
o->height_ == 1 ? "auto" : std::to_string(o->height_),
o->output_name_);
o->commit();
}
zwlr_layer_surface_v1_ack_configure(surface, serial);
}
static void onSurfaceClosed(void* data, struct zwlr_layer_surface_v1* /* surface */) {
auto o = static_cast<RawSurfaceImpl*>(data);
o->layer_surface_.reset();
}
};
}; // namespace waybar
waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config)
: output(w_output),
config(w_config),
window{Gtk::WindowType::WINDOW_TOPLEVEL},
surface(nullptr),
layer_surface_(nullptr),
anchor_(ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP),
layer_{bar_layer::BOTTOM},
left_(Gtk::ORIENTATION_HORIZONTAL, 0),
center_(Gtk::ORIENTATION_HORIZONTAL, 0),
right_(Gtk::ORIENTATION_HORIZONTAL, 0),
@ -25,32 +371,30 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config)
window.get_style_context()->add_class(config["name"].asString());
window.get_style_context()->add_class(config["position"].asString());
if (config["position"] == "right" || config["position"] == "left") {
height_ = 0;
width_ = 1;
if (config["layer"] == "top") {
layer_ = bar_layer::TOP;
} else if (config["layer"] == "overlay") {
layer_ = bar_layer::OVERLAY;
}
height_ = config["height"].isUInt() ? config["height"].asUInt() : height_;
width_ = config["width"].isUInt() ? config["width"].asUInt() : width_;
if (config["position"] == "bottom") {
anchor_ = ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM;
} else if (config["position"] == "left") {
anchor_ = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT;
} else if (config["position"] == "right") {
anchor_ = ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
}
if (anchor_ == ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM ||
anchor_ == ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP) {
anchor_ |= ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
} else if (anchor_ == ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT ||
anchor_ == ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT) {
anchor_ |= ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM;
auto position = config["position"].asString();
if (position == "right" || position == "left") {
left_ = Gtk::Box(Gtk::ORIENTATION_VERTICAL, 0);
center_ = Gtk::Box(Gtk::ORIENTATION_VERTICAL, 0);
right_ = Gtk::Box(Gtk::ORIENTATION_VERTICAL, 0);
box_ = Gtk::Box(Gtk::ORIENTATION_VERTICAL, 0);
vertical = true;
}
left_.get_style_context()->add_class("modules-left");
center_.get_style_context()->add_class("modules-center");
right_.get_style_context()->add_class("modules-right");
uint32_t height = config["height"].isUInt() ? config["height"].asUInt() : 0;
uint32_t width = config["width"].isUInt() ? config["width"].asUInt() : 0;
struct bar_margins margins_;
if (config["margin-top"].isInt() || config["margin-right"].isInt() ||
config["margin-bottom"].isInt() || config["margin-left"].isInt()) {
@ -98,170 +442,62 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config)
}
#ifdef HAVE_GTK_LAYER_SHELL
use_gls_ = config["gtk-layer-shell"].isBool() ? config["gtk-layer-shell"].asBool() : true;
if (use_gls_) {
initGtkLayerShell();
}
#endif
window.signal_realize().connect_notify(sigc::mem_fun(*this, &Bar::onRealize));
window.signal_map_event().connect_notify(sigc::mem_fun(*this, &Bar::onMap));
window.signal_configure_event().connect_notify(sigc::mem_fun(*this, &Bar::onConfigure));
window.set_size_request(width_, height_);
setupWidgets();
if (window.get_realized()) {
onRealize();
}
window.show_all();
}
void waybar::Bar::onConfigure(GdkEventConfigure* ev) {
auto tmp_height = height_;
auto tmp_width = width_;
if (ev->height > static_cast<int>(height_)) {
// Default minimal value
if (height_ != 1) {
spdlog::warn(MIN_HEIGHT_MSG, height_, ev->height);
}
if (config["height"].isUInt()) {
spdlog::info(SIZE_DEFINED, "Height");
} else {
tmp_height = ev->height;
}
}
if (ev->width > static_cast<int>(width_)) {
// Default minimal value
if (width_ != 1) {
spdlog::warn(MIN_WIDTH_MSG, width_, ev->width);
}
if (config["width"].isUInt()) {
spdlog::info(SIZE_DEFINED, "Width");
} else {
tmp_width = ev->width;
}
}
if (use_gls_) {
width_ = tmp_width;
height_ = tmp_height;
spdlog::debug("Set surface size {}x{} for output {}", width_, height_, output->name);
setExclusiveZone(tmp_width, tmp_height);
} else if (tmp_width != width_ || tmp_height != height_) {
setSurfaceSize(tmp_width, tmp_height);
}
}
#ifdef HAVE_GTK_LAYER_SHELL
void waybar::Bar::initGtkLayerShell() {
auto gtk_window = window.gobj();
// this has to be executed before GtkWindow.realize
gtk_layer_init_for_window(gtk_window);
gtk_layer_set_keyboard_interactivity(gtk_window, FALSE);
auto layer = config["layer"] == "top" ? GTK_LAYER_SHELL_LAYER_TOP : GTK_LAYER_SHELL_LAYER_BOTTOM;
gtk_layer_set_layer(gtk_window, layer);
gtk_layer_set_monitor(gtk_window, output->monitor->gobj());
gtk_layer_set_namespace(gtk_window, "waybar");
gtk_layer_set_anchor(
gtk_window, GTK_LAYER_SHELL_EDGE_LEFT, anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT);
gtk_layer_set_anchor(
gtk_window, GTK_LAYER_SHELL_EDGE_RIGHT, anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT);
gtk_layer_set_anchor(
gtk_window, GTK_LAYER_SHELL_EDGE_TOP, anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP);
gtk_layer_set_anchor(
gtk_window, GTK_LAYER_SHELL_EDGE_BOTTOM, anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM);
gtk_layer_set_margin(gtk_window, GTK_LAYER_SHELL_EDGE_LEFT, margins_.left);
gtk_layer_set_margin(gtk_window, GTK_LAYER_SHELL_EDGE_RIGHT, margins_.right);
gtk_layer_set_margin(gtk_window, GTK_LAYER_SHELL_EDGE_TOP, margins_.top);
gtk_layer_set_margin(gtk_window, GTK_LAYER_SHELL_EDGE_BOTTOM, margins_.bottom);
if (width_ > 1 && height_ > 1) {
/* configure events are not emitted if the bar is using initial size */
setExclusiveZone(width_, height_);
}
}
#endif
void waybar::Bar::onRealize() {
auto gdk_window = window.get_window()->gobj();
gdk_wayland_window_set_use_custom_surface(gdk_window);
}
void waybar::Bar::onMap(GdkEventAny* ev) {
auto gdk_window = window.get_window()->gobj();
surface = gdk_wayland_window_get_wl_surface(gdk_window);
if (use_gls_) {
return;
}
auto client = waybar::Client::inst();
// owned by output->monitor; no need to destroy
auto wl_output = gdk_wayland_monitor_get_wl_output(output->monitor->gobj());
auto layer =
config["layer"] == "top" ? ZWLR_LAYER_SHELL_V1_LAYER_TOP : ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM;
layer_surface_ = zwlr_layer_shell_v1_get_layer_surface(
client->layer_shell, surface, wl_output, layer, "waybar");
zwlr_layer_surface_v1_set_keyboard_interactivity(layer_surface_, false);
zwlr_layer_surface_v1_set_anchor(layer_surface_, anchor_);
zwlr_layer_surface_v1_set_margin(
layer_surface_, margins_.top, margins_.right, margins_.bottom, margins_.left);
setSurfaceSize(width_, height_);
setExclusiveZone(width_, height_);
static const struct zwlr_layer_surface_v1_listener layer_surface_listener = {
.configure = layerSurfaceHandleConfigure,
.closed = layerSurfaceHandleClosed,
};
zwlr_layer_surface_v1_add_listener(layer_surface_, &layer_surface_listener, this);
wl_surface_commit(surface);
wl_display_roundtrip(client->wl_display);
}
void waybar::Bar::setExclusiveZone(uint32_t width, uint32_t height) {
auto zone = 0;
if (visible) {
// exclusive zone already includes margin for anchored edge,
// only opposite margin should be added
if (vertical) {
zone += width;
zone += (anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT) ? margins_.right : margins_.left;
} else {
zone += height;
zone += (anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP) ? margins_.bottom : margins_.top;
}
}
spdlog::debug("Set exclusive zone {} for output {}", zone, output->name);
#ifdef HAVE_GTK_LAYER_SHELL
if (use_gls_) {
gtk_layer_set_exclusive_zone(window.gobj(), zone);
bool use_gls = config["gtk-layer-shell"].isBool() ? config["gtk-layer-shell"].asBool() : true;
if (use_gls) {
surface_impl_ = std::make_unique<GLSSurfaceImpl>(window, *output);
} else
#endif
{
zwlr_layer_surface_v1_set_exclusive_zone(layer_surface_, zone);
surface_impl_ = std::make_unique<RawSurfaceImpl>(window, *output);
}
surface_impl_->setLayer(layer_);
surface_impl_->setExclusiveZone(true);
surface_impl_->setMargins(margins_);
surface_impl_->setPosition(position);
surface_impl_->setSize(width, height);
window.signal_map_event().connect_notify(sigc::mem_fun(*this, &Bar::onMap));
setupWidgets();
window.show_all();
if (spdlog::should_log(spdlog::level::debug)) {
// Unfortunately, this function isn't in the C++ bindings, so we have to call the C version.
char* gtk_tree = gtk_style_context_to_string(
window.get_style_context()->gobj(),
(GtkStyleContextPrintFlags)(GTK_STYLE_CONTEXT_PRINT_RECURSE |
GTK_STYLE_CONTEXT_PRINT_SHOW_STYLE));
spdlog::debug("GTK widget tree:\n{}", gtk_tree);
g_free(gtk_tree);
}
}
void waybar::Bar::setSurfaceSize(uint32_t width, uint32_t height) {
/* If the client is anchored to two opposite edges, layer_surface.configure will return
* size without margins for the axis.
* layer_surface.set_size, however, expects size with margins for the anchored axis.
* This is not specified by wlr-layer-shell and based on actual behavior of sway.
void waybar::Bar::onMap(GdkEventAny*) {
/*
* Obtain a pointer to the custom layer surface for modules that require it (idle_inhibitor).
*/
if (vertical && height > 1) {
height += margins_.top + margins_.bottom;
}
if (!vertical && width > 1) {
width += margins_.right + margins_.left;
}
spdlog::debug("Set surface size {}x{} for output {}", width, height, output->name);
zwlr_layer_surface_v1_set_size(layer_surface_, width, height);
auto gdk_window = window.get_window()->gobj();
surface = gdk_wayland_window_get_wl_surface(gdk_window);
}
void waybar::Bar::setVisible(bool value) {
visible = value;
if (!visible) {
window.get_style_context()->add_class("hidden");
window.set_opacity(0);
surface_impl_->setLayer(bar_layer::BOTTOM);
} else {
window.get_style_context()->remove_class("hidden");
window.set_opacity(1);
surface_impl_->setLayer(layer_);
}
surface_impl_->setExclusiveZone(visible);
surface_impl_->commit();
}
void waybar::Bar::toggle() { setVisible(!visible); }
// Converting string to button code rn as to avoid doing it later
void waybar::Bar::setupAltFormatKeyForModule(const std::string& module_name) {
if (config.isMember(module_name)) {
@ -323,48 +559,6 @@ void waybar::Bar::handleSignal(int signal) {
}
}
void waybar::Bar::layerSurfaceHandleConfigure(void* data, struct zwlr_layer_surface_v1* surface,
uint32_t serial, uint32_t width, uint32_t height) {
auto o = static_cast<waybar::Bar*>(data);
if (width != o->width_ || height != o->height_) {
o->width_ = width;
o->height_ = height;
o->window.set_size_request(o->width_, o->height_);
o->window.resize(o->width_, o->height_);
o->setExclusiveZone(width, height);
spdlog::info(BAR_SIZE_MSG,
o->width_ == 1 ? "auto" : std::to_string(o->width_),
o->height_ == 1 ? "auto" : std::to_string(o->height_),
o->output->name);
wl_surface_commit(o->surface);
}
zwlr_layer_surface_v1_ack_configure(surface, serial);
}
void waybar::Bar::layerSurfaceHandleClosed(void* data, struct zwlr_layer_surface_v1* /*surface*/) {
auto o = static_cast<waybar::Bar*>(data);
if (o->layer_surface_) {
zwlr_layer_surface_v1_destroy(o->layer_surface_);
o->layer_surface_ = nullptr;
}
o->modules_left_.clear();
o->modules_center_.clear();
o->modules_right_.clear();
}
auto waybar::Bar::toggle() -> void {
visible = !visible;
if (!visible) {
window.get_style_context()->add_class("hidden");
window.set_opacity(0);
} else {
window.get_style_context()->remove_class("hidden");
window.set_opacity(1);
}
setExclusiveZone(width_, height_);
wl_surface_commit(surface);
}
void waybar::Bar::getModules(const Factory& factory, const std::string& pos) {
if (config[pos].isArray()) {
for (const auto& name : config[pos]) {

View File

@ -1,10 +1,15 @@
#include "client.hpp"
#include <fmt/ostream.h>
#include <spdlog/spdlog.h>
#include <fstream>
#include <iostream>
#include "idle-inhibit-unstable-v1-client-protocol.h"
#include "util/clara.hpp"
#include "util/json.hpp"
#include "wlr-layer-shell-unstable-v1-client-protocol.h"
waybar::Client *waybar::Client::inst() {
static auto c = new Client();
@ -32,6 +37,8 @@ void waybar::Client::handleGlobal(void *data, struct wl_registry *registry, uint
const char *interface, uint32_t version) {
auto client = static_cast<Client *>(data);
if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) {
// limit version to a highest supported by the client protocol file
version = std::min<uint32_t>(version, zwlr_layer_shell_v1_interface.version);
client->layer_shell = static_cast<struct zwlr_layer_shell_v1 *>(
wl_registry_bind(registry, name, &zwlr_layer_shell_v1_interface, version));
} else if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0 &&
@ -53,9 +60,9 @@ void waybar::Client::handleOutput(struct waybar_output &output) {
static const struct zxdg_output_v1_listener xdgOutputListener = {
.logical_position = [](void *, struct zxdg_output_v1 *, int32_t, int32_t) {},
.logical_size = [](void *, struct zxdg_output_v1 *, int32_t, int32_t) {},
.done = [](void *, struct zxdg_output_v1 *) {},
.done = &handleOutputDone,
.name = &handleOutputName,
.description = [](void *, struct zxdg_output_v1 *, const char *) {},
.description = &handleOutputDescription,
};
// owned by output->monitor; no need to destroy
auto wl_output = gdk_wayland_monitor_get_wl_output(output.monitor->gobj());
@ -64,21 +71,26 @@ void waybar::Client::handleOutput(struct waybar_output &output) {
}
bool waybar::Client::isValidOutput(const Json::Value &config, struct waybar_output &output) {
bool found = true;
if (config["output"].isArray()) {
bool in_array = false;
for (auto const &output_conf : config["output"]) {
if (output_conf.isString() && output_conf.asString() == output.name) {
in_array = true;
break;
if (output_conf.isString() &&
(output_conf.asString() == output.name || output_conf.asString() == output.identifier)) {
return true;
}
}
found = in_array;
return false;
} else if (config["output"].isString()) {
auto config_output = config["output"].asString();
if (!config_output.empty()) {
if (config_output.substr(0, 1) == "!") {
return config_output.substr(1) != output.name &&
config_output.substr(1) != output.identifier;
}
return config_output == output.name || config_output == output.identifier;
}
}
if (config["output"].isString() && config["output"].asString() != output.name) {
found = false;
}
return found;
return true;
}
struct waybar::waybar_output &waybar::Client::getOutput(void *addr) {
@ -104,28 +116,56 @@ std::vector<Json::Value> waybar::Client::getOutputConfigs(struct waybar_output &
return configs;
}
void waybar::Client::handleOutputDone(void *data, struct zxdg_output_v1 * /*xdg_output*/) {
auto client = waybar::Client::inst();
try {
auto &output = client->getOutput(data);
/**
* Multiple .done events may arrive in batch. In this case libwayland would queue
* xdg_output.destroy and dispatch all pending events, triggering this callback several times
* for the same output. .done events can also arrive after that for a scale or position changes.
* We wouldn't want to draw a duplicate bar for each such event either.
*
* All the properties we care about are immutable so it's safe to delete the xdg_output object
* on the first event and use the ptr value to check that the callback was already invoked.
*/
if (output.xdg_output) {
output.xdg_output.reset();
spdlog::debug("Output detection done: {} ({})", output.name, output.identifier);
auto configs = client->getOutputConfigs(output);
if (!configs.empty()) {
for (const auto &config : configs) {
client->bars.emplace_back(std::make_unique<Bar>(&output, config));
}
}
}
} catch (const std::exception &e) {
std::cerr << e.what() << std::endl;
}
}
void waybar::Client::handleOutputName(void * data, struct zxdg_output_v1 * /*xdg_output*/,
const char *name) {
auto client = waybar::Client::inst();
try {
auto &output = client->getOutput(data);
output.name = name;
spdlog::debug("Output detected: {} ({} {})",
name,
output.monitor->get_manufacturer(),
output.monitor->get_model());
auto configs = client->getOutputConfigs(output);
if (configs.empty()) {
output.xdg_output.reset();
} else {
wl_display_roundtrip(client->wl_display);
for (const auto &config : configs) {
client->bars.emplace_back(std::make_unique<Bar>(&output, config));
Glib::RefPtr<Gdk::Screen> screen = client->bars.back()->window.get_screen();
client->style_context_->add_provider_for_screen(
screen, client->css_provider_, GTK_STYLE_PROVIDER_PRIORITY_USER);
}
}
} catch (const std::exception &e) {
std::cerr << e.what() << std::endl;
}
}
void waybar::Client::handleOutputDescription(void *data, struct zxdg_output_v1 * /*xdg_output*/,
const char *description) {
auto client = waybar::Client::inst();
try {
auto & output = client->getOutput(data);
const char *open_paren = strrchr(description, '(');
// Description format: "identifier (name)"
size_t identifier_length = open_paren - description;
output.identifier = std::string(description, identifier_length - 1);
} catch (const std::exception &e) {
std::cerr << e.what() << std::endl;
}
@ -139,10 +179,21 @@ void waybar::Client::handleMonitorAdded(Glib::RefPtr<Gdk::Monitor> monitor) {
void waybar::Client::handleMonitorRemoved(Glib::RefPtr<Gdk::Monitor> monitor) {
spdlog::debug("Output removed: {} {}", monitor->get_manufacturer(), monitor->get_model());
/* This event can be triggered from wl_display_roundtrip called by GTK or our code.
* Defer destruction of bars for the output to the next iteration of the event loop to avoid
* deleting objects referenced by currently executed code.
*/
Glib::signal_idle().connect_once(
sigc::bind(sigc::mem_fun(*this, &Client::handleDeferredMonitorRemoval), monitor),
Glib::PRIORITY_HIGH_IDLE);
}
void waybar::Client::handleDeferredMonitorRemoval(Glib::RefPtr<Gdk::Monitor> monitor) {
for (auto it = bars.begin(); it != bars.end();) {
if ((*it)->output->monitor == monitor) {
auto output_name = (*it)->output->name;
(*it)->window.close();
(*it)->window.hide();
gtk_app->remove_window((*it)->window);
it = bars.erase(it);
spdlog::info("Bar removed from output: {}", output_name);
} else {
@ -159,6 +210,7 @@ std::tuple<const std::string, const std::string> waybar::Client::getConfigs(
"$HOME/.config/waybar/config",
"$HOME/waybar/config",
"/etc/xdg/waybar/config",
SYSCONFDIR "/xdg/waybar/config",
"./resources/config",
})
: config;
@ -167,6 +219,7 @@ std::tuple<const std::string, const std::string> waybar::Client::getConfigs(
"$HOME/.config/waybar/style.css",
"$HOME/waybar/style.css",
"/etc/xdg/waybar/style.css",
SYSCONFDIR "/xdg/waybar/style.css",
"./resources/style.css",
})
: style;
@ -195,6 +248,9 @@ auto waybar::Client::setupCss(const std::string &css_file) -> void {
if (!css_provider_->load_from_path(css_file)) {
throw std::runtime_error("Can't open style file");
}
// there's always only one screen
style_context_->add_provider_for_screen(
Gdk::Screen::get_default(), css_provider_, GTK_STYLE_PROVIDER_PRIORITY_USER);
}
void waybar::Client::bindInterfaces() {
@ -249,7 +305,8 @@ int waybar::Client::main(int argc, char *argv[]) {
if (!log_level.empty()) {
spdlog::set_level(spdlog::level::from_str(log_level));
}
gtk_app = Gtk::Application::create(argc, argv, "fr.arouillard.waybar");
gtk_app = Gtk::Application::create(
argc, argv, "fr.arouillard.waybar", Gio::APPLICATION_HANDLES_COMMAND_LINE);
gdk_display = Gdk::Display::get_default();
if (!gdk_display) {
throw std::runtime_error("Can't find display");
@ -265,10 +322,9 @@ int waybar::Client::main(int argc, char *argv[]) {
gtk_app->hold();
gtk_app->run();
bars.clear();
zxdg_output_manager_v1_destroy(xdg_output_manager);
zwlr_layer_shell_v1_destroy(layer_shell);
zwp_idle_inhibit_manager_v1_destroy(idle_inhibit_manager);
wl_registry_destroy(registry);
wl_display_disconnect(wl_display);
return 0;
}
void waybar::Client::reset() {
gtk_app->quit();
}

View File

@ -7,7 +7,7 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
auto hash_pos = name.find('#');
auto ref = name.substr(0, hash_pos);
auto id = hash_pos != std::string::npos ? name.substr(hash_pos + 1) : "";
#ifndef NO_FILESYSTEM
#if defined(__linux__) && !defined(NO_FILESYSTEM)
if (ref == "battery") {
return new waybar::modules::Battery(id, config_[name]);
}
@ -22,23 +22,40 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
if (ref == "sway/window") {
return new waybar::modules::sway::Window(id, bar_, config_[name]);
}
if (ref == "sway/language") {
return new waybar::modules::sway::Language(id, config_[name]);
}
#endif
#ifdef HAVE_WLR
if (ref == "wlr/taskbar") {
return new waybar::modules::wlr::Taskbar(id, bar_, config_[name]);
}
#endif
#ifdef HAVE_RIVER
if (ref == "river/tags") {
return new waybar::modules::river::Tags(id, bar_, config_[name]);
}
#endif
if (ref == "idle_inhibitor") {
return new waybar::modules::IdleInhibitor(id, bar_, config_[name]);
}
#if defined(HAVE_MEMORY_LINUX) || defined(HAVE_MEMORY_BSD)
if (ref == "memory") {
return new waybar::modules::Memory(id, config_[name]);
}
#endif
#if defined(HAVE_CPU_LINUX) || defined(HAVE_CPU_BSD)
if (ref == "cpu") {
return new waybar::modules::Cpu(id, config_[name]);
}
#endif
if (ref == "clock") {
return new waybar::modules::Clock(id, config_[name]);
}
if (ref == "disk") {
return new waybar::modules::Disk(id, config_[name]);
}
#if defined(HAVE_DBUSMENU) && !defined(NO_FILESYSTEM)
#ifdef HAVE_DBUSMENU
if (ref == "tray") {
return new waybar::modules::SNI::Tray(id, bar_, config_[name]);
}
@ -62,10 +79,22 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
if (ref == "mpd") {
return new waybar::modules::MPD(id, config_[name]);
}
#endif
#ifdef HAVE_LIBSNDIO
if (ref == "sndio") {
return new waybar::modules::Sndio(id, config_[name]);
}
#endif
if (ref == "temperature") {
return new waybar::modules::Temperature(id, config_[name]);
}
#if defined(__linux__)
# ifdef WANT_RFKILL
if (ref == "bluetooth") {
return new waybar::modules::Bluetooth(id, config_[name]);
}
# endif
#endif
if (ref.compare(0, 7, "custom/") == 0 && ref.size() > 7) {
return new waybar::modules::Custom(ref.substr(7), id, config_[name]);
}

View File

@ -1,16 +1,89 @@
#include <csignal>
#include <list>
#include <mutex>
#include <sys/types.h>
#include <sys/wait.h>
#include <spdlog/spdlog.h>
#include "client.hpp"
std::mutex reap_mtx;
std::list<pid_t> reap;
volatile bool reload;
void* signalThread(void* args) {
int err, signum;
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGCHLD);
while (true) {
err = sigwait(&mask, &signum);
if (err != 0) {
spdlog::error("sigwait failed: {}", strerror(errno));
continue;
}
switch (signum) {
case SIGCHLD:
spdlog::debug("Received SIGCHLD in signalThread");
if (!reap.empty()) {
reap_mtx.lock();
for (auto it = reap.begin(); it != reap.end(); ++it) {
if (waitpid(*it, nullptr, WNOHANG) == *it) {
spdlog::debug("Reaped child with PID: {}", *it);
it = reap.erase(it);
}
}
reap_mtx.unlock();
}
break;
default:
spdlog::debug("Received signal with number {}, but not handling",
signum);
break;
}
}
}
void startSignalThread(void) {
int err;
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGCHLD);
// Block SIGCHLD so it can be handled by the signal thread
// Any threads created by this one (the main thread) should not
// modify their signal mask to unblock SIGCHLD
err = pthread_sigmask(SIG_BLOCK, &mask, nullptr);
if (err != 0) {
spdlog::error("pthread_sigmask failed in startSignalThread: {}", strerror(err));
exit(1);
}
pthread_t thread_id;
err = pthread_create(&thread_id, nullptr, signalThread, nullptr);
if (err != 0) {
spdlog::error("pthread_create failed in startSignalThread: {}", strerror(err));
exit(1);
}
}
int main(int argc, char* argv[]) {
try {
auto client = waybar::Client::inst();
std::signal(SIGUSR1, [](int /*signal*/) {
for (auto& bar : waybar::Client::inst()->bars) {
bar->toggle();
}
});
std::signal(SIGUSR2, [](int /*signal*/) {
spdlog::info("Reloading...");
reload = true;
waybar::Client::inst()->reset();
});
for (int sig = SIGRTMIN + 1; sig <= SIGRTMAX; ++sig) {
std::signal(sig, [](int sig) {
for (auto& bar : waybar::Client::inst()->bars) {
@ -18,8 +91,14 @@ int main(int argc, char* argv[]) {
}
});
}
startSignalThread();
auto ret = 0;
do {
reload = false;
ret = client->main(argc, argv);
} while (reload);
auto ret = client->main(argc, argv);
delete client;
return ret;
} catch (const std::exception& e) {

View File

@ -1,16 +1,14 @@
#include "modules/backlight.hpp"
#include <fmt/format.h>
#include <libudev.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <algorithm>
#include <chrono>
#include <memory>
#include <libudev.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <fmt/format.h>
namespace {
class FileDescriptor {
public:
@ -187,6 +185,8 @@ auto waybar::modules::Backlight::update() -> void {
}
previous_best_ = best == nullptr ? std::nullopt : std::optional{*best};
previous_format_ = format_;
// Call parent update
ALabel::update();
}
template <class ForwardIt>
@ -211,7 +211,7 @@ void waybar::modules::Backlight::upsert_device(ForwardIt first, ForwardIt last,
check_nn(name);
const char *actual_brightness_attr =
strcmp(name, "amdgpu_bl0") == 0 ? "brightness" : "actual_brightness";
strncmp(name, "amdgpu_bl", 9) == 0 ? "brightness" : "actual_brightness";
const char *actual = udev_device_get_sysattr_value(dev, actual_brightness_attr);
check_nn(actual);

View File

@ -1,48 +1,85 @@
#include "modules/battery.hpp"
#include <spdlog/spdlog.h>
waybar::modules::Battery::Battery(const std::string& id, const Json::Value& config)
: ALabel(config, "battery", id, "{capacity}%", 60) {
getBatteries();
fd_ = inotify_init1(IN_CLOEXEC);
if (fd_ == -1) {
battery_watch_fd_ = inotify_init1(IN_CLOEXEC);
if (battery_watch_fd_ == -1) {
throw std::runtime_error("Unable to listen batteries.");
}
for (auto const& bat : batteries_) {
auto wd = inotify_add_watch(fd_, (bat / "uevent").c_str(), IN_ACCESS);
if (wd != -1) {
wds_.push_back(wd);
}
global_watch_fd_ = inotify_init1(IN_CLOEXEC);
if (global_watch_fd_ == -1) {
throw std::runtime_error("Unable to listen batteries.");
}
// Watch the directory for any added or removed batteries
global_watch = inotify_add_watch(global_watch_fd_, data_dir_.c_str(), IN_CREATE | IN_DELETE);
if (global_watch < 0) {
throw std::runtime_error("Could not watch for battery plug/unplug");
}
refreshBatteries();
worker();
}
waybar::modules::Battery::~Battery() {
for (auto wd : wds_) {
inotify_rm_watch(fd_, wd);
std::lock_guard<std::mutex> guard(battery_list_mutex_);
if (global_watch >= 0) {
inotify_rm_watch(global_watch_fd_, global_watch);
}
close(fd_);
close(global_watch_fd_);
for (auto it = batteries_.cbegin(); it != batteries_.cend(); it++) {
auto watch_id = (*it).second;
if (watch_id >= 0) {
inotify_rm_watch(battery_watch_fd_, watch_id);
}
batteries_.erase(it);
}
close(battery_watch_fd_);
}
void waybar::modules::Battery::worker() {
thread_timer_ = [this] {
// Make sure we eventually update the list of batteries even if we miss an
// inotify event for some reason
refreshBatteries();
dp.emit();
thread_timer_.sleep_for(interval_);
};
thread_ = [this] {
struct inotify_event event = {0};
int nbytes = read(fd_, &event, sizeof(event));
int nbytes = read(battery_watch_fd_, &event, sizeof(event));
if (nbytes != sizeof(event) || event.mask & IN_IGNORED) {
thread_.stop();
return;
}
// TODO: don't stop timer for now since there is some bugs :?
// thread_timer_.stop();
dp.emit();
};
thread_battery_update_ = [this] {
struct inotify_event event = {0};
int nbytes = read(global_watch_fd_, &event, sizeof(event));
if (nbytes != sizeof(event) || event.mask & IN_IGNORED) {
thread_.stop();
return;
}
refreshBatteries();
dp.emit();
};
}
void waybar::modules::Battery::getBatteries() {
void waybar::modules::Battery::refreshBatteries() {
std::lock_guard<std::mutex> guard(battery_list_mutex_);
// Mark existing list of batteries as not necessarily found
std::map<fs::path, bool> check_map;
for (auto const& bat : batteries_) {
check_map[bat.first] = false;
}
try {
for (auto& node : fs::directory_iterator(data_dir_)) {
if (!fs::is_directory(node)) {
@ -52,8 +89,23 @@ void waybar::modules::Battery::getBatteries() {
auto bat_defined = config_["bat"].isString();
if (((bat_defined && dir_name == config_["bat"].asString()) || !bat_defined) &&
fs::exists(node.path() / "capacity") && fs::exists(node.path() / "uevent") &&
fs::exists(node.path() / "status")) {
batteries_.push_back(node.path());
fs::exists(node.path() / "status") && fs::exists(node.path() / "type")) {
std::string type;
std::ifstream(node.path() / "type") >> type;
if (!type.compare("Battery")){
check_map[node.path()] = true;
auto search = batteries_.find(node.path());
if (search == batteries_.end()) {
// We've found a new battery save it and start listening for events
auto event_path = (node.path() / "uevent");
auto wd = inotify_add_watch(battery_watch_fd_, event_path.c_str(), IN_ACCESS);
if (wd < 0) {
throw std::runtime_error("Could not watch events for " + node.path().string());
}
batteries_[node.path()] = wd;
}
}
}
auto adap_defined = config_["adapter"].isString();
if (((adap_defined && dir_name == config_["adapter"].asString()) || !adap_defined) &&
@ -70,22 +122,45 @@ void waybar::modules::Battery::getBatteries() {
}
throw std::runtime_error("No batteries.");
}
// Remove any batteries that are no longer present and unwatch them
for (auto const& check : check_map) {
if (!check.second) {
auto watch_id = batteries_[check.first];
if (watch_id >= 0) {
inotify_rm_watch(battery_watch_fd_, watch_id);
}
batteries_.erase(check.first);
}
}
}
const std::tuple<uint8_t, float, std::string> waybar::modules::Battery::getInfos() const {
// Unknown > Full > Not charging > Discharging > Charging
static bool status_gt(const std::string& a, const std::string& b) {
if (a == b) return false;
else if (a == "Unknown") return true;
else if (a == "Full" && b != "Unknown") return true;
else if (a == "Not charging" && b != "Unknown" && b != "Full") return true;
else if (a == "Discharging" && b != "Unknown" && b != "Full" && b != "Not charging") return true;
return false;
}
const std::tuple<uint8_t, float, std::string, float> waybar::modules::Battery::getInfos() {
std::lock_guard<std::mutex> guard(battery_list_mutex_);
try {
uint16_t total = 0;
uint32_t total_power = 0; // μW
uint32_t total_energy = 0; // μWh
uint32_t total_energy_full = 0;
uint32_t total_energy_full_design = 0;
std::string status = "Unknown";
for (auto const& bat : batteries_) {
uint16_t capacity;
for (auto const& item : batteries_) {
auto bat = item.first;
uint32_t power_now;
uint32_t energy_full;
uint32_t energy_now;
uint32_t energy_full_design;
std::string _status;
std::ifstream(bat / "capacity") >> capacity;
std::ifstream(bat / "status") >> _status;
auto rate_path = fs::exists(bat / "current_now") ? "current_now" : "power_now";
std::ifstream(bat / rate_path) >> power_now;
@ -93,13 +168,17 @@ const std::tuple<uint8_t, float, std::string> waybar::modules::Battery::getInfos
std::ifstream(bat / now_path) >> energy_now;
auto full_path = fs::exists(bat / "charge_full") ? "charge_full" : "energy_full";
std::ifstream(bat / full_path) >> energy_full;
if (_status != "Unknown") {
auto full_design_path = fs::exists(bat / "charge_full_design") ? "charge_full_design" : "energy_full_design";
std::ifstream(bat / full_design_path) >> energy_full_design;
// Show the "smallest" status among all batteries
if (status_gt(status, _status)) {
status = _status;
}
total += capacity;
total_power += power_now;
total_energy += energy_now;
total_energy_full += energy_full;
total_energy_full_design += energy_full_design;
}
if (!adapter_.empty() && status == "Discharging") {
bool online;
@ -113,22 +192,40 @@ const std::tuple<uint8_t, float, std::string> waybar::modules::Battery::getInfos
time_remaining = (float)total_energy / total_power;
} else if (status == "Charging" && total_power != 0) {
time_remaining = -(float)(total_energy_full - total_energy) / total_power;
if (time_remaining > 0.0f) {
// If we've turned positive it means the battery is past 100% and so
// just report that as no time remaining
time_remaining = 0.0f;
}
}
float capacity = ((float)total_energy * 100.0f / (float) total_energy_full);
// Handle design-capacity
if (config_["design-capacity"].isBool() ? config_["design-capacity"].asBool() : false) {
capacity = ((float)total_energy * 100.0f / (float) total_energy_full_design);
}
uint16_t capacity = total / batteries_.size();
// Handle full-at
if (config_["full-at"].isUInt()) {
auto full_at = config_["full-at"].asUInt();
if (full_at < 100) {
capacity = static_cast<float>(capacity / full_at) * 100;
if (capacity > full_at) {
capacity = full_at;
}
capacity = 100.f * capacity / full_at;
}
}
return {capacity, time_remaining, status};
if (capacity > 100.f) {
// This can happen when the battery is calibrating and goes above 100%
// Handle it gracefully by clamping at 100%
capacity = 100.f;
}
uint8_t cap = round(capacity);
if (cap == 100) {
// If we've reached 100% just mark as full as some batteries can stay
// stuck reporting they're still charging but not yet done
status = "Full";
}
return {cap, time_remaining, status, total_power / 1e6};
} catch (const std::exception& e) {
spdlog::error("Battery: {}", e.what());
return {0, 0, "Unknown"};
return {0, 0, "Unknown", 0};
}
}
@ -148,10 +245,14 @@ const std::string waybar::modules::Battery::getAdapterStatus(uint8_t capacity) c
}
const std::string waybar::modules::Battery::formatTimeRemaining(float hoursRemaining) {
hoursRemaining = std::fabs(hoursRemaining);
hoursRemaining = std::fabs(hoursRemaining);
uint16_t full_hours = static_cast<uint16_t>(hoursRemaining);
uint16_t minutes = static_cast<uint16_t>(60 * (hoursRemaining - full_hours));
auto format = std::string("{H} h {M} min");
auto format = std::string("{H} h {M} min");
if (full_hours == 0 && minutes == 0) {
// Migh as well not show "0h 0min"
return "";
}
if (config_["format-time"].isString()) {
format = config_["format-time"].asString();
}
@ -159,28 +260,41 @@ const std::string waybar::modules::Battery::formatTimeRemaining(float hoursRemai
}
auto waybar::modules::Battery::update() -> void {
auto [capacity, time_remaining, status] = getInfos();
auto [capacity, time_remaining, status, power] = getInfos();
if (status == "Unknown") {
status = getAdapterStatus(capacity);
}
if (tooltipEnabled()) {
std::string tooltip_text;
if (time_remaining != 0) {
std::string time_to = std::string("Time to ") + ((time_remaining > 0) ? "empty" : "full");
tooltip_text = time_to + ": " + formatTimeRemaining(time_remaining);
} else {
tooltip_text = status;
}
label_.set_tooltip_text(tooltip_text);
}
// Transform to lowercase
std::transform(status.begin(), status.end(), status.begin(), ::tolower);
// Replace space with dash
auto status_pretty = status;
// Transform to lowercase and replace space with dash
std::transform(status.begin(), status.end(), status.begin(), [](char ch) {
return ch == ' ' ? '-' : ch;
return ch == ' ' ? '-' : std::tolower(ch);
});
auto format = format_;
auto state = getState(capacity, true);
auto time_remaining_formatted = formatTimeRemaining(time_remaining);
if (tooltipEnabled()) {
std::string tooltip_text_default;
std::string tooltip_format = "{timeTo}";
if (time_remaining != 0) {
std::string time_to = std::string("Time to ") + ((time_remaining > 0) ? "empty" : "full");
tooltip_text_default = time_to + ": " + time_remaining_formatted;
} else {
tooltip_text_default = status_pretty;
}
if (!state.empty() && config_["tooltip-format-" + status + "-" + state].isString()) {
tooltip_format = config_["tooltip-format-" + status + "-" + state].asString();
} else if (config_["tooltip-format-" + status].isString()) {
tooltip_format = config_["tooltip-format-" + status].asString();
} else if (!state.empty() && config_["tooltip-format-" + state].isString()) {
tooltip_format = config_["tooltip-format-" + state].asString();
} else if (config_["tooltip-format"].isString()) {
tooltip_format = config_["tooltip-format"].asString();
}
label_.set_tooltip_text(fmt::format(tooltip_format,
fmt::arg("timeTo", tooltip_text_default),
fmt::arg("capacity", capacity),
fmt::arg("time", time_remaining_formatted)));
}
if (!old_status_.empty()) {
label_.get_style_context()->remove_class(old_status_);
}
@ -197,9 +311,13 @@ auto waybar::modules::Battery::update() -> void {
event_box_.hide();
} else {
event_box_.show();
auto icons = std::vector<std::string>{status + "-" + state, status, state};
label_.set_markup(fmt::format(format,
fmt::arg("capacity", capacity),
fmt::arg("icon", getIcon(capacity, state)),
fmt::arg("time", formatTimeRemaining(time_remaining))));
fmt::arg("power", power),
fmt::arg("icon", getIcon(capacity, icons)),
fmt::arg("time", time_remaining_formatted)));
}
// Call parent update
ALabel::update();
}

25
src/modules/bluetooth.cpp Normal file
View File

@ -0,0 +1,25 @@
#include "modules/bluetooth.hpp"
#include <fmt/format.h>
waybar::modules::Bluetooth::Bluetooth(const std::string& id, const Json::Value& config)
: ALabel(config, "bluetooth", id, "{icon}", 10), rfkill_{RFKILL_TYPE_BLUETOOTH} {
rfkill_.on_update.connect(sigc::hide(sigc::mem_fun(*this, &Bluetooth::update)));
}
auto waybar::modules::Bluetooth::update() -> void {
std::string status = rfkill_.getState() ? "disabled" : "enabled";
label_.set_markup(
fmt::format(format_, fmt::arg("status", status), fmt::arg("icon", getIcon(0, status))));
if (tooltipEnabled()) {
if (config_["tooltip-format"].isString()) {
auto tooltip_format = config_["tooltip-format"].asString();
auto tooltip_text = fmt::format(tooltip_format, status);
label_.set_tooltip_text(tooltip_text);
} else {
label_.set_tooltip_text(status);
}
}
}

View File

@ -1,6 +1,11 @@
#include "modules/clock.hpp"
#include <time.h>
#include <spdlog/spdlog.h>
#include <sstream>
#include <type_traits>
#include "util/ustring_clen.hpp"
#ifdef HAVE_LANGINFO_1STDAY
#include <langinfo.h>
#include <locale.h>
@ -9,10 +14,9 @@
using waybar::modules::waybar_time;
waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
: ALabel(config, "clock", id, "{:%H:%M}", 60)
, fixed_time_zone_(false)
{
: ALabel(config, "clock", id, "{:%H:%M}", 60, false, false, true), fixed_time_zone_(false) {
if (config_["timezone"].isString()) {
spdlog::warn("As using a timezone, some format args may be missing as the date library havn't got a release since 2018.");
time_zone_ = date::locate_zone(config_["timezone"].asString());
fixed_time_zone_ = true;
}
@ -37,23 +41,67 @@ auto waybar::modules::Clock::update() -> void {
// Time zone can change. Be sure to pick that.
time_zone_ = date::current_zone();
}
auto now = std::chrono::system_clock::now();
waybar_time wtime = {locale_,
date::make_zoned(time_zone_, date::floor<std::chrono::seconds>(now))};
auto text = fmt::format(format_, wtime);
label_.set_markup(text);
std::string text;
if (!fixed_time_zone_) {
// As date dep is not fully compatible, prefer fmt
tzset();
auto localtime = fmt::localtime(std::chrono::system_clock::to_time_t(now));
text = fmt::format(format_, localtime);
label_.set_markup(text);
} else {
text = fmt::format(format_, wtime);
label_.set_markup(text);
}
if (tooltipEnabled()) {
if (config_["tooltip-format"].isString()) {
const auto calendar = calendar_text(wtime);
auto tooltip_format = config_["tooltip-format"].asString();
auto tooltip_text = fmt::format(tooltip_format, wtime, fmt::arg("calendar", calendar));
auto tooltip_format = config_["tooltip-format"].asString();
auto tooltip_text = fmt::format(tooltip_format, wtime, fmt::arg("calendar", calendar));
label_.set_tooltip_markup(tooltip_text);
} else {
label_.set_tooltip_markup(text);
}
}
// Call parent update
ALabel::update();
}
bool waybar::modules::Clock::handleScroll(GdkEventScroll *e) {
// defer to user commands if set
if (config_["on-scroll-up"].isString() || config_["on-scroll-down"].isString()) {
return AModule::handleScroll(e);
}
auto dir = AModule::getScrollDir(e);
if (dir != SCROLL_DIR::UP && dir != SCROLL_DIR::DOWN) {
return true;
}
if (!config_["timezones"].isArray() || config_["timezones"].empty()) {
return true;
}
auto nr_zones = config_["timezones"].size();
if (dir == SCROLL_DIR::UP) {
size_t new_idx = time_zone_idx_ + 1;
time_zone_idx_ = new_idx == nr_zones ? 0 : new_idx;
} else {
time_zone_idx_ = time_zone_idx_ == 0 ? nr_zones - 1 : time_zone_idx_ - 1;
}
auto zone_name = config_["timezones"][time_zone_idx_];
if (!zone_name.isString() || zone_name.empty()) {
fixed_time_zone_ = false;
} else {
time_zone_ = date::locate_zone(zone_name.asString());
fixed_time_zone_ = true;
}
update();
return true;
}
auto waybar::modules::Clock::calendar_text(const waybar_time& wtime) -> std::string {
@ -64,19 +112,19 @@ auto waybar::modules::Clock::calendar_text(const waybar_time& wtime) -> std::str
}
const date::year_month ym(ymd.year(), ymd.month());
const auto curr_day = ymd.day();
const auto curr_day = ymd.day();
std::stringstream os;
const auto first_dow = first_day_of_week();
const auto first_dow = first_day_of_week();
weekdays_header(first_dow, os);
// First week prefixed with spaces if needed.
auto wd = date::weekday(ym/1);
auto wd = date::weekday(ym / 1);
auto empty_days = (wd - first_dow).count();
if (empty_days > 0) {
os << std::string(empty_days * 3 - 1, ' ');
}
auto last_day = (ym/date::literals::last).day();
auto last_day = (ym / date::literals::last).day();
for (auto d = date::day(1); d <= last_day; ++d, ++wd) {
if (wd != first_dow) {
os << ' ';
@ -84,7 +132,12 @@ auto waybar::modules::Clock::calendar_text(const waybar_time& wtime) -> std::str
os << '\n';
}
if (d == curr_day) {
os << "<b><u>" << date::format("%e", d) << "</u></b>";
if (config_["today-format"].isString()) {
auto today_format = config_["today-format"].asString();
os << fmt::format(today_format, date::format("%e", d));
} else {
os << "<b><u>" << date::format("%e", d) << "</u></b>";
}
} else {
os << date::format("%e", d);
}
@ -96,17 +149,20 @@ auto waybar::modules::Clock::calendar_text(const waybar_time& wtime) -> std::str
return result;
}
auto waybar::modules::Clock::weekdays_header(const date::weekday& first_dow, std::ostream& os) -> void {
auto waybar::modules::Clock::weekdays_header(const date::weekday& first_dow, std::ostream& os)
-> void {
auto wd = first_dow;
do {
if (wd != first_dow) os << ' ';
Glib::ustring wd_ustring(date::format(locale_, "%a", wd));
auto clen = ustring_clen(wd_ustring);
auto wd_len = wd_ustring.length();
if (wd_len > 2) {
wd_ustring = wd_ustring.substr(0, 2);
wd_len = 2;
while (clen > 2) {
wd_ustring = wd_ustring.substr(0, wd_len-1);
wd_len--;
clen = ustring_clen(wd_ustring);
}
const std::string pad(2 - wd_len, ' ');
const std::string pad(2 - clen, ' ');
os << pad << wd_ustring;
} while (++wd != first_dow);
os << "\n";
@ -123,13 +179,13 @@ using deleting_unique_ptr = std::unique_ptr<T, deleter_from_fn<fn>>;
// Computations done similarly to Linux cal utility.
auto waybar::modules::Clock::first_day_of_week() -> date::weekday {
#ifdef HAVE_LANGINFO_1STDAY
deleting_unique_ptr<std::remove_pointer<locale_t>::type, freelocale>
posix_locale{newlocale(LC_ALL, locale_.name().c_str(), nullptr)};
deleting_unique_ptr<std::remove_pointer<locale_t>::type, freelocale> posix_locale{
newlocale(LC_ALL, locale_.name().c_str(), nullptr)};
if (posix_locale) {
const int i = (std::intptr_t) nl_langinfo_l(_NL_TIME_WEEK_1STDAY, posix_locale.get());
auto ymd = date::year(i / 10000)/(i / 100 % 100)/(i % 100);
auto wd = date::weekday(ymd);
uint8_t j = *nl_langinfo_l(_NL_TIME_FIRST_WEEKDAY, posix_locale.get());
const int i = (std::intptr_t)nl_langinfo_l(_NL_TIME_WEEK_1STDAY, posix_locale.get());
auto ymd = date::year(i / 10000) / (i / 100 % 100) / (i % 100);
auto wd = date::weekday(ymd);
uint8_t j = *nl_langinfo_l(_NL_TIME_FIRST_WEEKDAY, posix_locale.get());
return wd + date::days(j - 1);
}
#endif

View File

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

108
src/modules/cpu/bsd.cpp Normal file
View File

@ -0,0 +1,108 @@
#include "modules/cpu.hpp"
#include <sys/types.h>
#include <sys/sysctl.h>
#include <spdlog/spdlog.h>
#include <cstdlib> // malloc
#include <unistd.h> // sysconf
#include <cmath> // NAN
#if defined(__NetBSD__) || defined(__OpenBSD__)
# include <sys/sched.h>
#else
# include <sys/resource.h>
#endif
#if defined(__NetBSD__)
typedef uint64_t cp_time_t;
#else
typedef long cp_time_t;
#endif
#if defined(__NetBSD__) || defined(__OpenBSD__)
typedef uint64_t pcp_time_t;
#else
typedef long pcp_time_t;
#endif
std::vector<std::tuple<size_t, size_t>> waybar::modules::Cpu::parseCpuinfo() {
cp_time_t sum_cp_time[CPUSTATES];
size_t sum_sz = sizeof(sum_cp_time);
int ncpu = sysconf(_SC_NPROCESSORS_CONF);
size_t sz = CPUSTATES * (ncpu + 1) * sizeof(pcp_time_t);
pcp_time_t *cp_time = static_cast<pcp_time_t *>(malloc(sz)), *pcp_time = cp_time;
#if defined(__NetBSD__)
int mib[] = {
CTL_KERN,
KERN_CP_TIME,
};
if (sysctl(mib, sizeof(mib)/sizeof(mib[0]), sum_cp_time, &sum_sz, NULL, 0)) {
throw std::runtime_error("sysctl kern.cp_time failed");
}
for (int state = 0; state < CPUSTATES; state++) {
cp_time[state] = sum_cp_time[state];
}
pcp_time += CPUSTATES;
if (sysctl(mib, sizeof(mib)/sizeof(mib[0]), pcp_time, &sz, NULL, 0)) {
throw std::runtime_error("sysctl kern.cp_time failed");
}
#elif defined(__OpenBSD__)
{
int mib[] = {
CTL_KERN,
KERN_CPTIME,
};
if (sysctl(mib, sizeof(mib)/sizeof(mib[0]), sum_cp_time, &sum_sz, NULL, 0)) {
throw std::runtime_error("sysctl kern.cp_time failed");
}
}
for (int state = 0; state < CPUSTATES; state++) {
cp_time[state] = sum_cp_time[state];
}
pcp_time = cp_time;
sz /= ncpu + 1;
{
int mib[] = {
CTL_KERN,
KERN_CPTIME2,
0,
};
for (int cpu = 0; cpu < ncpu; cpu++) {
mib[2] = cpu;
pcp_time += CPUSTATES;
if (sysctl(mib, sizeof(mib)/sizeof(mib[0]), pcp_time, &sz, NULL, 0)) {
throw std::runtime_error("sysctl kern.cp_time2 failed");
}
}
}
#else
if (sysctlbyname("kern.cp_time", sum_cp_time, &sum_sz, NULL, 0)) {
throw std::runtime_error("sysctl kern.cp_time failed");
}
for (int state = 0; state < CPUSTATES; state++) {
cp_time[state] = sum_cp_time[state];
}
pcp_time += CPUSTATES;
if (sysctlbyname("kern.cp_times", pcp_time, &sz, NULL, 0)) {
throw std::runtime_error("sysctl kern.cp_times failed");
}
#endif
std::vector<std::tuple<size_t, size_t>> cpuinfo;
for (int cpu = 0; cpu < ncpu + 1; cpu++) {
pcp_time_t total = 0, *single_cp_time = &cp_time[cpu * CPUSTATES];
for (int state = 0; state < CPUSTATES; state++) {
total += single_cp_time[state];
}
cpuinfo.emplace_back(single_cp_time[CP_IDLE], total);
}
free(cp_time);
return cpuinfo;
}
std::vector<float> waybar::modules::Cpu::parseCpuFrequencies() {
static std::vector<float> frequencies;
if (frequencies.empty()) {
spdlog::warn("cpu/bsd: parseCpuFrequencies is not implemented, expect garbage in {*_frequency}");
frequencies.push_back(NAN);
}
return frequencies;
}

View File

@ -0,0 +1,85 @@
#include "modules/cpu.hpp"
waybar::modules::Cpu::Cpu(const std::string& id, const Json::Value& config)
: ALabel(config, "cpu", id, "{usage}%", 10) {
thread_ = [this] {
dp.emit();
thread_.sleep_for(interval_);
};
}
auto waybar::modules::Cpu::update() -> void {
// TODO: as creating dynamic fmt::arg arrays is buggy we have to calc both
auto cpu_load = getCpuLoad();
auto [cpu_usage, tooltip] = getCpuUsage();
auto [max_frequency, min_frequency, avg_frequency] = getCpuFrequency();
if (tooltipEnabled()) {
label_.set_tooltip_text(tooltip);
}
auto format = format_;
auto state = getState(cpu_usage);
if (!state.empty() && config_["format-" + state].isString()) {
format = config_["format-" + state].asString();
}
if (format.empty()) {
event_box_.hide();
} else {
event_box_.show();
label_.set_markup(fmt::format(format,
fmt::arg("load", cpu_load),
fmt::arg("usage", cpu_usage),
fmt::arg("max_frequency", max_frequency),
fmt::arg("min_frequency", min_frequency),
fmt::arg("avg_frequency", avg_frequency)));
}
// Call parent update
ALabel::update();
}
double waybar::modules::Cpu::getCpuLoad() {
double load[1];
if (getloadavg(load, 1) != -1) {
return load[0];
}
throw std::runtime_error("Can't get Cpu load");
}
std::tuple<uint16_t, std::string> waybar::modules::Cpu::getCpuUsage() {
if (prev_times_.empty()) {
prev_times_ = parseCpuinfo();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
std::vector<std::tuple<size_t, size_t>> curr_times = parseCpuinfo();
std::string tooltip;
uint16_t usage = 0;
for (size_t i = 0; i < curr_times.size(); ++i) {
auto [curr_idle, curr_total] = curr_times[i];
auto [prev_idle, prev_total] = prev_times_[i];
const float delta_idle = curr_idle - prev_idle;
const float delta_total = curr_total - prev_total;
uint16_t tmp = 100 * (1 - delta_idle / delta_total);
if (i == 0) {
usage = tmp;
tooltip = fmt::format("Total: {}%", tmp);
} else {
tooltip = tooltip + fmt::format("\nCore{}: {}%", i - 1, tmp);
}
}
prev_times_ = curr_times;
return {usage, tooltip};
}
std::tuple<float, float, float> waybar::modules::Cpu::getCpuFrequency() {
std::vector<float> frequencies = parseCpuFrequencies();
auto [min, max] = std::minmax_element(std::begin(frequencies), std::end(frequencies));
float avg_frequency = std::accumulate(std::begin(frequencies), std::end(frequencies), 0.0) / frequencies.size();
// Round frequencies with double decimal precision to get GHz
float max_frequency = std::ceil(*max / 10.0) / 100.0;
float min_frequency = std::ceil(*min / 10.0) / 100.0;
avg_frequency = std::ceil(avg_frequency / 10.0) / 100.0;
return { max_frequency, min_frequency, avg_frequency };
}

77
src/modules/cpu/linux.cpp Normal file
View File

@ -0,0 +1,77 @@
#include <filesystem>
#include "modules/cpu.hpp"
std::vector<std::tuple<size_t, size_t>> waybar::modules::Cpu::parseCpuinfo() {
const std::string data_dir_ = "/proc/stat";
std::ifstream info(data_dir_);
if (!info.is_open()) {
throw std::runtime_error("Can't open " + data_dir_);
}
std::vector<std::tuple<size_t, size_t>> cpuinfo;
std::string line;
while (getline(info, line)) {
if (line.substr(0, 3).compare("cpu") != 0) {
break;
}
std::stringstream sline(line.substr(5));
std::vector<size_t> times;
for (size_t time = 0; sline >> time; times.push_back(time))
;
size_t idle_time = 0;
size_t total_time = 0;
if (times.size() >= 4) {
idle_time = times[3];
total_time = std::accumulate(times.begin(), times.end(), 0);
}
cpuinfo.emplace_back(idle_time, total_time);
}
return cpuinfo;
}
std::vector<float> waybar::modules::Cpu::parseCpuFrequencies() {
const std::string file_path_ = "/proc/cpuinfo";
std::ifstream info(file_path_);
if (!info.is_open()) {
throw std::runtime_error("Can't open " + file_path_);
}
std::vector<float> frequencies;
std::string line;
while (getline(info, line)) {
if (line.substr(0, 7).compare("cpu MHz") != 0) {
continue;
}
std::string frequency_str = line.substr(line.find(":") + 2);
float frequency = std::strtol(frequency_str.c_str(), nullptr, 10);
frequencies.push_back(frequency);
}
info.close();
if (frequencies.size() <= 0) {
std::string cpufreq_dir = "/sys/devices/system/cpu/cpufreq";
if (std::filesystem::exists(cpufreq_dir)) {
std::vector<std::string> frequency_files = {
"/cpuinfo_min_freq",
"/cpuinfo_max_freq"
};
for (auto& p: std::filesystem::directory_iterator(cpufreq_dir)) {
for (auto freq_file: frequency_files) {
std::string freq_file_path = p.path().string() + freq_file;
if (std::filesystem::exists(freq_file_path)) {
std::string freq_value;
std::ifstream freq(freq_file_path);
if (freq.is_open()) {
getline(freq, freq_value);
float frequency = std::strtol(freq_value.c_str(), nullptr, 10);
frequencies.push_back(frequency / 1000);
freq.close();
}
}
}
}
}
}
return frequencies;
}

View File

@ -1,22 +1,21 @@
#include "modules/custom.hpp"
#include <spdlog/spdlog.h>
waybar::modules::Custom::Custom(const std::string& name, const std::string& id,
const Json::Value& config)
: ALabel(config, "custom-" + name, id, "{}"), name_(name), fp_(nullptr), pid_(-1) {
if (config_["exec"].isString()) {
if (interval_.count() > 0) {
delayWorker();
} else {
continuousWorker();
}
}
dp.emit();
if (interval_.count() > 0) {
delayWorker();
} else if (config_["exec"].isString()) {
continuousWorker();
}
}
waybar::modules::Custom::~Custom() {
if (pid_ != -1) {
kill(-pid_, 9);
killpg(pid_, SIGTERM);
pid_ = -1;
}
}
@ -25,14 +24,16 @@ void waybar::modules::Custom::delayWorker() {
thread_ = [this] {
bool can_update = true;
if (config_["exec-if"].isString()) {
auto res = util::command::exec(config_["exec-if"].asString());
if (res.exit_code != 0) {
output_ = util::command::execNoRead(config_["exec-if"].asString());
if (output_.exit_code != 0) {
can_update = false;
event_box_.hide();
dp.emit();
}
}
if (can_update) {
output_ = util::command::exec(config_["exec"].asString());
if (config_["exec"].isString()) {
output_ = util::command::exec(config_["exec"].asString());
}
dp.emit();
}
thread_.sleep_for(interval_);
@ -46,10 +47,9 @@ void waybar::modules::Custom::continuousWorker() {
if (!fp_) {
throw std::runtime_error("Unable to open " + cmd);
}
thread_ = [&] {
thread_ = [this, cmd] {
char* buff = nullptr;
size_t len = 0;
bool restart = false;
if (getline(&buff, &len, fp_) == -1) {
int exit_code = 1;
if (fp_) {
@ -62,27 +62,25 @@ void waybar::modules::Custom::continuousWorker() {
spdlog::error("{} stopped unexpectedly, is it endless?", name_);
}
if (config_["restart-interval"].isUInt()) {
restart = true;
pid_ = -1;
thread_.sleep_for(std::chrono::seconds(config_["restart-interval"].asUInt()));
fp_ = util::command::open(cmd, pid_);
if (!fp_) {
throw std::runtime_error("Unable to open " + cmd);
}
} else {
thread_.stop();
return;
}
}
std::string output = buff;
} else {
std::string output = buff;
// Remove last newline
if (!output.empty() && output[output.length() - 1] == '\n') {
output.erase(output.length() - 1);
}
output_ = {0, output};
dp.emit();
if (restart) {
pid_ = -1;
fp_ = util::command::open(cmd, pid_);
if (!fp_) {
throw std::runtime_error("Unable to open " + cmd);
// Remove last newline
if (!output.empty() && output[output.length() - 1] == '\n') {
output.erase(output.length() - 1);
}
thread_.sleep_for(std::chrono::seconds(config_["restart-interval"].asUInt()));
output_ = {0, output};
dp.emit();
}
};
}
@ -93,21 +91,28 @@ void waybar::modules::Custom::refresh(int sig) {
}
}
void waybar::modules::Custom::handleEvent() {
if (!config_["exec-on-event"].isBool() || config_["exec-on-event"].asBool()) {
thread_.wake_up();
}
}
bool waybar::modules::Custom::handleScroll(GdkEventScroll* e) {
auto ret = ALabel::handleScroll(e);
thread_.wake_up();
handleEvent();
return ret;
}
bool waybar::modules::Custom::handleToggle(GdkEventButton* const& e) {
auto ret = ALabel::handleToggle(e);
thread_.wake_up();
handleEvent();
return ret;
}
auto waybar::modules::Custom::update() -> void {
// Hide label if output is empty
if (config_["exec"].isString() && (output_.out.empty() || output_.exit_code != 0)) {
if ((config_["exec"].isString() || config_["exec-if"].isString()) &&
(output_.out.empty() || output_.exit_code != 0)) {
event_box_.hide();
} else {
if (config_["return-type"].asString() == "json") {
@ -126,9 +131,13 @@ auto waybar::modules::Custom::update() -> void {
label_.set_markup(str);
if (tooltipEnabled()) {
if (text_ == tooltip_) {
label_.set_tooltip_text(str);
if (label_.get_tooltip_markup() != str) {
label_.set_tooltip_markup(str);
}
} else {
label_.set_tooltip_text(tooltip_);
if (label_.get_tooltip_markup() != tooltip_) {
label_.set_tooltip_markup(tooltip_);
}
}
}
auto classes = label_.get_style_context()->list_classes();
@ -141,6 +150,8 @@ auto waybar::modules::Custom::update() -> void {
event_box_.show();
}
}
// Call parent update
ALabel::update();
}
void waybar::modules::Custom::parseOutputRaw() {

View File

@ -44,19 +44,32 @@ auto waybar::modules::Disk::update() -> void {
return;
}
auto free = pow_format(stats.f_bavail * stats.f_bsize, "B", true);
auto used = pow_format((stats.f_blocks - stats.f_bavail) * stats.f_bsize, "B", true);
auto total = pow_format(stats.f_blocks * stats.f_bsize, "B", true);
auto free = pow_format(stats.f_bavail * stats.f_frsize, "B", true);
auto used = pow_format((stats.f_blocks - stats.f_bavail) * stats.f_frsize, "B", true);
auto total = pow_format(stats.f_blocks * stats.f_frsize, "B", true);
auto percentage_used = (stats.f_blocks - stats.f_bavail) * 100 / stats.f_blocks;
auto format = format_;
auto state = getState(percentage_used);
if (!state.empty() && config_["format-" + state].isString()) {
format = config_["format-" + state].asString();
}
if (format.empty()) {
event_box_.hide();
} else {
event_box_.show();
label_.set_markup(fmt::format(format
, stats.f_bavail * 100 / stats.f_blocks
, fmt::arg("free", free)
, fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks)
, fmt::arg("used", used)
, fmt::arg("percentage_used", percentage_used)
, fmt::arg("total", total)
, fmt::arg("path", path_)
));
}
label_.set_markup(fmt::format(format_
, stats.f_bavail * 100 / stats.f_blocks
, fmt::arg("free", free)
, fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks)
, fmt::arg("used", used)
, fmt::arg("percentage_used", (stats.f_blocks - stats.f_bavail) * 100 / stats.f_blocks)
, fmt::arg("total", total)
, fmt::arg("path", path_)
));
if (tooltipEnabled()) {
std::string tooltip_format = "{used} used out of {total} on {path} ({percentage_used}%)";
if (config_["tooltip-format"].isString()) {
@ -67,10 +80,11 @@ auto waybar::modules::Disk::update() -> void {
, fmt::arg("free", free)
, fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks)
, fmt::arg("used", used)
, fmt::arg("percentage_used", (stats.f_blocks - stats.f_bavail) * 100 / stats.f_blocks)
, fmt::arg("percentage_used", percentage_used)
, fmt::arg("total", total)
, fmt::arg("path", path_)
));
}
event_box_.show();
// Call parent update
ALabel::update();
}

View File

@ -1,16 +1,28 @@
#include "modules/idle_inhibitor.hpp"
#include "idle-inhibit-unstable-v1-client-protocol.h"
#include "util/command.hpp"
std::list<waybar::AModule*> waybar::modules::IdleInhibitor::modules;
bool waybar::modules::IdleInhibitor::status = false;
waybar::modules::IdleInhibitor::IdleInhibitor(const std::string& id, const Bar& bar,
const Json::Value& config)
: ALabel(config, "idle_inhibitor", id, "{status}"),
bar_(bar),
status_("deactivated"),
idle_inhibitor_(nullptr),
pid_(-1) {
if (waybar::Client::inst()->idle_inhibit_manager == nullptr) {
throw std::runtime_error("idle-inhibit not available");
}
event_box_.add_events(Gdk::BUTTON_PRESS_MASK);
event_box_.signal_button_press_event().connect(
sigc::mem_fun(*this, &IdleInhibitor::handleToggle));
// Add this to the modules list
waybar::modules::IdleInhibitor::modules.push_back(this);
dp.emit();
}
@ -19,6 +31,10 @@ waybar::modules::IdleInhibitor::~IdleInhibitor() {
zwp_idle_inhibitor_v1_destroy(idle_inhibitor_);
idle_inhibitor_ = nullptr;
}
// Remove this from the modules list
waybar::modules::IdleInhibitor::modules.remove(this);
if (pid_ != -1) {
kill(-pid_, 9);
pid_ = -1;
@ -26,28 +42,44 @@ waybar::modules::IdleInhibitor::~IdleInhibitor() {
}
auto waybar::modules::IdleInhibitor::update() -> void {
label_.set_markup(
fmt::format(format_, fmt::arg("status", status_), fmt::arg("icon", getIcon(0, status_))));
label_.get_style_context()->add_class(status_);
if (tooltipEnabled()) {
label_.set_tooltip_text(status_);
// Check status
if (status) {
label_.get_style_context()->remove_class("deactivated");
if (idle_inhibitor_ == nullptr) {
idle_inhibitor_ = zwp_idle_inhibit_manager_v1_create_inhibitor(
waybar::Client::inst()->idle_inhibit_manager, bar_.surface);
}
} else {
label_.get_style_context()->remove_class("activated");
if (idle_inhibitor_ != nullptr) {
zwp_idle_inhibitor_v1_destroy(idle_inhibitor_);
idle_inhibitor_ = nullptr;
}
}
std::string status_text = status ? "activated" : "deactivated";
label_.set_markup(
fmt::format(format_, fmt::arg("status", status_text), fmt::arg("icon", getIcon(0, status_text))));
label_.get_style_context()->add_class(status_text);
if (tooltipEnabled()) {
label_.set_tooltip_text(status_text);
}
// Call parent update
ALabel::update();
}
bool waybar::modules::IdleInhibitor::handleToggle(GdkEventButton* const& e) {
if (e->button == 1) {
label_.get_style_context()->remove_class(status_);
if (idle_inhibitor_ != nullptr) {
zwp_idle_inhibitor_v1_destroy(idle_inhibitor_);
idle_inhibitor_ = nullptr;
status_ = "deactivated";
} else {
idle_inhibitor_ = zwp_idle_inhibit_manager_v1_create_inhibitor(
waybar::Client::inst()->idle_inhibit_manager, bar_.surface);
status_ = "activated";
status = !status;
// Make all other idle inhibitor modules update
for (auto const& module : waybar::modules::IdleInhibitor::modules) {
if (module != this) {
module->update();
}
}
click_param = status_;
}
ALabel::handleToggle(e);
return true;
}

View File

@ -1,63 +0,0 @@
#include "modules/memory.hpp"
waybar::modules::Memory::Memory(const std::string& id, const Json::Value& config)
: ALabel(config, "memory", id, "{}%", 30) {
thread_ = [this] {
dp.emit();
thread_.sleep_for(interval_);
};
}
auto waybar::modules::Memory::update() -> void {
parseMeminfo();
unsigned long memtotal = meminfo_["MemTotal"];
unsigned long memfree;
if (meminfo_.count("MemAvailable")) {
// New kernels (3.4+) have an accurate available memory field.
memfree = meminfo_["MemAvailable"];
} else {
// Old kernel; give a best-effort approximation of available memory.
memfree = meminfo_["MemFree"] + meminfo_["Buffers"] + meminfo_["Cached"] +
meminfo_["SReclaimable"] - meminfo_["Shmem"];
}
if (memtotal > 0 && memfree >= 0) {
auto total_ram_gigabytes = memtotal / std::pow(1024, 2);
int used_ram_percentage = 100 * (memtotal - memfree) / memtotal;
auto used_ram_gigabytes = (memtotal - memfree) / std::pow(1024, 2);
auto available_ram_gigabytes = memfree / std::pow(1024, 2);
getState(used_ram_percentage);
label_.set_markup(fmt::format(format_,
used_ram_percentage,
fmt::arg("total", total_ram_gigabytes),
fmt::arg("percentage", used_ram_percentage),
fmt::arg("used", used_ram_gigabytes),
fmt::arg("avail", available_ram_gigabytes)));
if (tooltipEnabled()) {
label_.set_tooltip_text(fmt::format("{:.{}f}Gb used", used_ram_gigabytes, 1));
}
event_box_.show();
} else {
event_box_.hide();
}
}
void waybar::modules::Memory::parseMeminfo() {
std::ifstream info(data_dir_);
if (!info.is_open()) {
throw std::runtime_error("Can't open " + data_dir_);
}
std::string line;
while (getline(info, line)) {
auto posDelim = line.find(':');
if (posDelim == std::string::npos) {
continue;
}
std::string name = line.substr(0, posDelim);
int64_t value = std::stol(line.substr(posDelim + 1));
meminfo_[name] = value;
}
}

View File

@ -0,0 +1,89 @@
#include "modules/memory.hpp"
#include <sys/types.h>
#include <sys/sysctl.h>
#include <unistd.h> // getpagesize
#if defined(__DragonFly__)
# include <sys/vmmeter.h> // struct vmstats
#elif defined(__NetBSD__)
# include <uvm/uvm_extern.h> // struct uvmexp_sysctl
#elif defined(__OpenBSD__)
# include <uvm/uvmexp.h> // struct uvmexp
#endif
static uint64_t get_total_memory() {
#if defined(HW_MEMSIZE) || defined(HW_PHYSMEM64)
uint64_t physmem;
#else
u_long physmem;
#endif
int mib[] = {
CTL_HW,
#if defined(HW_MEMSIZE)
HW_MEMSIZE,
#elif defined(HW_PHYSMEM64)
HW_PHYSMEM64,
#else
HW_PHYSMEM,
#endif
};
u_int miblen = sizeof(mib) / sizeof(mib[0]);
size_t sz = sizeof(physmem);
if (sysctl(mib, miblen, &physmem, &sz, NULL, 0)) {
throw std::runtime_error("sysctl hw.physmem failed");
}
return physmem;
}
static uint64_t get_free_memory() {
#if defined(__DragonFly__)
struct vmstats vms;
size_t sz = sizeof(vms);
if (sysctlbyname("vm.vmstats", &vms, &sz, NULL, 0)) {
throw std::runtime_error("sysctl vm.vmstats failed");
}
return static_cast<uint64_t>
(vms.v_free_count + vms.v_inactive_count + vms.v_cache_count)
* getpagesize();
#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
u_int v_free_count = 0, v_inactive_count = 0, v_cache_count = 0;
size_t sz = sizeof(u_int);
sysctlbyname("vm.stats.vm.v_free_count",
&v_free_count, &sz, NULL, 0);
sysctlbyname("vm.stats.vm.v_inactive_count",
&v_inactive_count, &sz, NULL, 0);
sysctlbyname("vm.stats.vm.v_cache_count",
&v_cache_count, &sz, NULL, 0);
return static_cast<uint64_t>
(v_free_count + v_inactive_count + v_cache_count)
* getpagesize();
#elif defined(__NetBSD__) || defined(__OpenBSD__)
#ifdef VM_UVMEXP2
# undef VM_UVMEXP
# define VM_UVMEXP VM_UVMEXP2
# define uvmexp uvmexp_sysctl
#else
# define filepages vnodepages
# define execpages vtextpages
#endif
int mib[] = {
CTL_VM,
VM_UVMEXP,
};
u_int miblen = sizeof(mib) / sizeof(mib[0]);
struct uvmexp uvmexp;
size_t sz = sizeof(uvmexp);
if (sysctl(mib, miblen, &uvmexp, &sz, NULL, 0)) {
throw std::runtime_error("sysctl vm.uvmexp failed");
}
return static_cast<uint64_t>
(uvmexp.free + uvmexp.inactive + uvmexp.filepages + uvmexp.execpages)
* uvmexp.pagesize;
#endif
}
void waybar::modules::Memory::parseMeminfo() {
meminfo_["MemTotal"] = get_total_memory() / 1024;
meminfo_["MemAvailable"] = get_free_memory() / 1024;
}

View File

@ -0,0 +1,67 @@
#include "modules/memory.hpp"
waybar::modules::Memory::Memory(const std::string& id, const Json::Value& config)
: ALabel(config, "memory", id, "{}%", 30) {
thread_ = [this] {
dp.emit();
thread_.sleep_for(interval_);
};
}
auto waybar::modules::Memory::update() -> void {
parseMeminfo();
unsigned long memtotal = meminfo_["MemTotal"];
unsigned long memfree;
if (meminfo_.count("MemAvailable")) {
// New kernels (3.4+) have an accurate available memory field.
memfree = meminfo_["MemAvailable"];
} else {
// Old kernel; give a best-effort approximation of available memory.
memfree = meminfo_["MemFree"] + meminfo_["Buffers"] + meminfo_["Cached"] +
meminfo_["SReclaimable"] - meminfo_["Shmem"];
}
if (memtotal > 0 && memfree >= 0) {
auto total_ram_gigabytes = memtotal / std::pow(1024, 2);
int used_ram_percentage = 100 * (memtotal - memfree) / memtotal;
auto used_ram_gigabytes = (memtotal - memfree) / std::pow(1024, 2);
auto available_ram_gigabytes = memfree / std::pow(1024, 2);
auto format = format_;
auto state = getState(used_ram_percentage);
if (!state.empty() && config_["format-" + state].isString()) {
format = config_["format-" + state].asString();
}
if (format.empty()) {
event_box_.hide();
} else {
event_box_.show();
label_.set_markup(fmt::format(format,
used_ram_percentage,
fmt::arg("total", total_ram_gigabytes),
fmt::arg("percentage", used_ram_percentage),
fmt::arg("used", used_ram_gigabytes),
fmt::arg("avail", available_ram_gigabytes)));
}
if (tooltipEnabled()) {
if (config_["tooltip-format"].isString()) {
auto tooltip_format = config_["tooltip-format"].asString();
label_.set_tooltip_text(fmt::format(tooltip_format,
used_ram_percentage,
fmt::arg("total", total_ram_gigabytes),
fmt::arg("percentage", used_ram_percentage),
fmt::arg("used", used_ram_gigabytes),
fmt::arg("avail", available_ram_gigabytes)));
} else {
label_.set_tooltip_text(fmt::format("{:.{}f}GiB used", used_ram_gigabytes, 1));
}
}
} else {
event_box_.hide();
}
// Call parent update
ALabel::update();
}

View File

@ -0,0 +1,20 @@
#include "modules/memory.hpp"
void waybar::modules::Memory::parseMeminfo() {
const std::string data_dir_ = "/proc/meminfo";
std::ifstream info(data_dir_);
if (!info.is_open()) {
throw std::runtime_error("Can't open " + data_dir_);
}
std::string line;
while (getline(info, line)) {
auto posDelim = line.find(':');
if (posDelim == std::string::npos) {
continue;
}
std::string name = line.substr(0, posDelim);
int64_t value = std::stol(line.substr(posDelim + 1));
meminfo_[name] = value;
}
}

View File

@ -1,16 +1,23 @@
#include "modules/mpd.hpp"
#include "modules/mpd/mpd.hpp"
#include <fmt/chrono.h>
#include <spdlog/spdlog.h>
#include <glibmm/ustring.h>
#include "modules/mpd/state.hpp"
#if defined(MPD_NOINLINE)
namespace waybar::modules {
#include "modules/mpd/state.inl.hpp"
} // namespace waybar::modules
#endif
waybar::modules::MPD::MPD(const std::string& id, const Json::Value& config)
: ALabel(config, "mpd", id, "{album} - {artist} - {title}", 5),
module_name_(id.empty() ? "mpd" : "mpd#" + id),
server_(nullptr),
port_(config_["port"].isUInt() ? config["port"].asUInt() : 0),
password_(config_["password"].empty() ? "" : config_["password"].asString()),
timeout_(config_["timeout"].isUInt() ? config_["timeout"].asUInt() * 1'000 : 30'000),
connection_(nullptr, &mpd_connection_free),
alternate_connection_(nullptr, &mpd_connection_free),
status_(nullptr, &mpd_status_free),
song_(nullptr, &mpd_song_free) {
if (!config_["port"].isNull() && !config_["port"].isUInt()) {
@ -28,66 +35,33 @@ waybar::modules::MPD::MPD(const std::string& id, const Json::Value& config)
server_ = config["server"].asCString();
}
event_listener().detach();
event_box_.add_events(Gdk::BUTTON_PRESS_MASK);
event_box_.signal_button_press_event().connect(sigc::mem_fun(*this, &MPD::handlePlayPause));
}
auto waybar::modules::MPD::update() -> void {
std::lock_guard guard(connection_lock_);
tryConnect();
context_.update();
// Call parent update
ALabel::update();
}
void waybar::modules::MPD::queryMPD() {
if (connection_ != nullptr) {
spdlog::debug("{}: fetching state information", module_name_);
try {
bool wasPlaying = playing();
if(!wasPlaying) {
// Wait until the periodic_updater has stopped
std::lock_guard periodic_guard(periodic_lock_);
}
fetchState();
if (!wasPlaying && playing()) {
periodic_updater().detach();
}
} catch (const std::exception& e) {
spdlog::debug("{}: fetch complete", module_name_);
} catch (std::exception const& e) {
spdlog::error("{}: {}", module_name_, e.what());
state_ = MPD_STATE_UNKNOWN;
}
dp.emit();
}
setLabel();
}
std::thread waybar::modules::MPD::event_listener() {
return std::thread([this] {
while (true) {
try {
if (connection_ == nullptr) {
// Retry periodically if no connection
dp.emit();
std::this_thread::sleep_for(interval_);
} else {
waitForEvent();
dp.emit();
}
} catch (const std::exception& e) {
spdlog::warn("{}: {}", module_name_, e.what());
}
}
});
}
std::thread waybar::modules::MPD::periodic_updater() {
return std::thread([this] {
std::lock_guard guard(periodic_lock_);
while (connection_ != nullptr && playing()) {
dp.emit();
std::this_thread::sleep_for(std::chrono::seconds(1));
}
});
}
std::string waybar::modules::MPD::getTag(mpd_tag_type type, unsigned idx) {
std::string waybar::modules::MPD::getTag(mpd_tag_type type, unsigned idx) const {
std::string result =
config_["unknown-tag"].isString() ? config_["unknown-tag"].asString() : "N/A";
const char* tag = mpd_song_get_tag(song_.get(), type, idx);
@ -124,8 +98,9 @@ void waybar::modules::MPD::setLabel() {
}
auto format = format_;
std::string artist, album_artist, album, title, date;
Glib::ustring artist, album_artist, album, title;
std::string date;
int song_pos = 0, queue_length = 0;
std::chrono::seconds elapsedTime, totalTime;
std::string stateIcon = "";
@ -141,8 +116,8 @@ void waybar::modules::MPD::setLabel() {
label_.get_style_context()->add_class("playing");
label_.get_style_context()->remove_class("paused");
} else if (paused()) {
format =
config_["format-paused"].isString() ? config_["format-paused"].asString() : config_["format"].asString();
format = config_["format-paused"].isString() ? config_["format-paused"].asString()
: config_["format"].asString();
label_.get_style_context()->add_class("paused");
label_.get_style_context()->remove_class("playing");
}
@ -154,6 +129,8 @@ void waybar::modules::MPD::setLabel() {
album = getTag(MPD_TAG_ALBUM);
title = getTag(MPD_TAG_TITLE);
date = getTag(MPD_TAG_DATE);
song_pos = mpd_status_get_song_pos(status_.get());
queue_length = mpd_status_get_queue_length(status_.get());
elapsedTime = std::chrono::seconds(mpd_status_get_elapsed_time(status_.get()));
totalTime = std::chrono::seconds(mpd_status_get_total_time(status_.get()));
}
@ -166,43 +143,60 @@ void waybar::modules::MPD::setLabel() {
std::string repeatIcon = getOptionIcon("repeat", repeatActivated);
bool singleActivated = mpd_status_get_single(status_.get());
std::string singleIcon = getOptionIcon("single", singleActivated);
if (config_["artist-len"].isInt()) artist = artist.substr(0, config_["artist-len"].asInt());
if (config_["album-artist-len"].isInt()) album_artist = album_artist.substr(0, config_["album-artist-len"].asInt());
if (config_["album-len"].isInt()) album = album.substr(0, config_["album-len"].asInt());
if (config_["title-len"].isInt()) title = title.substr(0,config_["title-len"].asInt());
// TODO: format can fail
label_.set_markup(
fmt::format(format,
fmt::arg("artist", Glib::Markup::escape_text(artist).raw()),
fmt::arg("albumArtist", Glib::Markup::escape_text(album_artist).raw()),
fmt::arg("album", Glib::Markup::escape_text(album).raw()),
fmt::arg("title", Glib::Markup::escape_text(title).raw()),
fmt::arg("date", Glib::Markup::escape_text(date).raw()),
fmt::arg("elapsedTime", elapsedTime),
fmt::arg("totalTime", totalTime),
fmt::arg("stateIcon", stateIcon),
fmt::arg("consumeIcon", consumeIcon),
fmt::arg("randomIcon", randomIcon),
fmt::arg("repeatIcon", repeatIcon),
fmt::arg("singleIcon", singleIcon)));
try {
label_.set_markup(
fmt::format(format,
fmt::arg("artist", Glib::Markup::escape_text(artist).raw()),
fmt::arg("albumArtist", Glib::Markup::escape_text(album_artist).raw()),
fmt::arg("album", Glib::Markup::escape_text(album).raw()),
fmt::arg("title", Glib::Markup::escape_text(title).raw()),
fmt::arg("date", Glib::Markup::escape_text(date).raw()),
fmt::arg("elapsedTime", elapsedTime),
fmt::arg("totalTime", totalTime),
fmt::arg("songPosition", song_pos),
fmt::arg("queueLength", queue_length),
fmt::arg("stateIcon", stateIcon),
fmt::arg("consumeIcon", consumeIcon),
fmt::arg("randomIcon", randomIcon),
fmt::arg("repeatIcon", repeatIcon),
fmt::arg("singleIcon", singleIcon)));
} catch (fmt::format_error const& e) {
spdlog::warn("mpd: format error: {}", e.what());
}
if (tooltipEnabled()) {
std::string tooltip_format;
tooltip_format = config_["tooltip-format"].isString() ? config_["tooltip-format"].asString()
: "MPD (connected)";
auto tooltip_text = fmt::format(tooltip_format,
fmt::arg("artist", artist),
fmt::arg("albumArtist", album_artist),
fmt::arg("album", album),
fmt::arg("title", title),
fmt::arg("date", date),
fmt::arg("stateIcon", stateIcon),
fmt::arg("consumeIcon", consumeIcon),
fmt::arg("randomIcon", randomIcon),
fmt::arg("repeatIcon", repeatIcon),
fmt::arg("singleIcon", singleIcon));
label_.set_tooltip_text(tooltip_text);
try {
auto tooltip_text = fmt::format(tooltip_format,
fmt::arg("artist", artist.raw()),
fmt::arg("albumArtist", album_artist.raw()),
fmt::arg("album", album.raw()),
fmt::arg("title", title.raw()),
fmt::arg("date", date),
fmt::arg("elapsedTime", elapsedTime),
fmt::arg("totalTime", totalTime),
fmt::arg("songPosition", song_pos),
fmt::arg("queueLength", queue_length),
fmt::arg("stateIcon", stateIcon),
fmt::arg("consumeIcon", consumeIcon),
fmt::arg("randomIcon", randomIcon),
fmt::arg("repeatIcon", repeatIcon),
fmt::arg("singleIcon", singleIcon));
label_.set_tooltip_text(tooltip_text);
} catch (fmt::format_error const& e) {
spdlog::warn("mpd: format error (tooltip): {}", e.what());
}
}
}
std::string waybar::modules::MPD::getStateIcon() {
std::string waybar::modules::MPD::getStateIcon() const {
if (!config_["state-icons"].isObject()) {
return "";
}
@ -224,7 +218,7 @@ std::string waybar::modules::MPD::getStateIcon() {
}
}
std::string waybar::modules::MPD::getOptionIcon(std::string optionName, bool activated) {
std::string waybar::modules::MPD::getOptionIcon(std::string optionName, bool activated) const {
if (!config_[optionName + "-icons"].isObject()) {
return "";
}
@ -247,25 +241,30 @@ void waybar::modules::MPD::tryConnect() {
}
connection_ =
unique_connection(mpd_connection_new(server_, port_, timeout_), &mpd_connection_free);
detail::unique_connection(mpd_connection_new(server_, port_, timeout_), &mpd_connection_free);
alternate_connection_ =
unique_connection(mpd_connection_new(server_, port_, timeout_), &mpd_connection_free);
if (connection_ == nullptr || alternate_connection_ == nullptr) {
if (connection_ == nullptr) {
spdlog::error("{}: Failed to connect to MPD", module_name_);
connection_.reset();
alternate_connection_.reset();
return;
}
try {
checkErrors(connection_.get());
spdlog::info("{}: Connected to MPD", module_name_);
spdlog::debug("{}: Connected to MPD", module_name_);
if (!password_.empty()) {
bool res = mpd_run_password(connection_.get(), password_.c_str());
if (!res) {
spdlog::error("{}: Wrong MPD password", module_name_);
connection_.reset();
return;
}
checkErrors(connection_.get());
}
} catch (std::runtime_error& e) {
spdlog::error("{}: Failed to connect to MPD: {}", module_name_, e.what());
connection_.reset();
alternate_connection_.reset();
}
}
@ -278,48 +277,34 @@ void waybar::modules::MPD::checkErrors(mpd_connection* conn) {
case MPD_ERROR_CLOSED:
mpd_connection_clear_error(conn);
connection_.reset();
alternate_connection_.reset();
state_ = MPD_STATE_UNKNOWN;
throw std::runtime_error("Connection to MPD closed");
default:
auto error_message = mpd_connection_get_error_message(conn);
mpd_connection_clear_error(conn);
throw std::runtime_error(std::string(error_message));
if (conn) {
auto error_message = mpd_connection_get_error_message(conn);
std::string error(error_message);
mpd_connection_clear_error(conn);
throw std::runtime_error(error);
}
throw std::runtime_error("Invalid connection");
}
}
void waybar::modules::MPD::fetchState() {
if (connection_ == nullptr) {
spdlog::error("{}: Not connected to MPD", module_name_);
return;
}
auto conn = connection_.get();
status_ = unique_status(mpd_run_status(conn), &mpd_status_free);
status_ = detail::unique_status(mpd_run_status(conn), &mpd_status_free);
checkErrors(conn);
state_ = mpd_status_get_state(status_.get());
checkErrors(conn);
song_ = unique_song(mpd_run_current_song(conn), &mpd_song_free);
checkErrors(conn);
}
void waybar::modules::MPD::waitForEvent() {
auto conn = alternate_connection_.get();
// Wait for a player (play/pause), option (random, shuffle, etc.), or playlist
// change
if (!mpd_send_idle_mask(
conn, static_cast<mpd_idle>(MPD_IDLE_PLAYER | MPD_IDLE_OPTIONS | MPD_IDLE_QUEUE))) {
checkErrors(conn);
return;
}
// alternate_idle_ = true;
// See issue #277:
// https://github.com/Alexays/Waybar/issues/277
mpd_recv_idle(conn, /* disable_timeout = */ false);
// See issue #281:
// https://github.com/Alexays/Waybar/issues/281
std::lock_guard guard(connection_lock_);
checkErrors(conn);
mpd_response_finish(conn);
song_ = detail::unique_song(mpd_run_current_song(conn), &mpd_song_free);
checkErrors(conn);
}
@ -329,24 +314,13 @@ bool waybar::modules::MPD::handlePlayPause(GdkEventButton* const& e) {
}
if (e->button == 1) {
std::lock_guard guard(connection_lock_);
if (stopped()) {
mpd_run_play(connection_.get());
} else {
mpd_run_toggle_pause(connection_.get());
}
if (state_ == MPD_STATE_PLAY)
context_.pause();
else
context_.play();
} else if (e->button == 3) {
std::lock_guard guard(connection_lock_);
mpd_run_stop(connection_.get());
context_.stop();
}
return true;
}
bool waybar::modules::MPD::stopped() {
return connection_ == nullptr || state_ == MPD_STATE_UNKNOWN || state_ == MPD_STATE_STOP;
}
bool waybar::modules::MPD::playing() { return connection_ != nullptr && state_ == MPD_STATE_PLAY; }
bool waybar::modules::MPD::paused() { return connection_ != nullptr && state_ == MPD_STATE_PAUSE; }

383
src/modules/mpd/state.cpp Normal file
View File

@ -0,0 +1,383 @@
#include "modules/mpd/state.hpp"
#include <fmt/chrono.h>
#include <spdlog/spdlog.h>
#include "modules/mpd/mpd.hpp"
#if defined(MPD_NOINLINE)
namespace waybar::modules {
#include "modules/mpd/state.inl.hpp"
} // namespace waybar::modules
#endif
namespace waybar::modules::detail {
#define IDLE_RUN_NOIDLE_AND_CMD(...) \
if (idle_connection_.connected()) { \
idle_connection_.disconnect(); \
auto conn = ctx_->connection().get(); \
if (!mpd_run_noidle(conn)) { \
if (mpd_connection_get_error(conn) != MPD_ERROR_SUCCESS) { \
spdlog::error("mpd: Idle: failed to unregister for IDLE events"); \
ctx_->checkErrors(conn); \
} \
} \
__VA_ARGS__; \
}
void Idle::play() {
IDLE_RUN_NOIDLE_AND_CMD(mpd_run_play(conn));
ctx_->setState(std::make_unique<Playing>(ctx_));
}
void Idle::pause() {
IDLE_RUN_NOIDLE_AND_CMD(mpd_run_pause(conn, true));
ctx_->setState(std::make_unique<Paused>(ctx_));
}
void Idle::stop() {
IDLE_RUN_NOIDLE_AND_CMD(mpd_run_stop(conn));
ctx_->setState(std::make_unique<Stopped>(ctx_));
}
#undef IDLE_RUN_NOIDLE_AND_CMD
void Idle::update() noexcept {
// This is intentionally blank.
}
void Idle::entry() noexcept {
auto conn = ctx_->connection().get();
assert(conn != nullptr);
if (!mpd_send_idle_mask(
conn, static_cast<mpd_idle>(MPD_IDLE_PLAYER | MPD_IDLE_OPTIONS | MPD_IDLE_QUEUE))) {
ctx_->checkErrors(conn);
spdlog::error("mpd: Idle: failed to register for IDLE events");
} else {
spdlog::debug("mpd: Idle: watching FD");
sigc::slot<bool, Glib::IOCondition const&> idle_slot = sigc::mem_fun(*this, &Idle::on_io);
idle_connection_ =
Glib::signal_io().connect(idle_slot,
mpd_connection_get_fd(conn),
Glib::IO_IN | Glib::IO_PRI | Glib::IO_ERR | Glib::IO_HUP);
}
}
void Idle::exit() noexcept {
if (idle_connection_.connected()) {
idle_connection_.disconnect();
spdlog::debug("mpd: Idle: unwatching FD");
}
}
bool Idle::on_io(Glib::IOCondition const&) {
auto conn = ctx_->connection().get();
// callback should do this:
enum mpd_idle events = mpd_recv_idle(conn, /* ignore_timeout?= */ false);
spdlog::debug("mpd: Idle: recv_idle events -> {}", events);
mpd_response_finish(conn);
try {
ctx_->checkErrors(conn);
} catch (std::exception const& e) {
spdlog::warn("mpd: Idle: error: {}", e.what());
ctx_->setState(std::make_unique<Disconnected>(ctx_));
return false;
}
ctx_->fetchState();
mpd_state state = ctx_->state();
if (state == MPD_STATE_STOP) {
ctx_->emit();
ctx_->setState(std::make_unique<Stopped>(ctx_));
} else if (state == MPD_STATE_PLAY) {
ctx_->emit();
ctx_->setState(std::make_unique<Playing>(ctx_));
} else if (state == MPD_STATE_PAUSE) {
ctx_->emit();
ctx_->setState(std::make_unique<Paused>(ctx_));
} else {
ctx_->emit();
// self transition
ctx_->setState(std::make_unique<Idle>(ctx_));
}
return false;
}
void Playing::entry() noexcept {
sigc::slot<bool> timer_slot = sigc::mem_fun(*this, &Playing::on_timer);
timer_connection_ = Glib::signal_timeout().connect(timer_slot, /* milliseconds */ 1'000);
spdlog::debug("mpd: Playing: enabled 1 second periodic timer.");
}
void Playing::exit() noexcept {
if (timer_connection_.connected()) {
timer_connection_.disconnect();
spdlog::debug("mpd: Playing: disabled 1 second periodic timer.");
}
}
bool Playing::on_timer() {
// Attempt to connect with MPD.
try {
ctx_->tryConnect();
// Success?
if (!ctx_->is_connected()) {
ctx_->setState(std::make_unique<Disconnected>(ctx_));
return false;
}
ctx_->fetchState();
if (!ctx_->is_playing()) {
if (ctx_->is_paused()) {
ctx_->setState(std::make_unique<Paused>(ctx_));
} else {
ctx_->setState(std::make_unique<Stopped>(ctx_));
}
return false;
}
ctx_->queryMPD();
ctx_->emit();
} catch (std::exception const& e) {
spdlog::warn("mpd: Playing: error: {}", e.what());
ctx_->setState(std::make_unique<Disconnected>(ctx_));
return false;
}
return true;
}
void Playing::stop() {
if (timer_connection_.connected()) {
timer_connection_.disconnect();
mpd_run_stop(ctx_->connection().get());
}
ctx_->setState(std::make_unique<Stopped>(ctx_));
}
void Playing::pause() {
if (timer_connection_.connected()) {
timer_connection_.disconnect();
mpd_run_pause(ctx_->connection().get(), true);
}
ctx_->setState(std::make_unique<Paused>(ctx_));
}
void Playing::update() noexcept { ctx_->do_update(); }
void Paused::entry() noexcept {
sigc::slot<bool> timer_slot = sigc::mem_fun(*this, &Paused::on_timer);
timer_connection_ = Glib::signal_timeout().connect(timer_slot, /* milliseconds */ 200);
spdlog::debug("mpd: Paused: enabled 200 ms periodic timer.");
}
void Paused::exit() noexcept {
if (timer_connection_.connected()) {
timer_connection_.disconnect();
spdlog::debug("mpd: Paused: disabled 200 ms periodic timer.");
}
}
bool Paused::on_timer() {
bool rc = true;
// Attempt to connect with MPD.
try {
ctx_->tryConnect();
// Success?
if (!ctx_->is_connected()) {
ctx_->setState(std::make_unique<Disconnected>(ctx_));
return false;
}
ctx_->fetchState();
ctx_->emit();
if (ctx_->is_paused()) {
ctx_->setState(std::make_unique<Idle>(ctx_));
rc = false;
} else if (ctx_->is_playing()) {
ctx_->setState(std::make_unique<Playing>(ctx_));
rc = false;
} else if (ctx_->is_stopped()) {
ctx_->setState(std::make_unique<Stopped>(ctx_));
rc = false;
}
} catch (std::exception const& e) {
spdlog::warn("mpd: Paused: error: {}", e.what());
ctx_->setState(std::make_unique<Disconnected>(ctx_));
rc = false;
}
return rc;
}
void Paused::play() {
if (timer_connection_.connected()) {
timer_connection_.disconnect();
mpd_run_play(ctx_->connection().get());
}
ctx_->setState(std::make_unique<Playing>(ctx_));
}
void Paused::stop() {
if (timer_connection_.connected()) {
timer_connection_.disconnect();
mpd_run_stop(ctx_->connection().get());
}
ctx_->setState(std::make_unique<Stopped>(ctx_));
}
void Paused::update() noexcept { ctx_->do_update(); }
void Stopped::entry() noexcept {
sigc::slot<bool> timer_slot = sigc::mem_fun(*this, &Stopped::on_timer);
timer_connection_ = Glib::signal_timeout().connect(timer_slot, /* milliseconds */ 200);
spdlog::debug("mpd: Stopped: enabled 200 ms periodic timer.");
}
void Stopped::exit() noexcept {
if (timer_connection_.connected()) {
timer_connection_.disconnect();
spdlog::debug("mpd: Stopped: disabled 200 ms periodic timer.");
}
}
bool Stopped::on_timer() {
bool rc = true;
// Attempt to connect with MPD.
try {
ctx_->tryConnect();
// Success?
if (!ctx_->is_connected()) {
ctx_->setState(std::make_unique<Disconnected>(ctx_));
return false;
}
ctx_->fetchState();
ctx_->emit();
if (ctx_->is_stopped()) {
ctx_->setState(std::make_unique<Idle>(ctx_));
rc = false;
} else if (ctx_->is_playing()) {
ctx_->setState(std::make_unique<Playing>(ctx_));
rc = false;
} else if (ctx_->is_paused()) {
ctx_->setState(std::make_unique<Paused>(ctx_));
rc = false;
}
} catch (std::exception const& e) {
spdlog::warn("mpd: Stopped: error: {}", e.what());
ctx_->setState(std::make_unique<Disconnected>(ctx_));
rc = false;
}
return rc;
}
void Stopped::play() {
if (timer_connection_.connected()) {
timer_connection_.disconnect();
mpd_run_play(ctx_->connection().get());
}
ctx_->setState(std::make_unique<Playing>(ctx_));
}
void Stopped::pause() {
if (timer_connection_.connected()) {
timer_connection_.disconnect();
mpd_run_pause(ctx_->connection().get(), true);
}
ctx_->setState(std::make_unique<Paused>(ctx_));
}
void Stopped::update() noexcept { ctx_->do_update(); }
void Disconnected::arm_timer(int interval) noexcept {
// unregister timer, if present
disarm_timer();
// register timer
sigc::slot<bool> timer_slot = sigc::mem_fun(*this, &Disconnected::on_timer);
timer_connection_ =
Glib::signal_timeout().connect(timer_slot, interval);
spdlog::debug("mpd: Disconnected: enabled interval timer.");
}
void Disconnected::disarm_timer() noexcept {
// unregister timer, if present
if (timer_connection_.connected()) {
timer_connection_.disconnect();
spdlog::debug("mpd: Disconnected: disabled interval timer.");
}
}
void Disconnected::entry() noexcept {
ctx_->emit();
arm_timer(1'000);
}
void Disconnected::exit() noexcept {
disarm_timer();
}
bool Disconnected::on_timer() {
// Attempt to connect with MPD.
try {
ctx_->tryConnect();
// Success?
if (ctx_->is_connected()) {
ctx_->fetchState();
ctx_->emit();
if (ctx_->is_playing()) {
ctx_->setState(std::make_unique<Playing>(ctx_));
} else if (ctx_->is_paused()) {
ctx_->setState(std::make_unique<Paused>(ctx_));
} else {
ctx_->setState(std::make_unique<Stopped>(ctx_));
}
return false; // do not rearm timer
}
} catch (std::exception const& e) {
spdlog::warn("mpd: Disconnected: error: {}", e.what());
}
arm_timer(ctx_->interval() * 1'000);
return false;
}
void Disconnected::update() noexcept { ctx_->do_update(); }
} // namespace waybar::modules::detail

View File

@ -3,8 +3,11 @@
#include <sys/eventfd.h>
#include <fstream>
#include <cassert>
#include <optional>
#include "util/format.hpp"
#ifdef WANT_RFKILL
#include "util/rfkill.hpp"
#endif
namespace {
@ -86,6 +89,9 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf
cidr_(-1),
signal_strength_dbm_(0),
signal_strength_(0),
#ifdef WANT_RFKILL
rfkill_{RFKILL_TYPE_WLAN},
#endif
frequency_(0) {
auto down_octets = read_netstat(BANDWIDTH_CATEGORY, BANDWIDTH_DOWN_TOTAL_KEY);
auto up_octets = read_netstat(BANDWIDTH_CATEGORY, BANDWIDTH_UP_TOTAL_KEY);
@ -117,8 +123,6 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf
waybar::modules::Network::~Network() {
if (ev_fd_ > -1) {
eventfd_write(ev_fd_, 1);
std::this_thread::sleep_for(std::chrono::milliseconds(150));
close(ev_fd_);
}
if (efd_ > -1) {
@ -191,11 +195,12 @@ void waybar::modules::Network::createInfoSocket() {
}
nl80211_id_ = genl_ctrl_resolve(sock_, "nl80211");
if (nl80211_id_ < 0) {
throw std::runtime_error("Can't resolve nl80211 interface");
spdlog::warn("Can't resolve nl80211 interface");
}
}
void waybar::modules::Network::worker() {
// update via here not working
thread_timer_ = [this] {
{
std::lock_guard<std::mutex> lock(mutex_);
@ -206,6 +211,17 @@ void waybar::modules::Network::worker() {
}
thread_timer_.sleep_for(interval_);
};
#ifdef WANT_RFKILL
rfkill_.on_update.connect([this](auto &) {
/* If we are here, it's likely that the network thread already holds the mutex and will be
* holding it for a next few seconds.
* Let's delegate the update to the timer thread instead of blocking the main thread.
*/
thread_timer_.wake_up();
});
#else
spdlog::warn("Waybar has been built without rfkill support.");
#endif
thread_ = [this] {
std::array<struct epoll_event, EPOLL_MAX> events{};
@ -222,7 +238,13 @@ void waybar::modules::Network::worker() {
}
const std::string waybar::modules::Network::getNetworkState() const {
if (ifid_ == -1) return "disconnected";
if (ifid_ == -1) {
#ifdef WANT_RFKILL
if (rfkill_.getState())
return "disabled";
#endif
return "disconnected";
}
if (ipaddr_.empty()) return "linked";
if (essid_.empty()) return "ethernet";
return "wifi";
@ -308,13 +330,16 @@ auto waybar::modules::Network::update() -> void {
fmt::arg("bandwidthUpBits", pow_format(bandwidth_up * 8ull / interval_.count(), "b/s")),
fmt::arg("bandwidthDownOctets", pow_format(bandwidth_down / interval_.count(), "o/s")),
fmt::arg("bandwidthUpOctets", pow_format(bandwidth_up / interval_.count(), "o/s")));
if (label_.get_tooltip_text() != text) {
if (label_.get_tooltip_text() != tooltip_text) {
label_.set_tooltip_text(tooltip_text);
}
} else if (label_.get_tooltip_text() != text) {
label_.set_tooltip_text(text);
}
}
// Call parent update
ALabel::update();
}
// Based on https://gist.github.com/Yawning/c70d804d4b8ae78cc698

View File

@ -207,6 +207,7 @@ const std::string waybar::modules::Pulseaudio::getPortIcon() const {
auto waybar::modules::Pulseaudio::update() -> void {
auto format = format_;
std::string tooltip_format;
if (!alt_) {
std::string format_name = "format";
if (monitor_.find("a2dp_sink") != std::string::npos) {
@ -222,27 +223,55 @@ auto waybar::modules::Pulseaudio::update() -> void {
}
format_name = format_name + "-muted";
label_.get_style_context()->add_class("muted");
label_.get_style_context()->add_class("sink-muted");
} else {
label_.get_style_context()->remove_class("muted");
label_.get_style_context()->remove_class("sink-muted");
}
format =
config_[format_name].isString() ? config_[format_name].asString() : format;
}
// TODO: find a better way to split source/sink
std::string format_source = "{volume}%";
if (source_muted_ && config_["format-source-muted"].isString()) {
format_source = config_["format-source-muted"].asString();
} else if (!source_muted_ && config_["format-source"].isString()) {
format_source = config_["format-source"].asString();
if (source_muted_) {
label_.get_style_context()->add_class("source-muted");
if (config_["format-source-muted"].isString()) {
format_source = config_["format-source-muted"].asString();
}
} else {
label_.get_style_context()->remove_class("source-muted");
if (config_["format-source-muted"].isString()) {
format_source = config_["format-source"].asString();
}
}
format_source = fmt::format(format_source, fmt::arg("volume", source_volume_));
label_.set_markup(fmt::format(format,
fmt::arg("desc", desc_),
fmt::arg("volume", volume_),
fmt::arg("format_source", format_source),
fmt::arg("source_volume", source_volume_),
fmt::arg("source_desc", source_desc_),
fmt::arg("icon", getIcon(volume_, getPortIcon()))));
getState(volume_);
if (tooltipEnabled()) {
label_.set_tooltip_text(desc_);
if (tooltip_format.empty() && config_["tooltip-format"].isString()) {
tooltip_format = config_["tooltip-format"].asString();
}
if (!tooltip_format.empty()) {
label_.set_tooltip_text(fmt::format(
tooltip_format,
fmt::arg("desc", desc_),
fmt::arg("volume", volume_),
fmt::arg("format_source", format_source),
fmt::arg("source_volume", source_volume_),
fmt::arg("source_desc", source_desc_),
fmt::arg("icon", getIcon(volume_, getPortIcon()))));
} else {
label_.set_tooltip_text(desc_);
}
}
// Call parent update
ALabel::update();
}

132
src/modules/river/tags.cpp Normal file
View File

@ -0,0 +1,132 @@
#include <gtkmm/button.h>
#include <gtkmm/label.h>
#include <spdlog/spdlog.h>
#include <wayland-client.h>
#include <algorithm>
#include "client.hpp"
#include "modules/river/tags.hpp"
#include "river-status-unstable-v1-client-protocol.h"
#include "xdg-output-unstable-v1-client-protocol.h"
namespace waybar::modules::river {
static void listen_focused_tags(void *data, struct zriver_output_status_v1 *zriver_output_status_v1,
uint32_t tags) {
static_cast<Tags *>(data)->handle_focused_tags(tags);
}
static void listen_view_tags(void *data, struct zriver_output_status_v1 *zriver_output_status_v1,
struct wl_array *tags) {
static_cast<Tags *>(data)->handle_view_tags(tags);
}
static const zriver_output_status_v1_listener output_status_listener_impl{
.focused_tags = listen_focused_tags,
.view_tags = listen_view_tags,
};
static void handle_global(void *data, struct wl_registry *registry, uint32_t name,
const char *interface, uint32_t version) {
if (std::strcmp(interface, zriver_status_manager_v1_interface.name) == 0) {
static_cast<Tags *>(data)->status_manager_ = static_cast<struct zriver_status_manager_v1 *>(
wl_registry_bind(registry, name, &zriver_status_manager_v1_interface, version));
}
}
static void handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) {
/* Ignore event */
}
static const wl_registry_listener registry_listener_impl = {.global = handle_global,
.global_remove = handle_global_remove};
Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &config)
: waybar::AModule(config, "tags", id, false, false),
status_manager_{nullptr},
bar_(bar),
box_{bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0},
output_status_{nullptr} {
struct wl_display * display = Client::inst()->wl_display;
struct wl_registry *registry = wl_display_get_registry(display);
wl_registry_add_listener(registry, &registry_listener_impl, this);
wl_display_roundtrip(display);
if (!status_manager_) {
spdlog::error("river_status_manager_v1 not advertised");
return;
}
box_.set_name("tags");
if (!id.empty()) {
box_.get_style_context()->add_class(id);
}
event_box_.add(box_);
// Default to 9 tags
const uint32_t num_tags = config["num-tags"].isUInt() ? config_["num-tags"].asUInt() : 9;
std::vector<std::string> tag_labels(num_tags);
for (uint32_t tag = 0; tag < num_tags; ++tag) {
tag_labels[tag] = std::to_string(tag+1);
}
const Json::Value custom_labels = config["tag-labels"];
if (custom_labels.isArray() && !custom_labels.empty()) {
for (uint32_t tag = 0; tag < std::min(num_tags, custom_labels.size()); ++tag) {
tag_labels[tag] = custom_labels[tag].asString();
}
}
for (const auto &tag_label : tag_labels) {
Gtk::Button &button = buttons_.emplace_back(tag_label);
button.set_relief(Gtk::RELIEF_NONE);
box_.pack_start(button, false, false, 0);
button.show();
}
struct wl_output *output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj());
output_status_ = zriver_status_manager_v1_get_river_output_status(status_manager_, output);
zriver_output_status_v1_add_listener(output_status_, &output_status_listener_impl, this);
zriver_status_manager_v1_destroy(status_manager_);
}
Tags::~Tags() {
if (output_status_) {
zriver_output_status_v1_destroy(output_status_);
}
}
void Tags::handle_focused_tags(uint32_t tags) {
uint32_t i = 0;
for (auto &button : buttons_) {
if ((1 << i) & tags) {
button.get_style_context()->add_class("focused");
} else {
button.get_style_context()->remove_class("focused");
}
++i;
}
}
void Tags::handle_view_tags(struct wl_array *view_tags) {
// First clear all occupied state
for (auto &button : buttons_) {
button.get_style_context()->remove_class("occupied");
}
// Set tags with a view to occupied
uint32_t *start = static_cast<uint32_t *>(view_tags->data);
for (uint32_t *tags = start; tags < start + view_tags->size / sizeof(uint32_t); ++tags) {
uint32_t i = 0;
for (auto &button : buttons_) {
if (*tags & (1 << i)) {
button.get_style_context()->add_class("occupied");
}
++i;
}
}
}
} /* namespace waybar::modules::river */

View File

@ -0,0 +1,33 @@
#include "modules/simpleclock.hpp"
#include <time.h>
waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
: ALabel(config, "clock", id, "{:%H:%M}", 60) {
thread_ = [this] {
dp.emit();
auto now = std::chrono::system_clock::now();
auto timeout = std::chrono::floor<std::chrono::seconds>(now + interval_);
auto diff = std::chrono::seconds(timeout.time_since_epoch().count() % interval_.count());
thread_.sleep_until(timeout - diff);
};
}
auto waybar::modules::Clock::update() -> void {
tzset(); // Update timezone information
auto now = std::chrono::system_clock::now();
auto localtime = fmt::localtime(std::chrono::system_clock::to_time_t(now));
auto text = fmt::format(format_, localtime);
label_.set_markup(text);
if (tooltipEnabled()) {
if (config_["tooltip-format"].isString()) {
auto tooltip_format = config_["tooltip-format"].asString();
auto tooltip_text = fmt::format(tooltip_format, localtime);
label_.set_tooltip_text(tooltip_text);
} else {
label_.set_tooltip_text(text);
}
}
// Call parent update
ALabel::update();
}

201
src/modules/sndio.cpp Normal file
View File

@ -0,0 +1,201 @@
#include "modules/sndio.hpp"
#include <algorithm>
#include <cstdlib>
#include <poll.h>
#include <fmt/format.h>
#include <spdlog/spdlog.h>
namespace waybar::modules {
void ondesc(void *arg, struct sioctl_desc *d, int curval) {
auto self = static_cast<Sndio*>(arg);
if (d == NULL) {
// d is NULL when the list is done
return;
}
self->set_desc(d, curval);
}
void onval(void *arg, unsigned int addr, unsigned int val) {
auto self = static_cast<Sndio*>(arg);
self->put_val(addr, val);
}
auto Sndio::connect_to_sndio() -> void {
hdl_ = sioctl_open(SIO_DEVANY, SIOCTL_READ | SIOCTL_WRITE, 0);
if (hdl_ == nullptr) {
throw std::runtime_error("sioctl_open() failed.");
}
if (sioctl_ondesc(hdl_, ondesc, this) == 0) {
throw std::runtime_error("sioctl_ondesc() failed.");
}
if (sioctl_onval(hdl_, onval, this) == 0) {
throw std::runtime_error("sioctl_onval() failed.");
}
pfds_.reserve(sioctl_nfds(hdl_));
}
Sndio::Sndio(const std::string &id, const Json::Value &config)
: ALabel(config, "sndio", id, "{volume}%", 1),
hdl_(nullptr),
pfds_(0),
addr_(0),
volume_(0),
old_volume_(0),
maxval_(0),
muted_(false) {
connect_to_sndio();
event_box_.show();
event_box_.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK | Gdk::BUTTON_PRESS_MASK);
event_box_.signal_scroll_event().connect(
sigc::mem_fun(*this, &Sndio::handleScroll));
event_box_.signal_button_press_event().connect(
sigc::mem_fun(*this, &Sndio::handleToggle));
thread_ = [this] {
dp.emit();
int nfds = sioctl_pollfd(hdl_, pfds_.data(), POLLIN);
if (nfds == 0) {
throw std::runtime_error("sioctl_pollfd() failed.");
}
while (poll(pfds_.data(), nfds, -1) < 0) {
if (errno != EINTR) {
throw std::runtime_error("poll() failed.");
}
}
int revents = sioctl_revents(hdl_, pfds_.data());
if (revents & POLLHUP) {
spdlog::warn("sndio disconnected!");
sioctl_close(hdl_);
hdl_ = nullptr;
// reconnection loop
while (thread_.isRunning()) {
try {
connect_to_sndio();
} catch(std::runtime_error const& e) {
// avoid leaking hdl_
if (hdl_) {
sioctl_close(hdl_);
hdl_ = nullptr;
}
// rate limiting for the retries
thread_.sleep_for(interval_);
continue;
}
spdlog::warn("sndio reconnected!");
break;
}
}
};
}
Sndio::~Sndio() {
sioctl_close(hdl_);
}
auto Sndio::update() -> void {
auto format = format_;
unsigned int vol = 100. * static_cast<double>(volume_) / static_cast<double>(maxval_);
if (volume_ == 0) {
label_.get_style_context()->add_class("muted");
} else {
label_.get_style_context()->remove_class("muted");
}
label_.set_markup(fmt::format(format,
fmt::arg("volume", vol),
fmt::arg("raw_value", volume_)));
ALabel::update();
}
auto Sndio::set_desc(struct sioctl_desc *d, unsigned int val) -> void {
std::string name{d->func};
std::string node_name{d->node0.name};
if (name == "level" && node_name == "output" && d->type == SIOCTL_NUM) {
// store addr for output.level value, used in put_val
addr_ = d->addr;
maxval_ = d->maxval;
volume_ = val;
}
}
auto Sndio::put_val(unsigned int addr, unsigned int val) -> void {
if (addr == addr_) {
volume_ = val;
}
}
bool Sndio::handleScroll(GdkEventScroll *e) {
// change the volume only when no user provided
// events are configured
if (config_["on-scroll-up"].isString() || config_["on-scroll-down"].isString()) {
return AModule::handleScroll(e);
}
// only try to talk to sndio if connected
if (hdl_ == nullptr) return true;
auto dir = AModule::getScrollDir(e);
if (dir == SCROLL_DIR::NONE) {
return true;
}
int step = 5;
if (config_["scroll-step"].isInt()) {
step = config_["scroll-step"].asInt();
}
int new_volume = volume_;
if (muted_) {
new_volume = old_volume_;
}
if (dir == SCROLL_DIR::UP) {
new_volume += step;
} else if (dir == SCROLL_DIR::DOWN) {
new_volume -= step;
}
new_volume = std::clamp(new_volume, 0, static_cast<int>(maxval_));
// quits muted mode if volume changes
muted_ = false;
sioctl_setval(hdl_, addr_, new_volume);
return true;
}
bool Sndio::handleToggle(GdkEventButton* const& e) {
// toggle mute only when no user provided events are configured
if (config_["on-click"].isString()) {
return AModule::handleToggle(e);
}
// only try to talk to sndio if connected
if (hdl_ == nullptr) return true;
muted_ = !muted_;
if (muted_) {
// store old volume to be able to restore it later
old_volume_ = volume_;
sioctl_setval(hdl_, addr_, 0);
} else {
sioctl_setval(hdl_, addr_, old_volume_);
}
return true;
}
} /* namespace waybar::modules */

Some files were not shown because too many files have changed in this diff Show More