Compare commits

..

247 Commits
0.9.5 ... 0.9.8

Author SHA1 Message Date
e5787a2617 chore: 0.9.8 2021-08-16 15:47:34 +02:00
9aec6bbed4 Merge pull request #1190 from mswiger/fix_incorrect_tray_icon_scale
Fix incorrect tray icon scaling
2021-08-01 13:39:29 +02:00
4f6a9b1bc2 Fix incorrect tray icon scaling 2021-07-31 18:01:31 -07:00
28e7a96e37 Merge pull request #1188 from WhyNotHugo/patch-1
Don't start if graphical-session is not running
2021-07-31 20:36:52 +02:00
710f933fa6 Don't start if graphical-session is not running
Currently waybar _can_ try to start even if there's no graphical session (and
no sway) running. Adding `Requisite=` prevents this. From `systemd.unit(5)`:

    Requisite=
       Similar to Requires=. However, if the units listed here are not
       started already, they will not be started and the starting of
       this unit will fail immediately.  Requisite= does not imply an
       ordering dependency, even if both units are started in the same
       transaction. Hence this setting should usually be combined with
       After=, to ensure this unit is not started before the other
       unit.

       When Requisite=b.service is used on a.service, this dependency
       will show as RequisiteOf=a.service in property listing of
       b.service.  RequisiteOf= dependency cannot be specified directly.
2021-07-31 16:56:55 +02:00
bad72de960 Merge pull request #1105 from Amanieu/fix_power_calc
Fix power calculation when battery units are in μA instead of μW
2021-07-25 15:07:27 +02:00
65166109c9 Merge branch 'master' into fix_power_calc 2021-07-25 15:07:01 +02:00
91156dfc75 Merge pull request #1178 from Anakael/pr/anakael/add-languge-tooltip-format
[sway/language] Add tooltip-format
2021-07-25 15:05:58 +02:00
af2113931a fix typo 2021-07-24 17:26:49 +03:00
68e4457f3a Add tooltip-formay 2021-07-24 17:24:37 +03:00
1b4ddbca3a Merge pull request #1024 from GrantMoyer/keyboard_state
Keyboard state module
2021-07-23 16:15:02 +02:00
445ad22580 Merge branch 'master' into keyboard_state 2021-07-23 15:59:08 +02:00
88a5f713ed Prefer keyboard-state over keyboard_state 2021-07-23 09:45:07 -04:00
2009ceb350 Update opensuse 2021-07-23 15:01:29 +02:00
77a2eff2ce Update opensuse 2021-07-23 14:49:03 +02:00
cf832798fb Update debian 2021-07-23 14:46:03 +02:00
3f3f2d9c2c Merge pull request #1159 from Anakael/pr/anakael/sway-language-impr
[sway/language] Improve sway/language
2021-07-23 14:42:22 +02:00
b47705ac21 Merge branch 'master' into pr/anakael/sway-language-impr 2021-07-23 09:07:43 +02:00
b33be38877 Merge pull request #1145 from alebastr/sni-enhancements
tray module enhancements
2021-07-23 09:05:54 +02:00
a5fe6f40b8 feat(tray): handle Status property
On the `Passive` value of `Status` tray items would be hidden unless
`show-passive-items` is set to true.
On the `NeedsAttention` value of `Status` tray items will have a
`.needs-attention` CSS class.
2021-07-22 08:04:06 -07:00
245f7f4b11 feat(tray): handle scroll events 2021-07-22 08:04:05 -07:00
1418f96e46 feat(tray): fallback to Title for items without ToolTip 2021-07-22 08:04:04 -07:00
84a8f79bbe feat(tray): implement tooltips (text only) for tray items 2021-07-22 08:04:03 -07:00
4b6253e810 refactor(tray): infer changed properties from signal name
Comparing two GVariants is too expensive; let's collect the set of
properties updated by each signal and apply them unconditionally.
2021-07-22 08:04:00 -07:00
929fc16994 fix(tray): ignore unused WindowId property 2021-07-22 08:01:25 -07:00
811f0896c9 Merge pull request #1174 from mswiger/fix_blurry_tray_icons
Fix blurry tray icons for HiDPI displays
2021-07-21 10:36:16 +02:00
7729ca3427 Update debian 2021-07-21 10:28:56 +02:00
100d4d3499 Merge branch 'master' into pr/anakael/sway-language-impr 2021-07-21 09:40:19 +02:00
7f5fd1ac86 Update opensuse 2021-07-21 09:30:47 +02:00
1f5c07a07f Update debian 2021-07-21 09:27:54 +02:00
67d482d28b Update opensuse 2021-07-21 09:23:52 +02:00
1440ed29d4 Fix blurry tray icons for HiDPI displays 2021-07-20 22:29:34 -07:00
311c5779ea Remove unused variable 2021-07-20 23:03:41 -04:00
9880c6929f Install libevdev in FreeBSD workflow 2021-07-20 23:03:41 -04:00
99138ffdcd Add man page for keyboard_state module 2021-07-20 21:09:00 -04:00
08e886ebc6 Search for device automatically if none given 2021-07-20 21:09:00 -04:00
6fdbc27998 Add default style 2021-07-20 21:09:00 -04:00
40e6360722 Update css class when locked/unlocked 2021-07-20 21:09:00 -04:00
642e28166b Add more configuaration 2021-07-20 21:09:00 -04:00
6dfa31fb17 Basic keyboard state module 2021-07-20 21:09:00 -04:00
c91cc2218b Merge pull request #1170 from larsch/default_name_fix
Fix pulseaudio icon name compilation error
2021-07-20 15:26:18 +02:00
6f2bfd43bf Fix pulseaudio icon name compilation error 2021-07-20 15:25:05 +02:00
f43f8773c4 Merge pull request #1169 from roosemberth/pa-control-active-sink
pulseaudio: Control currently running sink
2021-07-20 14:01:23 +02:00
bb072675ba Merge pull request #1161 from xytovl/per-device-pulse-icon
Support per-device icon in pulseaudio
2021-07-20 14:00:14 +02:00
fa43072be7 Merge pull request #1163 from Anakael/pr/anakael/update-dockerfiles
Update dockerfiles
2021-07-20 13:59:09 +02:00
86a43b9042 pulseaudio: Control currently running sink
In a system with multiple sinks, the default sink may not always be
the once currently being used. It is more useful to control the
currently active sink rather than an unused one.

This patch does not make any difference if the system only uses the
default sink.

Signed-off-by: Roosembert Palacios <roosemberth@posteo.ch>
2021-07-20 10:16:53 +02:00
2506c0104a Update Dockerfiles/fedora
Co-authored-by: Aleksei Bavshin <alebastr89@gmail.com>
2021-07-16 11:37:58 +03:00
948eba92a5 Update dockerfiles 2021-07-16 03:03:11 +03:00
ad09072a6d Merge branch 'master' into pr/anakael/sway-language-impr 2021-07-15 22:17:38 +02:00
9c2b5efe7b Support per-device icon in pulseaudio 2021-07-15 09:20:43 +02:00
91cdf80c65 Merge pull request #1157 from tiosgz/multi-bar-fix
Do not fail to parse a multi-bar config
2021-07-14 09:04:24 +02:00
9cce5ea6b5 Update dockerfiles 2021-07-13 04:49:19 +03:00
8310700bbb Improve sway/language 2021-07-13 04:33:12 +03:00
78aaa5c1b4 Do not fail to parse a multi-bar config 2021-07-10 20:22:37 +00:00
7c1303f57c Merge pull request #1147 from alebastr/github-ci-linux
Add GitHub CI jobs on Linux
2021-07-03 01:08:39 +02:00
569517c531 chore: update freebsd-vm to 0.1.4 2021-07-03 00:55:59 +02:00
1c2e0083ba Merge pull request #1144 from ajakk/master
libfmt >=8.0.0 compatibility
2021-07-03 00:30:33 +02:00
a8edc0886d Delete .travis.yml 2021-07-03 00:28:43 +02:00
8e1f85e1c3 Update archlinux 2021-07-03 00:27:57 +02:00
5420a91046 chore: update FreeBSD action to address ntp sync issue 2021-07-01 00:13:30 -07:00
2a52efa99a chore: update fedora dockerfile 2021-06-30 23:51:28 -07:00
d3c59c42ef feat(ci): add GitHub CI jobs on Linux 2021-06-30 23:51:09 -07:00
368e4813de libfmt >=8.0.0 compatibility 2021-06-30 13:12:38 -05:00
36857ae72b Merge pull request #995 from OskarCarl/master
Add recursive config includes
2021-06-23 23:35:56 +02:00
982d571b2e Add include man section 2021-06-23 23:08:47 +02:00
e62b634f72 Workaround for circular imports 2021-06-21 19:29:09 +02:00
e8278431d2 Proper formatting 2021-06-21 19:05:01 +02:00
14f626d422 Add recursive config includes 2021-06-21 19:05:01 +02:00
d08fbb2ef2 Merge pull request #1132 from alebastr/fix-noexcept-condvar-crash
fix(util): protect std::condition_variable methods from pthread_cancel
2021-06-15 13:20:05 +02:00
5da268077c fix(util): protect std::condition_variable methods from pthread_cancel
The changes in GCC 11.x made `std::condition_variable` implementation
internals `noexcept`. `noexcept` is known to interact particularly bad
with `pthread_cancel`, i.e. `__cxxabiv1::__force_unwind` passing through
the `noexcept` call stack frame causes a `std::terminate` call and
immediate termination of the program

Digging through the GCC ML archives[1] lead me to the idea of patching
this with a few pthread_setcancelstate's. As bad as the solution is, it
seems to be the best we can do within C++17 limits and without major
rework.

[1]: https://gcc.gnu.org/legacy-ml/gcc/2017-08/msg00156.html
2021-06-12 12:56:44 -07:00
20160749e7 Merge pull request #1130 from tperard/fix-network-format-config
network: Fix mix use of default and state specific format
2021-06-08 22:53:10 +02:00
194f4c2f18 network: Fix mix use of default and state specific format
Whenever the network module is configured with both "format" and
"format-$state" and when the module use "format-$state" once, it
override the value that was saved from "format".

For example, if both "format" and "format-disconnect" are configured,
and only those, as soon as the module show information about a
disconnected interface, it will keep showing the format for
disconnected, even if the interface is connected again later.

Fix that by always setting a value to default_format_ in update() and
thus use the intended default format when needed.

Fixes #1129
2021-06-08 18:50:32 +01:00
9e34be7b16 Merge pull request #1126 from tperard/fix-network-auto-detection
Fix network interface auto detection
2021-06-05 18:06:30 +02:00
6e041d5275 Merge pull request #1125 from maximbaz/sway-language-ignore-empty 2021-06-05 18:01:26 +02:00
33617b67f0 network: Fix one case where default route is deleted without notification
When an interface's state is change to "down", all the route
associated with it are deleted without an RTM_DELROUTE event.

So when this happen, reset the module to start looking for a new
external interface / default route.

Fixes #1117
2021-06-05 16:52:04 +01:00
efaac20d82 network: Handle ip route priority
When there's a new default route with higher priority, switch to it.
2021-06-05 16:51:54 +01:00
ce97df34e6 network: Also clear ifname in clearIface()
Since we reset `ifid_`, clear `ifname_` as well.
2021-06-05 16:51:40 +01:00
23b9923eeb network: Parse whole RTM_NEWROUTE msg before interpreting it
The check to figure out if we have the default route should be after
the for loop that parses the route attributes, to avoid acting on
incomplete information. We are going to use more fields from the
message.
2021-06-05 16:51:35 +01:00
999c1b6b81 sway-language: ignore events with empty layout 2021-06-05 15:03:52 +02:00
1a98ecf6b0 Merge branch 'master' into fix_power_calc 2021-05-30 11:17:54 +01:00
5444a66e71 Merge pull request #1116 from tperard/fix-network-rework
Fix network module rework
2021-05-27 21:24:07 +02:00
f49a7a1acb network: Update WiFi information when available
The module doesn't update the `essid_` as soon as a WiFi interface is
connected, but that happens at some point later, depending on
"interval" configuration.

Fix that by rerunning the get WiFi information thread when the
`carrier` state changes. Also, we will clear the state related to WiFi
when the connection is drop to avoid stale information.
2021-05-27 19:36:14 +01:00
28dfb0ba41 network: Fix use of carrier information
Some RTM_NEWLINK messages may not have the IFLA_CARRIER information.
This is the case when a WiFi interface report scan result are
available. `carrier` is used regardless of if it is present in the
message or not. This would result in the interface appearing
"disconnected" in waybar when it isn't.

This patch now check that `carrier` is available before using it.

The same thing could potentially happen to `ifname` so check if it's
set before recording it.

Fixes: c1427ff (network: Handle carrier information)
Fixes #388
2021-05-26 19:23:20 +01:00
94a882bf95 Merge pull request #1113 from alebastr/exclusive-and-passthrough
Add config options for exclusive zone and input event passthrough
2021-05-22 10:34:39 +02:00
da2d603b53 doc: add man for exclusive and passthrough flags 2021-05-21 22:44:19 -07:00
7aaa3df701 feat(bar): add config flag to disable exclusive zone 2021-05-21 22:44:18 -07:00
729553d3bc feat(bar): add config flag for pointer event passthrough 2021-05-21 22:44:17 -07:00
f78a802d11 Merge pull request #1106 from tperard/network-module-reword
Network module rework
2021-05-21 17:02:28 +02:00
826a549d1f Merge pull request #1112 from yonatan8070/master
Add options to use a .json extension for the config filename
2021-05-21 17:00:02 +02:00
99918205ed Correct .json to .jsonc 2021-05-21 17:53:43 +03:00
c65ec9e14f Add options to use a .json extension for the config filename 2021-05-21 15:54:48 +03:00
c1427ff807 network: Handle carrier information
IFLA_CARRIER allows to know when a cable is plugged to the Ethernet
card or when the WiFi is connected. If there's no carrier, the
interface will be considered disconnected.
2021-05-15 16:38:00 +01:00
0bb436f949 network: Rework interface auto detection, handle route change events
Last part of the rework of handleEvents(), this time we take the
getExternalInterface() function and add it to the handleEvents()
function. That way, waybar can react immediately when a new "external
interface" is available and doesn't need to probe. Also that avoid to
have two different functions consuming from the same socket and we
don't need to recode some of the functions that are already available
via libnl (to send and receive messages).
2021-05-15 16:38:00 +01:00
0fc7ef6685 network: Rework address lookup to use only events
In order to get the IP address of an interface, we can get the
information out of NEWADDR events without needed to call getifaddrs().
And when now events are expected, we can requests a dump of all
addresses and handle addresses changes the same way via handleEvents()
only.
2021-05-15 16:38:00 +01:00
c9bbaa7241 network: Rework initial interface search by using a dump
Instead of using an alternative way to list all links in order to
choose one when an "interface" is in the configuration, we can ask for
a dump of all interface an reuse the handleEvents() function.

This patch also start to rework the handleEvents() function to grab
more information out of each event, like the interface name.
2021-05-15 16:38:00 +01:00
63fdf66ad6 network: Read all available messages on ev_sock_
When more than one message is available to read on the ev_sock_
socket, only the first one is read.

Make some changes to be able to read all the messages available by
setting the socket to non-blocking. This way we can detect when
there's nothing left to read and loop back to wait with epoll.
2021-05-15 16:38:00 +01:00
9357a6cb88 network: Start the module with some text in the label_
Fix modules starting with no text, but not hidding.

Start with some "text" in the module's label_, update() will then
update it. Since the text should be different, update() will be able
to show or hide the event_box_. This is to work around the case where
the module start with no text, but the the event_box_ is shown.
2021-05-15 16:38:00 +01:00
dbc06abf18 network: Initialise cidr_ like clearIface() does 2021-05-15 16:38:00 +01:00
4d067619a8 Fix power calculation when battery units are in μA instead of μW 2021-05-15 15:55:38 +01:00
cf3d6545c3 Merge pull request #1101 from Max1Truc/master
fix: incorrect battery percentage on Pinebook Pro
2021-05-11 10:41:38 +02:00
f3a6e2d494 fix: incorrect battery percentage on Pinebook Pro 2021-05-10 21:00:14 +02:00
cdce3e03ea Update meson.build 2021-04-30 14:25:48 +02:00
b25b7d29fc Update spdlog.wrap 2021-04-30 14:25:26 +02:00
71d7596b6f fix: bluetooth status tooltip 2021-04-30 14:23:49 +02:00
06e699c862 Merge pull request #1087 from Synthetica9/multiple-rewrites
rewriteTitle: allow multiple sequential rewrites
2021-04-27 00:00:53 +02:00
a03283d65f rewriteTitle: allow multiple sequential rewrites 2021-04-26 20:26:43 +02:00
ef38061edd Merge pull request #1084 from gabegorelick/battery-discharging-full
[modules/battery] allow format-discharging-full
2021-04-26 09:28:00 +02:00
7e13e26c29 [modules/battery] allow format-discharging-full
`format-discharging-full` has been impossible since #923 made it
impossible to be full and discharging at the same time. This should
fix that by only making `format-charging-full` impossible. Whether
or not that should be allowed is a good question, but beyond the
scope of this change.

Fixes #1031
2021-04-25 22:00:24 -04:00
5f7329f5b9 Merge pull request #1081 from David96/master
[modules/pulseaudio] fix bluetooth class for PipeWire
2021-04-25 14:03:00 +02:00
2213380dc0 [modules/pulseaudio] fix bluetooth class for PipeWire
apparently, pipewire-pulse slightly changed the naming of the sink.
2021-04-25 11:19:35 +02:00
66d8035ed1 Merge pull request #1055 from vrld/feature-rewrite-window-title
Add option to rewrite sway/window title
2021-04-21 14:23:45 +02:00
7cdf178f8d Document changes in manpage
Add section on rewrite rules and extend example
2021-04-21 12:24:47 +02:00
af3c868a5b Catch exception on erroneous rules
std::regex and std::regex_replace may throw an std::regex_error if the
expression or replacement contain errors.

Log this error and carry on with the next rule, so that the title is
shown even if the config contains errors.
2021-04-21 12:24:47 +02:00
b16c8972c7 Add option to rewrite sway/window title
Rewrites window title according to config option "rewrite".
"rewrite" is an object where keys are regular expressions and values are
rewrite rules if the expression matches. Rules may contain references to
captures of the expression. Regex and replacement follow ECMA-script
rules. If no regex matches, the title is left unchanged.

example:
"sway/window": {
  "rewrite": {
    "(.*) - Mozilla Firefox": " $1",
    "(.*) - zsh": " $1",
  }
}
2021-04-21 12:24:47 +02:00
1c9b62de07 Merge pull request #1076 from Scrumplex/add-mpd-volume-status
Implement MPD volume status format template
2021-04-20 09:38:19 +02:00
fc89b01ba6 feat: implement mpd volume format template
Allow the user to show the current volume from MPD status via the
`format` and/or `tooltip-format` configuration options.

The values are provided by libmpdclient and are integers, generally
between 0-100 (without %). Values above 100 are also possible, as mpd
output plugins like `pulse` support volumes above 100%.

Signed-off-by: Sefa Eyeoglu <contact@scrumplex.net>
2021-04-20 08:35:47 +02:00
70e67c5daa Merge pull request #1074 from Anakael/feature/taskbar-ignore-list
[wlr/taskbar] Add ignore-list param
2021-04-18 20:02:14 +02:00
5ad3b6018a Remove exceed protected 2021-04-18 21:37:58 +03:00
ba278985e8 Add ignore-list param to wlr/taskbar 2021-04-18 21:34:29 +03:00
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
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
0d03c1d4da Fix waybar-pulseaudio with pipewire-pulse 2020-12-04 23:51:10 +01:00
3b576ae12d Add "tooltip-format" to temperature module 2020-11-26 20:23:19 -05: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
4229e9b2ca Implemented format-{state} for cpu/disk/memory 2020-10-12 02:05:26 +02:00
45f7f9b07a Merge branch 'master' into config-reloading 2020-10-11 23:00:25 +02:00
943b6bc51b Implement support for reloading of config files.
Fixes #759.
2020-08-28 22:34:24 +10:00
9b51094743 Merge branch 'master' into battery-custom-tooltip 2020-08-20 13:52:25 +02:00
8fb54f47ea battery: allow custom tooltip format 2020-08-19 23:13:03 +02:00
90 changed files with 2445 additions and 799 deletions

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

@ -0,0 +1,23 @@
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.1.4 # 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 evdev-proto gtk-layer-shell gtkmm30 jsoncpp libdbusmenu \
libevdev libfmt libmpdclient libudev-devd meson pkgconf pulseaudio \
scdoc sndio spdlog
run: |
meson build -Dman-pages=enabled
ninja -C build

25
.github/workflows/linux.yml vendored Normal file
View File

@ -0,0 +1,25 @@
name: linux
on: [push, pull_request]
jobs:
build:
strategy:
matrix:
distro:
- alpine
- archlinux
- debian
- fedora
- opensuse
runs-on: ubuntu-latest
container:
image: alexays/waybar:${{ matrix.distro }}
steps:
- uses: actions/checkout@v2
- name: configure
run: meson -Dman-pages=enabled build
- name: build
run: ninja -C build

View File

@ -1,38 +0,0 @@
language: cpp
services:
- docker
git:
submodules: false
env:
- distro: debian
- distro: archlinux
- distro: fedora
- distro: alpine
- distro: opensuse
before_install:
- docker pull alexays/waybar:${distro}
- find . -type f \( -name '*.cpp' -o -name '*.h' \) -print0 | xargs -r0 clang-format -i
script:
- echo FROM alexays/waybar:${distro} > Dockerfile
- 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-dev libnl3-dev pulseaudio-dev libmpdclient-dev sndio-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 libxkbcommon

View File

@ -1,6 +1,6 @@
# vim: ft=Dockerfile
FROM archlinux/base:latest
FROM archlinux:base-devel
RUN pacman -Syu --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
pacman -S git meson base-devel libinput wayland wayland-protocols pixman libxkbcommon mesa gtkmm3 jsoncpp pugixml scdoc libpulse libdbusmenu-gtk3 libmpdclient gobject-introspection --noconfirm libxkbcommon

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 libdbusmenu-gtk3-dev libnl-3-dev libnl-genl-3-dev libpulse-dev libmpdclient-dev gobject-introspection libgirepository1.0-dev && \
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 libxkbcommon-dev libxkbregistry-dev libxkbregistry0 && \
apt-get clean

View File

@ -1,7 +1,12 @@
# vim: ft=Dockerfile
FROM fedora:32
FROM fedora:latest
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 && \
RUN dnf install -y @c-development git-core meson scdoc 'pkgconfig(date)' \
'pkgconfig(dbusmenu-gtk3-0.4)' 'pkgconfig(fmt)' 'pkgconfig(gdk-pixbuf-2.0)' \
'pkgconfig(gio-unix-2.0)' 'pkgconfig(gtk-layer-shell-0)' 'pkgconfig(gtkmm-3.0)' \
'pkgconfig(jsoncpp)' 'pkgconfig(libinput)' 'pkgconfig(libmpdclient)' \
'pkgconfig(libnl-3.0)' 'pkgconfig(libnl-genl-3.0)' 'pkgconfig(libpulse)' \
'pkgconfig(libudev)' 'pkgconfig(pugixml)' 'pkgconfig(sigc++-2.0)' 'pkgconfig(spdlog)' \
'pkgconfig(wayland-client)' 'pkgconfig(wayland-cursor)' 'pkgconfig(wayland-protocols)' 'pkgconfig(xkbregistry)' && \
dnf clean all -y

View File

@ -3,5 +3,7 @@
FROM opensuse/tumbleweed:latest
RUN zypper -n up && \
zypper addrepo https://download.opensuse.org/repositories/X11:Wayland/openSUSE_Tumbleweed/X11:Wayland.repo | echo 'a' && \
zypper -n refresh && \
zypper -n install -t pattern devel_C_C++ && \
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
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 libxkbregistry-devel scdoc

View File

@ -68,6 +68,7 @@ libappindicator-gtk3 [Tray module]
libdbusmenu-gtk3 [Tray module]
libmpdclient [MPD module]
libsndio [sndio module]
libevdev [KeyboardState module]
```
**Build dependencies**
@ -86,6 +87,7 @@ sudo apt install \
clang-tidy \
gobject-introspection \
libdbusmenu-gtk3-dev \
libevdev-dev \
libfmt-dev \
libgirepository1.0-dev \
libgtk-3-dev \

View File

@ -14,7 +14,7 @@ class ALabel : public AModule {
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);
virtual std::string getIcon(uint16_t, const std::vector<std::string> &alts, uint16_t max = 0);
protected:
Gtk::Label label_;

View File

@ -17,6 +17,7 @@ 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};
@ -43,6 +44,7 @@ class BarSurface {
virtual void setExclusiveZone(bool enable) = 0;
virtual void setLayer(bar_layer layer) = 0;
virtual void setMargins(const struct bar_margins &margins) = 0;
virtual void setPassThrough(bool enable) = 0;
virtual void setPosition(const std::string_view &position) = 0;
virtual void setSize(uint32_t width, uint32_t height) = 0;
virtual void commit(){};
@ -63,6 +65,7 @@ class Bar {
struct waybar_output *output;
Json::Value config;
struct wl_surface * surface;
bool exclusive = true;
bool visible = true;
bool vertical = false;
Gtk::Window window;

View File

@ -6,6 +6,7 @@
#include <unistd.h>
#include <wayland-client.h>
#include <wordexp.h>
#include "bar.hpp"
struct zwlr_layer_shell_v1;
@ -18,6 +19,7 @@ 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;
@ -33,20 +35,25 @@ 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, int depth) -> void;
auto resolveConfigIncludes(Json::Value &config, int depth) -> void;
auto mergeConfig(Json::Value &a_config_, Json::Value &b_config_) -> 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,7 +1,11 @@
#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"
@ -34,6 +38,9 @@
#ifdef HAVE_LIBUDEV
#include "modules/backlight.hpp"
#endif
#ifdef HAVE_LIBEVDEV
#include "modules/keyboard_state.hpp"
#endif
#ifdef HAVE_LIBPULSE
#include "modules/pulseaudio.hpp"
#endif

View File

@ -31,11 +31,11 @@ class Battery : public ALabel {
private:
static inline const fs::path data_dir_ = "/sys/class/power_supply/";
void refreshBatteries();
void worker();
const std::string getAdapterStatus(uint8_t capacity) const;
const std::tuple<uint8_t, float, std::string> getInfos();
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);
int global_watch;
std::map<fs::path,int> batteries_;

View File

@ -1,10 +1,6 @@
#pragma once
#include <fmt/format.h>
#include "ALabel.hpp"
#include <fmt/chrono.h>
#include "util/sleeper_thread.hpp"
#include "util/rfkill.hpp"
namespace waybar::modules {
@ -16,10 +12,6 @@ class Bluetooth : public ALabel {
auto update() -> void;
private:
std::string status_;
util::SleeperThread thread_;
util::SleeperThread intervall_thread_;
util::Rfkill rfkill_;
};

View File

@ -1,7 +1,6 @@
#pragma once
#include <fmt/format.h>
#include <unistd.h>
#include <cstdint>
#include <fstream>
#include <numeric>
@ -20,9 +19,11 @@ class Cpu : public ALabel {
auto update() -> void;
private:
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

@ -0,0 +1,47 @@
#pragma once
#include <fmt/format.h>
#if FMT_VERSION < 60000
#include <fmt/time.h>
#else
#include <fmt/chrono.h>
#endif
#include "AModule.hpp"
#include "bar.hpp"
#include "util/sleeper_thread.hpp"
#include <gtkmm/label.h>
extern "C" {
#include <libevdev/libevdev.h>
}
namespace waybar::modules {
class KeyboardState : public AModule {
public:
KeyboardState(const std::string&, const waybar::Bar&, const Json::Value&);
~KeyboardState();
auto update() -> void;
private:
static auto openDevice(const std::string&) -> std::pair<int, libevdev*>;
Gtk::Box box_;
Gtk::Label numlock_label_;
Gtk::Label capslock_label_;
Gtk::Label scrolllock_label_;
std::string numlock_format_;
std::string capslock_format_;
std::string scrolllock_format_;
const std::chrono::seconds interval_;
std::string icon_locked_;
std::string icon_unlocked_;
int fd_;
libevdev* dev_;
util::SleeperThread thread_;
};
} // namespace waybar::modules

View File

@ -2,9 +2,7 @@
#include <arpa/inet.h>
#include <fmt/format.h>
#include <ifaddrs.h>
#include <linux/nl80211.h>
#include <net/if.h>
#include <netlink/genl/ctrl.h>
#include <netlink/genl/genl.h>
#include <netlink/netlink.h>
@ -28,23 +26,20 @@ class Network : public ALabel {
static const uint8_t EPOLL_MAX = 200;
static int handleEvents(struct nl_msg*, void*);
static int handleEventsDone(struct nl_msg*, void*);
static int handleScan(struct nl_msg*, void*);
void askForStateDump(void);
void worker();
void createInfoSocket();
void createEventSocket();
int getExternalInterface(int skip_idx = -1) const;
void getInterfaceAddress();
int netlinkRequest(void*, uint32_t, uint32_t groups = 0) const;
int netlinkResponse(void*, uint32_t, uint32_t groups = 0) const;
void parseEssid(struct nlattr**);
void parseSignal(struct nlattr**);
void parseFreq(struct nlattr**);
bool associatedOrJoined(struct nlattr**);
bool checkInterface(struct ifinfomsg* rtif, std::string name);
int getPreferredIface(int skip_idx = -1, bool wait = true) const;
bool checkInterface(std::string name);
auto getInfo() -> void;
void checkNewInterface(struct ifinfomsg* rtif);
const std::string getNetworkState() const;
void clearIface();
bool wildcardMatch(const std::string& pattern, const std::string& text) const;
@ -59,11 +54,17 @@ class Network : public ALabel {
int nl80211_id_;
std::mutex mutex_;
bool want_route_dump_;
bool want_link_dump_;
bool want_addr_dump_;
bool dump_in_progress_;
unsigned long long bandwidth_down_total_;
unsigned long long bandwidth_up_total_;
std::string state_;
std::string essid_;
bool carrier_;
std::string ifname_;
std::string ipaddr_;
std::string netmask_;
@ -71,12 +72,11 @@ class Network : public ALabel {
int32_t signal_strength_dbm_;
uint8_t signal_strength_;
uint32_t frequency_;
uint32_t route_priority;
util::SleeperThread thread_;
util::SleeperThread thread_timer_;
#ifdef WANT_RFKILL
util::SleeperThread thread_rfkill_;
util::Rfkill rfkill_;
#endif
};

View File

@ -24,7 +24,7 @@ class Pulseaudio : public ALabel {
static void volumeModifyCb(pa_context*, int, void*);
bool handleScroll(GdkEventScroll* e);
const std::string getPortIcon() const;
const std::vector<std::string> getPulseIcon() const;
pa_threaded_mainloop* mainloop_;
pa_mainloop_api* mainloop_api_;
@ -38,7 +38,8 @@ class Pulseaudio : public ALabel {
std::string form_factor_;
std::string desc_;
std::string monitor_;
std::string default_sink_name_;
std::string current_sink_name_;
bool current_sink_running_;
// SOURCE
uint32_t source_idx_{0};
uint16_t source_volume_;

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

View File

@ -11,8 +11,16 @@
#include <libdbusmenu-gtk/dbusmenu-gtk.h>
#include <sigc++/trackable.h>
#include <set>
#include <string_view>
namespace waybar::modules::SNI {
struct ToolTip {
Glib::ustring icon_name;
Glib::ustring text;
};
class Item : public sigc::trackable {
public:
Item(const std::string&, const std::string&, const Json::Value&);
@ -27,10 +35,8 @@ class Item : public sigc::trackable {
Gtk::EventBox event_box;
std::string category;
std::string id;
std::string status;
std::string title;
int32_t window_id;
std::string icon_name;
Glib::RefPtr<Gdk::Pixbuf> icon_pixmap;
Glib::RefPtr<Gtk::IconTheme> icon_theme;
@ -39,6 +45,7 @@ class Item : public sigc::trackable {
std::string attention_movie_name;
std::string icon_theme_path;
std::string menu;
ToolTip tooltip;
DbusmenuGtkMenu* dbus_menu = nullptr;
Gtk::Menu* gtk_menu = nullptr;
/**
@ -51,6 +58,7 @@ class Item : public sigc::trackable {
private:
void proxyReady(Glib::RefPtr<Gio::AsyncResult>& result);
void setProperty(const Glib::ustring& name, Glib::VariantBase& value);
void setStatus(const Glib::ustring& value);
void getUpdatedProperties();
void processUpdatedProperties(Glib::RefPtr<Gio::AsyncResult>& result);
void onSignal(const Glib::ustring& sender_name, const Glib::ustring& signal_name,
@ -58,14 +66,24 @@ class Item : public sigc::trackable {
void updateImage();
Glib::RefPtr<Gdk::Pixbuf> extractPixBuf(GVariant* variant);
Glib::RefPtr<Gdk::Pixbuf> getIconPixbuf();
Glib::RefPtr<Gdk::Pixbuf> getIconByName(const std::string& name, int size);
double getScaledIconSize();
static void onMenuDestroyed(Item* self, GObject* old_menu_pointer);
void makeMenu();
bool handleClick(GdkEventButton* const& /*ev*/);
bool handleScroll(GdkEventScroll* const&);
// smooth scrolling threshold
gdouble scroll_threshold_ = 0;
gdouble distance_scrolled_x_ = 0;
gdouble distance_scrolled_y_ = 0;
// visibility of items with Status == Passive
bool show_passive_ = false;
Glib::RefPtr<Gio::DBus::Proxy> proxy_;
Glib::RefPtr<Gio::Cancellable> cancellable_;
bool update_pending_;
std::set<std::string_view> update_pending_;
};
} // namespace waybar::modules::SNI

View File

@ -29,4 +29,8 @@ enum ipc_command_type {
IPC_EVENT_BINDING = ((1 << 31) | 5),
IPC_EVENT_SHUTDOWN = ((1 << 31) | 6),
IPC_EVENT_TICK = ((1 << 31) | 7),
// sway-specific event types
IPC_EVENT_BAR_STATE_UPDATE = ((1<<31) | 20),
IPC_EVENT_INPUT = ((1<<31) | 21),
};

View File

@ -1,6 +1,11 @@
#pragma once
#include <fmt/format.h>
#include <xkbcommon/xkbregistry.h>
#include <map>
#include <string>
#include "ALabel.hpp"
#include "bar.hpp"
#include "client.hpp"
@ -16,13 +21,41 @@ class Language : public ALabel, public sigc::trackable {
auto update() -> void;
private:
struct Layout {
std::string full_name;
std::string short_name;
std::string variant;
};
class XKBContext {
public:
XKBContext();
~XKBContext();
auto next_layout() -> Layout*;
private:
rxkb_context* context_ = nullptr;
rxkb_layout* xkb_layout_ = nullptr;
Layout* layout_ = nullptr;
};
void onEvent(const struct Ipc::ipc_response&);
void onCmd(const struct Ipc::ipc_response&);
auto set_current_layout(std::string current_layout) -> void;
auto init_layouts_map(const std::vector<std::string>& used_layouts) -> void;
const static std::string XKB_LAYOUT_NAMES_KEY;
const static std::string XKB_ACTIVE_LAYOUT_NAME_KEY;
std::string lang_;
util::JsonParser parser_;
std::mutex mutex_;
Ipc ipc_;
Layout layout_;
std::string tooltip_format_ = "";
std::map<std::string, Layout> layouts_map_;
XKBContext xkb_context_;
bool is_variant_displayed;
util::JsonParser parser_;
std::mutex mutex_;
Ipc ipc_;
};
} // namespace waybar::modules::sway

View File

@ -22,6 +22,7 @@ class Window : public ALabel, public sigc::trackable {
std::tuple<std::size_t, int, std::string, std::string> getFocusedNode(const Json::Value& nodes,
std::string& output);
void getTree();
std::string rewriteTitle(const std::string& title);
const Bar& bar_;
std::string window_;

View File

@ -20,7 +20,7 @@ class Workspaces : public AModule, public sigc::trackable {
auto update() -> void;
private:
static inline const std::string workspace_switch_cmd_ = "workspace --no-auto-back-and-forth \"{}\"";
static inline const std::string workspace_switch_cmd_ = "workspace {} \"{}\"";
static int convertWorkspaceNameToNum(std::string name);

View File

@ -8,6 +8,7 @@
#include <memory>
#include <string>
#include <vector>
#include <unordered_set>
#include <gdk/gdk.h>
@ -61,6 +62,7 @@ class Task
Gtk::Label text_before_;
Gtk::Label text_after_;
bool button_visible_;
bool ignored_;
bool with_icon_;
std::string format_before_;
@ -132,6 +134,7 @@ class Taskbar : public waybar::AModule
std::vector<TaskPtr> tasks_;
std::vector<Glib::RefPtr<Gtk::IconTheme>> icon_themes_;
std::unordered_set<std::string> ignore_list_;
struct zwlr_foreign_toplevel_manager_v1 *manager_;
struct wl_seat *seat_;
@ -155,6 +158,7 @@ class Taskbar : public waybar::AModule
bool all_outputs() const;
std::vector<Glib::RefPtr<Gtk::IconTheme>> icon_themes() const;
const std::unordered_set<std::string>& ignore_list() const;
};
} /* namespace waybar::modules::wlr */

View File

@ -5,6 +5,13 @@
#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;
@ -77,6 +84,18 @@ inline FILE* open(const std::string& cmd, int& pid) {
// 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);

View File

@ -35,14 +35,18 @@ namespace fmt {
// The rationale for ignoring it is that the only reason to specify
// an alignment and a with is to get a fixed width bar, and ">" is
// sufficient in this implementation.
#if FMT_VERSION < 80000
width = parse_nonnegative_int(it, end, ctx);
#else
width = detail::parse_nonnegative_int(it, end, -1);
#endif
}
return it;
}
template<class FormatContext>
auto format(const pow_format& s, FormatContext &ctx) -> decltype (ctx.out()) {
const char* units[] = { "", "K", "M", "G", "T", "P", nullptr};
const char* units[] = { "", "k", "M", "G", "T", "P", nullptr};
auto base = s.binary_ ? 1024ull : 1000ll;
auto fraction = (double) s.val_;

View File

@ -1,19 +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 {
class Rfkill : public sigc::trackable {
public:
Rfkill(enum rfkill_type rfkill_type);
~Rfkill() = default;
void waitForEvent();
~Rfkill();
bool getState() const;
sigc::signal<void(struct rfkill_event&)> on_update;
private:
enum rfkill_type rfkill_type_;
int state_ = 0;
bool state_ = false;
int fd_ = -1;
bool on_event(Glib::IOCondition cond);
};
} // namespace waybar::util

View File

@ -8,6 +8,20 @@
namespace waybar::util {
/**
* Defer pthread_cancel until the end of a current scope.
*
* Required to protect a scope where it's unsafe to raise `__forced_unwind` exception.
* An example of these is a call of a method marked as `noexcept`; an attempt to cancel within such
* a method may result in a `std::terminate` call.
*/
class CancellationGuard {
int oldstate;
public:
CancellationGuard() { pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate); }
~CancellationGuard() { pthread_setcancelstate(oldstate, &oldstate); }
};
class SleeperThread {
public:
SleeperThread() = default;
@ -33,14 +47,16 @@ class SleeperThread {
bool isRunning() const { return do_run_; }
auto sleep_for(std::chrono::system_clock::duration dur) {
std::unique_lock lk(mutex_);
std::unique_lock lk(mutex_);
CancellationGuard cancel_lock;
return condvar_.wait_for(lk, dur, [this] { return signal_ || !do_run_; });
}
auto sleep_until(
std::chrono::time_point<std::chrono::system_clock, std::chrono::system_clock::duration>
time_point) {
std::unique_lock lk(mutex_);
std::unique_lock lk(mutex_);
CancellationGuard cancel_lock;
return condvar_.wait_until(lk, time_point, [this] { return signal_ || !do_run_; });
}

15
include/util/string.hpp Normal file
View File

@ -0,0 +1,15 @@
#include <string>
const std::string WHITESPACE = " \n\r\t\f\v";
std::string ltrim(const std::string s) {
size_t begin = s.find_first_not_of(WHITESPACE);
return (begin == std::string::npos) ? "" : s.substr(begin);
}
std::string rtrim(const std::string s) {
size_t end = s.find_last_not_of(WHITESPACE);
return (end == std::string::npos) ? "" : s.substr(0, end + 1);
}
std::string trim(const std::string& s) { return rtrim(ltrim(s)); }

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

@ -24,6 +24,14 @@ The *backlight* module displays the current backlight level.
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.
*rotate*: ++
typeof: integer ++
Positive value to rotate the text label.

View File

@ -22,6 +22,11 @@ The *battery* module displays the current capacity and state (eg. charging) of y
typeof: integer ++
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%.
*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 ++
@ -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.
@ -91,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

@ -12,11 +12,6 @@ The *bluetooth* module displays information about the status of the device's blu
Addressed by *bluetooth*
*interval*: ++
typeof: integer ++
default: 60 ++
The interval in which the bluetooth state gets updated.
*format*: ++
typeof: string ++
default: *{icon}* ++
@ -35,6 +30,14 @@ Addressed by *bluetooth*
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.
@ -80,12 +83,11 @@ Addressed by *bluetooth*
"bluetooth": {
"format": "{icon}",
"format-alt": "bluetooth: {status}",
"interval": 30,
"format-icons": {
"enabled": "",
"disabled": ""
},
"tooltip-format": "{status}"
"tooltip-format": "{}"
}
```

View File

@ -45,6 +45,14 @@ The *clock* module displays the current date and time.
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.

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.
@ -71,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

@ -67,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.

View File

@ -39,6 +39,14 @@ Addressed by *disk*
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.

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

View File

@ -0,0 +1,80 @@
waybar-keyboard-state(5)
# NAME
waybar - keyboard-state module
# DESCRIPTION
The *keyboard-state* module displays the state of number lock, caps lock, and scroll lock.
# CONFIGURATION
*interval*: ++
typeof: integer ++
default: 1 ++
The interval, in seconds, to poll the keyboard state.
*format*: ++
typeof: string|object ++
default: {name} {icon} ++
The format, how information should be displayed. If a string, the same format is used for all keyboard states. If an object, the fields "numlock", "capslock", and "scrolllock" each specify the format for the corresponding state. Any unspecified states use the default format.
*format-icons*: ++
typeof: object ++
default: {"locked": "locked", "unlocked": "unlocked"} ++
Based on the keyboard state, the corresponding icon gets selected. The same set of icons is used for number, caps, and scroll lock, but the icon is selected from the set independently for each. See *icons*.
*numlock*: ++
typeof: bool ++
default: false ++
Display the number lock state.
*capslock*: ++
typeof: bool ++
default: false ++
Display the caps lock state.
*scrolllock*: ++
typeof: bool ++
default: false ++
Display the scroll lock state.
*device-path*: ++
typeof: string ++
default: chooses first valid input device ++
Which libevdev input device to show the state of. Libevdev devices can be found in /dev/input. The device should support number lock, caps lock, and scroll lock events.
# FORMAT REPLACEMENTS
*{name}*: Caps, Num, or Scroll.
*{icon}*: Icon, as defined in *format-icons*.
# ICONS
The following *format-icons* can be set.
- *locked*: Will be shown when the keyboard state is locked. Default "locked".
- *unlocked*: Will be shown when the keyboard state is not locked. Default "unlocked"
# EXAMPLE:
```
"keyboard-state": {
"numlock": true,
"capslock": true,
"format": "{name} {icon}",
"format-icons": {
"locked": "",
"unlocked": ""
}
}
```
# STYLE
- *#keyboard-state*
- *#keyboard-state label*
- *#keyboard-state label.locked*

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.

View File

@ -73,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.
@ -81,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.
@ -148,6 +172,8 @@ Addressed by *mpd*
*{date}*: The date of the current song
*{volume}*: The current volume in percent
*{elapsedTime}*: The current position of the current song. To format as a date/time (see example configuration)
*{totalTime}*: The length of the current song. To format as a date/time (see example configuration)

View File

@ -64,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.

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 ++
@ -101,6 +109,9 @@ Additionally you can control the volume by scrolling *up* or *down* while the cu
# ICONS:
The following strings for *format-icons* are supported.
- the device name
If they are found in the current PulseAudio port name, the corresponding icons will be selected.
- *default* (Shown, when no other port is found)
@ -123,6 +134,7 @@ If they are found in the current PulseAudio port name, the corresponding icons w
"format-bluetooth": "{volume}% {icon}",
"format-muted": "",
"format-icons": {
"alsa_output.pci-0000_00_1f.3.analog-stereo": "",
"headphones": "",
"handsfree": "",
"headset": "",

View File

@ -17,6 +17,10 @@ Addressed by *river/tags*
default: 9 ++
The number of tags that should be displayed.
*tag-labels*: ++
typeof: array ++
The label to display for each tag.
# EXAMPLE
```

View File

@ -26,6 +26,14 @@ cursor is over the module, and clicking on the module toggles mute.
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 ++

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

@ -15,55 +15,35 @@ Addressed by *sway/language*
*format*: ++
typeof: string ++
default: {} ++
The format, how information should be displayed. On {} data gets inserted.
The format, how layout 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.
*on-click*: ++
*tooltip-format*: ++
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.
default: {} ++
The format, how layout should be displayed in tooltip.
*tooltip*: ++
typeof: bool ++
default: true ++
Option to disable tooltip on hover.
# FORMAT REPLACEMENTS
*{short}*: Short name of layout (e.g. "en"). Equals to {}.
*{long}*: Long name of layout (e.g. "English (Dvorak)").
*{variant}*: Variant of layout (e.g. "Dvorak").
# EXAMPLES
```
"sway/language": {
"format": "{}",
"max-length": 50
},
"sway/language": {
"format": "{short} {variant}",
}
```

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.

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.
@ -58,12 +66,32 @@ Addressed by *sway/window*
default: true ++
Option to disable tooltip on hover.
*rewrite*: ++
typeof: object ++
Rules to rewrite window title. See *rewrite rules*.
# REWRITE RULES
*rewrite* is an object where keys are regular expressions and values are
rewrite rules if the expression matches. Rules may contain references to
captures of the expression.
Regular expression and replacement follow ECMA-script rules.
If no expression matches, the title is left unchanged.
Invalid expressions (e.g., mismatched parentheses) are skipped.
# EXAMPLES
```
"sway/window": {
"format": "{}",
"max-length": 50
"max-length": 50,
"rewrite": {
"(.*) - Mozilla Firefox": "🌎 $1",
"(.*) - zsh": "> [$1]"
}
}
```

View File

@ -66,12 +66,16 @@ Addressed by *sway/workspaces*
Lists workspaces that should always be shown, even when non existent
*on-update*: ++
typeof: string ++
Command to execute when the module is updated.
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.
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

View File

@ -50,6 +50,11 @@ Addressed by *temperature*
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.

View File

@ -16,6 +16,15 @@ Addressed by *tray*
typeof: integer ++
Defines the size of the tray icons.
*show-passive-items*: ++
typeof: bool ++
default: false ++
Defines visibility of the tray icons with *Passive* status.
*smooth-scrolling-threshold*: ++
typeof: double ++
Threshold to be used when scrolling.
*spacing*: ++
typeof: integer ++
Defines the spacing between the tray icons.
@ -37,3 +46,6 @@ Addressed by *tray*
# STYLE
- *#tray*
- *#tray > .passive*
- *#tray > .active*
- *#tray > .needs-attention*

View File

@ -68,6 +68,10 @@ Addressed by *wlr/taskbar*
typeof: string ++
Command to execute when the module is updated.
*ignore-list*: ++
typeof: array ++
List of app_id to be invisible.
# FORMAT REPLACEMENTS
*{icon}*: The icon of the application.
@ -98,7 +102,10 @@ Addressed by *wlr/taskbar*
"icon-theme": "Numix-Circle",
"tooltip-format": "{title}",
"on-click": "activate",
"on-click-middle": "close"
"on-click-middle": "close",
"ignore-list": [
"Alacritty"
]
}
```

View File

@ -68,12 +68,28 @@ Also a minimal example configuration can be found on the at the bottom of this m
typeof: string ++
Optional name added as a CSS class, for styling multiple waybars.
*exclusive* ++
typeof: bool ++
default: *true* unless the layer is set to *overlay* ++
Option to request an exclusive zone from the compositor. Disable this to allow drawing application windows underneath or on top of the bar.
*passthrough* ++
typeof: bool ++
default: *false* unless the layer is set to *overlay* ++
Option to pass any pointer events to the window under the bar.
Intended to be used with either *top* or *overlay* layers and without exclusive zone.
*gtk-layer-shell* ++
typeof: bool ++
default: true ++
Option to disable the use of gtk-layer-shell for popups.
Only functional if compiled with gtk-layer-shell support.
*include* ++
typeof: string|array ++
Paths to additional configuration files. In case of duplicate options, the including file's value takes precedence. Make sure to avoid circular imports.
For a multi-bar config, specify at least an empty object for each bar also in every file being included.
# MODULE FORMAT
You can use PangoMarkupFormat (See https://developer.gnome.org/pango/stable/PangoMarkupFormat.html#PangoMarkupFormat).
@ -192,6 +208,7 @@ Valid options for the "rotate" property are: 0, 90, 180 and 270.
- *waybar-custom(5)*
- *waybar-disk(5)*
- *waybar-idle-inhibitor(5)*
- *waybar-keyboard-state(5)*
- *waybar-memory(5)*
- *waybar-mpd(5)*
- *waybar-network(5)*

View File

@ -1,6 +1,6 @@
project(
'waybar', 'cpp', 'c',
version: '0.9.5',
version: '0.9.8',
license: 'MIT',
meson_version: '>= 0.49.0',
default_options : [
@ -80,7 +80,7 @@ is_openbsd = host_machine.system() == 'openbsd'
thread_dep = dependency('threads')
fmt = dependency('fmt', version : ['>=5.3.0'], fallback : ['fmt', 'fmt_dep'])
spdlog = dependency('spdlog', version : ['>=1.8.0'], fallback : ['spdlog', 'spdlog_dep'], default_options : ['external_fmt=true'])
spdlog = dependency('spdlog', version : ['>=1.8.5'], fallback : ['spdlog', 'spdlog_dep'], default_options : ['external_fmt=true'])
wayland_client = dependency('wayland-client')
wayland_cursor = dependency('wayland-cursor')
wayland_protos = dependency('wayland-protocols')
@ -94,7 +94,9 @@ 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'))
libevdev = dependency('libevdev', required: get_option('libevdev'))
libmpdclient = dependency('libmpdclient', required: get_option('mpd'))
xkbregistry = dependency('xkbregistry')
libsndio = compiler.find_library('sndio', required: get_option('sndio'))
if libsndio.found()
@ -112,7 +114,11 @@ 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' ], modules : [ 'date::date', 'date::date-tz' ], 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')
@ -136,7 +142,6 @@ src_files = files(
'src/factory.cpp',
'src/AModule.cpp',
'src/ALabel.cpp',
'src/modules/clock.cpp',
'src/modules/custom.cpp',
'src/modules/disk.cpp',
'src/modules/idle_inhibitor.cpp',
@ -144,6 +149,7 @@ src_files = files(
'src/main.cpp',
'src/bar.cpp',
'src/client.cpp',
'src/util/ustring_clen.cpp'
)
if is_linux
@ -211,6 +217,11 @@ if libudev.found() and (is_linux or libepoll.found())
src_files += 'src/modules/backlight.cpp'
endif
if libevdev.found() and (is_linux or libepoll.found())
add_project_arguments('-DHAVE_LIBEVDEV', language: 'cpp')
src_files += 'src/modules/keyboard_state.cpp'
endif
if libmpdclient.found()
add_project_arguments('-DHAVE_LIBMPDCLIENT', language: 'cpp')
src_files += 'src/modules/mpd/mpd.cpp'
@ -236,6 +247,13 @@ if get_option('rfkill').enabled()
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(
@ -259,9 +277,11 @@ executable(
libudev,
libepoll,
libmpdclient,
libevdev,
gtk_layer_shell,
libsndio,
tz_dep
tz_dep,
xkbregistry
],
include_directories: [include_directories('include')],
install: true,
@ -299,11 +319,13 @@ if scdoc.found()
'waybar-custom.5.scd',
'waybar-disk.5.scd',
'waybar-idle-inhibitor.5.scd',
'waybar-keyboard-state.5.scd',
'waybar-memory.5.scd',
'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',

View File

@ -1,6 +1,7 @@
option('libcxx', type : 'boolean', value : false, description : 'Build with Clang\'s libc++ instead of libstdc++ on Linux.')
option('libnl', type: 'feature', value: 'auto', description: 'Enable libnl support for network related features')
option('libudev', type: 'feature', value: 'auto', description: 'Enable libudev support for udev related features')
option('libevdev', type: 'feature', value: 'auto', description: 'Enable libevdev support for evdev related features')
option('pulseaudio', type: 'feature', value: 'auto', description: 'Enable support for pulseaudio')
option('systemd', type: 'feature', value: 'auto', description: 'Install systemd user service unit')
option('dbusmenu-gtk', type: 'feature', value: 'auto', description: 'Enable support for tray')

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", "sway/language", "battery", "battery#bat2", "clock", "tray"],
"modules-right": ["mpd", "idle_inhibitor", "pulseaudio", "network", "cpu", "memory", "temperature", "backlight", "keyboard-state", "sway/language", "battery", "battery#bat2", "clock", "tray"],
// Modules configuration
// "sway/workspaces": {
// "disable-scroll": true,
@ -23,11 +23,20 @@
// "default": ""
// }
// },
"keyboard-state": {
"numlock": true,
"capslock": true,
"format": "{name} {icon}",
"format-icons": {
"locked": "",
"unlocked": ""
}
},
"sway/mode": {
"format": "<span style=\"italic\">{}</span>"
},
"mpd": {
"format": "{stateIcon} {consumeIcon}{randomIcon}{repeatIcon}{singleIcon}{artist} - {album} - {title} ({elapsedTime:%M:%S}/{totalTime:%M:%S}) ⸨{songPosition}|{queueLength}⸩ ",
"format": "{stateIcon} {consumeIcon}{randomIcon}{repeatIcon}{singleIcon}{artist} - {album} - {title} ({elapsedTime:%M:%S}/{totalTime:%M:%S}) ⸨{songPosition}|{queueLength}⸩ {volume}% ",
"format-disconnected": "Disconnected ",
"format-stopped": "{consumeIcon}{randomIcon}{repeatIcon}{singleIcon}Stopped ",
"unknown-tag": "N/A",
@ -145,3 +154,4 @@
// "exec": "$HOME/.config/waybar/mediaplayer.py --player spotify 2> /dev/null" // Filter player based on name
}
}

View File

@ -69,6 +69,7 @@ window#waybar.chromium {
#battery,
#cpu,
#memory,
#disk,
#temperature,
#backlight,
#network,
@ -107,7 +108,7 @@ window#waybar.chromium {
color: #000000;
}
#battery.charging {
#battery.charging, #battery.plugged {
color: #ffffff;
background-color: #26A65B;
}
@ -142,6 +143,10 @@ label:focus {
background-color: #9b59b6;
}
#disk {
background-color: #964B00;
}
#backlight {
background-color: #90b1b1;
}
@ -190,6 +195,15 @@ label:focus {
background-color: #2980b9;
}
#tray > .passive {
-gtk-icon-effect: dim;
}
#tray > .needs-attention {
-gtk-icon-effect: highlight;
background-color: #eb4d4b;
}
#idle_inhibitor {
background-color: #2d3436;
}
@ -223,3 +237,19 @@ label:focus {
margin: 0 5px;
min-width: 16px;
}
#keyboard-state {
background: #97e1ad;
color: #000000;
padding: 0 0px;
margin: 0 5px;
min-width: 16px;
}
#keyboard-state > label {
padding: 0 5px;
}
#keyboard-state > label.locked {
background: rgba(0, 0, 0, 0.2);
}

View File

@ -3,9 +3,11 @@ Description=Highly customizable Wayland bar for Sway and Wlroots based composito
Documentation=https://github.com/Alexays/Waybar/wiki/
PartOf=graphical-session.target
After=graphical-session.target
Requisite=graphical-session.target
[Service]
ExecStart=@prefix@/bin/waybar
ExecReload=kill -SIGUSR2 $MAINPID
Restart=on-failure
[Install]

View File

@ -20,7 +20,7 @@ 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) {
@ -28,9 +28,28 @@ ALabel::ALabel(const Json::Value& config, const std::string& name, const std::st
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 {
@ -57,17 +76,17 @@ 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) {
std::string ALabel::getIcon(uint16_t percentage, const 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())) {
format_icons = format_icons[alt];
_alt = alt;
break;
} else {
format_icons = format_icons["default"];
}
}
format_icons = format_icons[_alt];
}
if (format_icons.isArray()) {
auto size = format_icons.size();

View File

@ -33,6 +33,7 @@ struct GLSSurfaceImpl : public BarSurface, public sigc::trackable {
gtk_layer_set_monitor(window_.gobj(), output.monitor->gobj());
gtk_layer_set_namespace(window_.gobj(), "waybar");
window.signal_map_event().connect_notify(sigc::mem_fun(*this, &GLSSurfaceImpl::onMap));
window.signal_configure_event().connect_notify(
sigc::mem_fun(*this, &GLSSurfaceImpl::onConfigure));
}
@ -62,6 +63,18 @@ struct GLSSurfaceImpl : public BarSurface, public sigc::trackable {
gtk_layer_set_layer(window_.gobj(), layer);
}
void setPassThrough(bool enable) override {
passthrough_ = enable;
auto gdk_window = window_.get_window();
if (gdk_window) {
Cairo::RefPtr<Cairo::Region> region;
if (enable) {
region = Cairo::Region::create();
}
gdk_window->input_shape_combine_region(region, 0, 0);
}
}
void setPosition(const std::string_view& position) override {
auto unanchored = GTK_LAYER_SHELL_EDGE_BOTTOM;
vertical_ = false;
@ -93,8 +106,11 @@ struct GLSSurfaceImpl : public BarSurface, public sigc::trackable {
std::string output_name_;
uint32_t width_;
uint32_t height_;
bool passthrough_ = false;
bool vertical_ = false;
void onMap(GdkEventAny* ev) { setPassThrough(passthrough_); }
void onConfigure(GdkEventConfigure* ev) {
/*
* GTK wants new size for the window.
@ -182,6 +198,20 @@ struct RawSurfaceImpl : public BarSurface, public sigc::trackable {
}
}
void setPassThrough(bool enable) override {
passthrough_ = enable;
/* GTK overwrites any region changes applied directly to the wl_surface,
* thus the same GTK region API as in the GLS impl has to be used. */
auto gdk_window = window_.get_window();
if (gdk_window) {
Cairo::RefPtr<Cairo::Region> region;
if (enable) {
region = Cairo::Region::create();
}
gdk_window->input_shape_combine_region(region, 0, 0);
}
}
void setPosition(const std::string_view& position) override {
anchor_ = HORIZONTAL_ANCHOR | ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP;
if (position == "bottom") {
@ -230,6 +260,7 @@ struct RawSurfaceImpl : public BarSurface, public sigc::trackable {
uint32_t height_ = 0;
uint8_t anchor_ = HORIZONTAL_ANCHOR | ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP;
bool exclusive_zone_ = true;
bool passthrough_ = false;
struct bar_margins margins_;
zwlr_layer_shell_v1_layer layer_ = ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM;
@ -262,6 +293,7 @@ struct RawSurfaceImpl : public BarSurface, public sigc::trackable {
setSurfaceSize(width_, height_);
setExclusiveZone(exclusive_zone_);
setPassThrough(passthrough_);
commit();
wl_display_roundtrip(client->wl_display);
@ -370,9 +402,6 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config)
window.get_style_context()->add_class(output->name);
window.get_style_context()->add_class(config["name"].asString());
window.get_style_context()->add_class(config["position"].asString());
left_.get_style_context()->add_class("modules-left");
center_.get_style_context()->add_class("modules-center");
right_.get_style_context()->add_class("modules-right");
if (config["layer"] == "top") {
layer_ = bar_layer::TOP;
@ -380,6 +409,21 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config)
layer_ = bar_layer::OVERLAY;
}
if (config["exclusive"].isBool()) {
exclusive = config["exclusive"].asBool();
} else if (layer_ == bar_layer::OVERLAY) {
// swaybar defaults: overlay mode does not reserve an exclusive zone
exclusive = false;
}
bool passthrough = false;
if (config["passthrough"].isBool()) {
passthrough = config["passthrough"].asBool();
} else if (layer_ == bar_layer::OVERLAY) {
// swaybar defaults: overlay mode does not accept pointer events.
passthrough = true;
}
auto position = config["position"].asString();
if (position == "right" || position == "left") {
@ -390,6 +434,10 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config)
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;
@ -451,8 +499,9 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config)
}
surface_impl_->setLayer(layer_);
surface_impl_->setExclusiveZone(true);
surface_impl_->setExclusiveZone(exclusive);
surface_impl_->setMargins(margins_);
surface_impl_->setPassThrough(passthrough);
surface_impl_->setPosition(position);
surface_impl_->setSize(width, height);
@ -491,7 +540,7 @@ void waybar::Bar::setVisible(bool value) {
window.set_opacity(1);
surface_impl_->setLayer(layer_);
}
surface_impl_->setExclusiveZone(visible);
surface_impl_->setExclusiveZone(exclusive && visible);
surface_impl_->commit();
}

View File

@ -1,12 +1,14 @@
#include "client.hpp"
#include <fmt/ostream.h>
#include <spdlog/spdlog.h>
#include <fstream>
#include <iostream>
#include "util/clara.hpp"
#include "util/json.hpp"
#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() {
@ -58,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());
@ -71,18 +73,20 @@ void waybar::Client::handleOutput(struct waybar_output &output) {
bool waybar::Client::isValidOutput(const Json::Value &config, struct waybar_output &output) {
if (config["output"].isArray()) {
for (auto const &output_conf : config["output"]) {
if (output_conf.isString() && output_conf.asString() == output.name) {
if (output_conf.isString() &&
(output_conf.asString() == output.name || output_conf.asString() == output.identifier)) {
return true;
}
}
return false;
} else if (config["output"].isString()) {
auto config_output_name = config["output"].asString();
if (!config_output_name.empty()) {
if (config_output_name.substr(0, 1) == "!") {
return config_output_name.substr(1) != output.name;
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_name == output.name;
return config_output == output.name || config_output == output.identifier;
}
}
@ -112,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;
}
@ -147,6 +179,16 @@ 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;
@ -165,9 +207,13 @@ std::tuple<const std::string, const std::string> waybar::Client::getConfigs(
const std::string &config, const std::string &style) const {
auto config_file = config.empty() ? getValidPath({
"$XDG_CONFIG_HOME/waybar/config",
"$XDG_CONFIG_HOME/waybar/config.jsonc",
"$HOME/.config/waybar/config",
"$HOME/.config/waybar/config.jsonc",
"$HOME/waybar/config",
"$HOME/waybar/config.jsonc",
"/etc/xdg/waybar/config",
"/etc/xdg/waybar/config.jsonc",
SYSCONFDIR "/xdg/waybar/config",
"./resources/config",
})
@ -188,14 +234,62 @@ std::tuple<const std::string, const std::string> waybar::Client::getConfigs(
return {config_file, css_file};
}
auto waybar::Client::setupConfig(const std::string &config_file) -> void {
auto waybar::Client::setupConfig(const std::string &config_file, int depth) -> void {
if (depth > 100) {
throw std::runtime_error("Aborting due to likely recursive include in config files");
}
std::ifstream file(config_file);
if (!file.is_open()) {
throw std::runtime_error("Can't open config file");
}
std::string str((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
util::JsonParser parser;
config_ = parser.parse(str);
Json::Value tmp_config_ = parser.parse(str);
if (tmp_config_.isArray()) {
for (auto &config_part : tmp_config_) {
resolveConfigIncludes(config_part, depth);
}
} else {
resolveConfigIncludes(tmp_config_, depth);
}
mergeConfig(config_, tmp_config_);
}
auto waybar::Client::resolveConfigIncludes(Json::Value &config, int depth) -> void {
Json::Value includes = config["include"];
if (includes.isArray()) {
for (const auto &include : includes) {
spdlog::info("Including resource file: {}", include.asString());
setupConfig(getValidPath({include.asString()}), ++depth);
}
} else if (includes.isString()) {
spdlog::info("Including resource file: {}", includes.asString());
setupConfig(getValidPath({includes.asString()}), ++depth);
}
}
auto waybar::Client::mergeConfig(Json::Value &a_config_, Json::Value &b_config_) -> void {
if (!a_config_) {
// For the first config
a_config_ = b_config_;
} else if (a_config_.isObject() && b_config_.isObject()) {
for (const auto &key : b_config_.getMemberNames()) {
if (a_config_[key].isObject() && b_config_[key].isObject()) {
mergeConfig(a_config_[key], b_config_[key]);
} else {
a_config_[key] = b_config_[key];
}
}
} else if (a_config_.isArray() && b_config_.isArray()) {
// This can happen only on the top-level array of a multi-bar config
for (Json::Value::ArrayIndex i = 0; i < b_config_.size(); i++) {
if (a_config_[i].isObject() && b_config_[i].isObject()) {
mergeConfig(a_config_[i], b_config_[i]);
}
}
} else {
spdlog::error("Cannot merge config, conflicting or invalid JSON types");
}
}
auto waybar::Client::setupCss(const std::string &css_file) -> void {
@ -206,6 +300,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() {
@ -260,7 +357,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", Gio::APPLICATION_HANDLES_COMMAND_LINE);
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");
@ -270,16 +368,15 @@ int waybar::Client::main(int argc, char *argv[]) {
}
wl_display = gdk_wayland_display_get_wl_display(gdk_display->gobj());
auto [config_file, css_file] = getConfigs(config, style);
setupConfig(config_file);
setupConfig(config_file, 0);
setupCss(css_file);
bindInterfaces();
gtk_app->hold();
gtk_app->run();
bars.clear();
zxdg_output_manager_v1_destroy(xdg_output_manager);
zwlr_layer_shell_v1_destroy(layer_shell);
zwp_idle_inhibit_manager_v1_destroy(idle_inhibit_manager);
wl_registry_destroy(registry);
wl_display_disconnect(wl_display);
return 0;
}
void waybar::Client::reset() {
gtk_app->quit();
}

View File

@ -70,6 +70,11 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
return new waybar::modules::Backlight(id, config_[name]);
}
#endif
#ifdef HAVE_LIBEVDEV
if (ref == "keyboard-state") {
return new waybar::modules::KeyboardState(id, bar_, config_[name]);
}
#endif
#ifdef HAVE_LIBPULSE
if (ref == "pulseaudio") {
return new waybar::modules::Pulseaudio(id, config_[name]);

View File

@ -8,6 +8,7 @@
std::mutex reap_mtx;
std::list<pid_t> reap;
volatile bool reload;
void* signalThread(void* args) {
int err, signum;
@ -70,12 +71,19 @@ void startSignalThread(void) {
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) {
@ -85,7 +93,12 @@ int main(int argc, char* argv[]) {
}
startSignalThread();
auto ret = client->main(argc, argv);
auto ret = 0;
do {
reload = false;
ret = client->main(argc, argv);
} while (reload);
delete client;
return ret;
} catch (const std::exception& e) {

View File

@ -135,33 +135,73 @@ void waybar::modules::Battery::refreshBatteries() {
}
}
const std::tuple<uint8_t, float, std::string> waybar::modules::Battery::getInfos() {
// 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 {
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& 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 / "status") >> _status;
auto rate_path = fs::exists(bat / "current_now") ? "current_now" : "power_now";
std::ifstream(bat / rate_path) >> power_now;
auto now_path = fs::exists(bat / "charge_now") ? "charge_now" : "energy_now";
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") {
// Some battery will report current and charge in μA/μAh.
// Scale these by the voltage to get μW/μWh.
if (fs::exists(bat / "current_now")) {
uint32_t voltage_now;
uint32_t current_now;
uint32_t charge_now;
uint32_t charge_full;
uint32_t charge_full_design;
std::ifstream(bat / "voltage_now") >> voltage_now;
std::ifstream(bat / "current_now") >> current_now;
std::ifstream(bat / "charge_full") >> charge_full;
std::ifstream(bat / "charge_full_design") >> charge_full_design;
if (fs::exists(bat / "charge_now"))
std::ifstream(bat / "charge_now") >> charge_now;
else {
// charge_now is missing on some systems, estimate using capacity.
uint32_t capacity;
std::ifstream(bat / "capacity") >> capacity;
charge_now = (capacity * charge_full) / 100;
}
power_now = ((uint64_t)current_now * (uint64_t)voltage_now) / 1000000;
energy_now = ((uint64_t)charge_now * (uint64_t)voltage_now) / 1000000;
energy_full = ((uint64_t)charge_full * (uint64_t)voltage_now) / 1000000;
energy_full_design = ((uint64_t)charge_full_design * (uint64_t)voltage_now) / 1000000;
} else {
std::ifstream(bat / "power_now") >> power_now;
std::ifstream(bat / "energy_now") >> energy_now;
std::ifstream(bat / "energy_full") >> energy_full;
std::ifstream(bat / "energy_full_design") >> energy_full_design;
}
// Show the "smallest" status among all batteries
if (status_gt(status, _status)) {
status = _status;
}
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;
@ -182,6 +222,10 @@ const std::tuple<uint8_t, float, std::string> waybar::modules::Battery::getInfos
}
}
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);
}
// Handle full-at
if (config_["full-at"].isUInt()) {
auto full_at = config_["full-at"].asUInt();
@ -195,16 +239,16 @@ const std::tuple<uint8_t, float, std::string> waybar::modules::Battery::getInfos
capacity = 100.f;
}
uint8_t cap = round(capacity);
if (cap == 100) {
if (cap == 100 && status == "Charging") {
// 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};
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};
}
}
@ -224,7 +268,7 @@ 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");
@ -239,26 +283,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);
}
auto status_pretty = status;
// Transform to lowercase and replace space with dash
std::transform(status.begin(), status.end(), status.begin(), [](char 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_);
}
@ -278,8 +337,9 @@ auto waybar::modules::Battery::update() -> void {
auto icons = std::vector<std::string>{status + "-" + state, status, state};
label_.set_markup(fmt::format(format,
fmt::arg("capacity", capacity),
fmt::arg("power", power),
fmt::arg("icon", getIcon(capacity, icons)),
fmt::arg("time", formatTimeRemaining(time_remaining))));
fmt::arg("time", time_remaining_formatted)));
}
// Call parent update
ALabel::update();

View File

@ -1,45 +1,25 @@
#include "modules/bluetooth.hpp"
#include "util/rfkill.hpp"
#include <linux/rfkill.h>
#include <time.h>
#include <fmt/format.h>
waybar::modules::Bluetooth::Bluetooth(const std::string& id, const Json::Value& config)
: ALabel(config, "bluetooth", id, "{icon}", 10),
status_("disabled"),
rfkill_{RFKILL_TYPE_BLUETOOTH} {
thread_ = [this] {
dp.emit();
rfkill_.waitForEvent();
};
intervall_thread_ = [this] {
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);
dp.emit();
};
: 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 {
if (rfkill_.getState()) {
status_ = "disabled";
} else {
status_ = "enabled";
}
std::string status = rfkill_.getState() ? "disabled" : "enabled";
label_.set_markup(
fmt::format(
format_,
fmt::arg("status", status_),
fmt::arg("icon", getIcon(0, status_))));
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_);
auto tooltip_text = fmt::format(tooltip_format, status, fmt::arg("status", status));
label_.set_tooltip_text(tooltip_text);
} else {
label_.set_tooltip_text(status_);
label_.set_tooltip_text(status);
}
}
}

View File

@ -5,6 +5,7 @@
#include <sstream>
#include <type_traits>
#include "util/ustring_clen.hpp"
#ifdef HAVE_LANGINFO_1STDAY
#include <langinfo.h>
#include <locale.h>
@ -154,12 +155,14 @@ auto waybar::modules::Clock::weekdays_header(const date::weekday& first_dow, std
do {
if (wd != first_dow) os << ' ';
Glib::ustring wd_ustring(date::format(locale_, "%a", wd));
auto wd_len = wd_ustring.length();
if (wd_len > 2) {
wd_ustring = wd_ustring.substr(0, 2);
wd_len = 2;
auto clen = ustring_clen(wd_ustring);
auto wd_len = wd_ustring.length();
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";
@ -193,6 +196,9 @@ template <>
struct fmt::formatter<waybar_time> : fmt::formatter<std::tm> {
template <typename FormatContext>
auto format(const waybar_time& t, FormatContext& ctx) {
#if FMT_VERSION >= 80000
auto& tm_format = specs;
#endif
return format_to(ctx.out(), "{}", date::format(t.locale, fmt::to_string(tm_format), t.ztime));
}
};

View File

@ -2,8 +2,10 @@
#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>
@ -95,3 +97,12 @@ std::vector<std::tuple<size_t, size_t>> waybar::modules::Cpu::parseCpuinfo() {
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

@ -12,19 +12,36 @@ 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);
}
label_.set_markup(fmt::format(format_, fmt::arg("load", cpu_load), fmt::arg("usage", cpu_usage)));
getState(cpu_usage);
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();
}
uint16_t waybar::modules::Cpu::getCpuLoad() {
double waybar::modules::Cpu::getCpuLoad() {
double load[1];
if (getloadavg(load, 1) != -1) {
return load[0] * 100 / sysconf(_SC_NPROCESSORS_ONLN);
return load[0];
}
throw std::runtime_error("Can't get Cpu load");
}
@ -53,3 +70,16 @@ std::tuple<uint16_t, std::string> waybar::modules::Cpu::getCpuUsage() {
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 };
}

View File

@ -1,3 +1,4 @@
#include <filesystem>
#include "modules/cpu.hpp"
std::vector<std::tuple<size_t, size_t>> waybar::modules::Cpu::parseCpuinfo() {
@ -27,3 +28,50 @@ std::vector<std::tuple<size_t, size_t>> waybar::modules::Cpu::parseCpuinfo() {
}
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

@ -49,15 +49,27 @@ auto waybar::modules::Disk::update() -> void {
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;
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_)
));
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_)
));
}
if (tooltipEnabled()) {
std::string tooltip_format = "{used} used out of {total} on {path} ({percentage_used}%)";
if (config_["tooltip-format"].isString()) {
@ -73,8 +85,6 @@ auto waybar::modules::Disk::update() -> void {
, fmt::arg("path", path_)
));
}
event_box_.show();
getState(percentage_used);
// Call parent update
ALabel::update();
}

View File

@ -12,6 +12,10 @@ waybar::modules::IdleInhibitor::IdleInhibitor(const std::string& id, const Bar&
bar_(bar),
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));

View File

@ -0,0 +1,152 @@
#include "modules/keyboard_state.hpp"
#include <filesystem>
#include <spdlog/spdlog.h>
extern "C" {
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
}
waybar::modules::KeyboardState::KeyboardState(const std::string& id, const Bar& bar, const Json::Value& config)
: AModule(config, "keyboard-state", id, false, !config["disable-scroll"].asBool()),
box_(bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0),
numlock_label_(""),
capslock_label_(""),
numlock_format_(config_["format"].isString() ? config_["format"].asString()
: config_["format"]["numlock"].isString() ? config_["format"]["numlock"].asString()
: "{name} {icon}"),
capslock_format_(config_["format"].isString() ? config_["format"].asString()
: config_["format"]["capslock"].isString() ? config_["format"]["capslock"].asString()
: "{name} {icon}"),
scrolllock_format_(config_["format"].isString() ? config_["format"].asString()
: config_["format"]["scrolllock"].isString() ? config_["format"]["scrolllock"].asString()
: "{name} {icon}"),
interval_(std::chrono::seconds(config_["interval"].isUInt() ? config_["interval"].asUInt() : 1)),
icon_locked_(config_["format-icons"]["locked"].isString()
? config_["format-icons"]["locked"].asString()
: "locked"),
icon_unlocked_(config_["format-icons"]["unlocked"].isString()
? config_["format-icons"]["unlocked"].asString()
: "unlocked"),
fd_(0),
dev_(nullptr) {
box_.set_name("keyboard-state");
if (config_["numlock"].asBool()) {
box_.pack_end(numlock_label_, false, false, 0);
}
if (config_["capslock"].asBool()) {
box_.pack_end(capslock_label_, false, false, 0);
}
if (config_["scrolllock"].asBool()) {
box_.pack_end(scrolllock_label_, false, false, 0);
}
if (!id.empty()) {
box_.get_style_context()->add_class(id);
}
event_box_.add(box_);
if (config_["device-path"].isString()) {
std::string dev_path = config_["device-path"].asString();
std::tie(fd_, dev_) = openDevice(dev_path);
} else {
DIR* dev_dir = opendir("/dev/input");
if (dev_dir == nullptr) {
throw std::runtime_error("Failed to open /dev/input");
}
dirent *ep;
while ((ep = readdir(dev_dir))) {
if (ep->d_type != DT_CHR) continue;
std::string dev_path = std::string("/dev/input/") + ep->d_name;
try {
std::tie(fd_, dev_) = openDevice(dev_path);
spdlog::info("Found device {} at '{}'", libevdev_get_name(dev_), dev_path);
break;
} catch (const std::runtime_error& e) {
continue;
}
}
if (dev_ == nullptr) {
throw std::runtime_error("Failed to find keyboard device");
}
}
thread_ = [this] {
dp.emit();
thread_.sleep_for(interval_);
};
}
waybar::modules::KeyboardState::~KeyboardState() {
libevdev_free(dev_);
int err = close(fd_);
if (err < 0) {
// Not much we can do, so ignore it.
}
}
auto waybar::modules::KeyboardState::openDevice(const std::string& path) -> std::pair<int, libevdev*> {
int fd = open(path.c_str(), O_NONBLOCK | O_CLOEXEC | O_RDONLY);
if (fd < 0) {
throw std::runtime_error("Can't open " + path);
}
libevdev* dev;
int err = libevdev_new_from_fd(fd, &dev);
if (err < 0) {
throw std::runtime_error("Can't create libevdev device");
}
if (!libevdev_has_event_type(dev, EV_LED)) {
throw std::runtime_error("Device doesn't support LED events");
}
if (!libevdev_has_event_code(dev, EV_LED, LED_NUML)
|| !libevdev_has_event_code(dev, EV_LED, LED_CAPSL)
|| !libevdev_has_event_code(dev, EV_LED, LED_SCROLLL)) {
throw std::runtime_error("Device doesn't support num lock, caps lock, or scroll lock events");
}
return std::make_pair(fd, dev);
}
auto waybar::modules::KeyboardState::update() -> void {
int err = LIBEVDEV_READ_STATUS_SUCCESS;
while (err == LIBEVDEV_READ_STATUS_SUCCESS) {
input_event ev;
err = libevdev_next_event(dev_, LIBEVDEV_READ_FLAG_NORMAL, &ev);
while (err == LIBEVDEV_READ_STATUS_SYNC) {
err = libevdev_next_event(dev_, LIBEVDEV_READ_FLAG_SYNC, &ev);
}
}
if (err != -EAGAIN) {
throw std::runtime_error("Failed to sync evdev device");
}
int numl = libevdev_get_event_value(dev_, EV_LED, LED_NUML);
int capsl = libevdev_get_event_value(dev_, EV_LED, LED_CAPSL);
int scrolll = libevdev_get_event_value(dev_, EV_LED, LED_SCROLLL);
struct {
bool state;
Gtk::Label& label;
const std::string& format;
const char* name;
} label_states[] = {
{(bool) numl, numlock_label_, numlock_format_, "Num"},
{(bool) capsl, capslock_label_, capslock_format_, "Caps"},
{(bool) scrolll, scrolllock_label_, scrolllock_format_, "Scroll"},
};
for (auto& label_state : label_states) {
std::string text;
text = fmt::format(label_state.format,
fmt::arg("icon", label_state.state ? icon_locked_ : icon_unlocked_),
fmt::arg("name", label_state.name));
label_state.label.set_markup(text);
if (label_state.state) {
label_state.label.get_style_context()->add_class("locked");
} else {
label_state.label.get_style_context()->remove_class("locked");
}
}
AModule::update();
}

View File

@ -28,13 +28,24 @@ auto waybar::modules::Memory::update() -> void {
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)));
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();
@ -48,7 +59,6 @@ auto waybar::modules::Memory::update() -> void {
label_.set_tooltip_text(fmt::format("{:.{}f}GiB used", used_ram_gigabytes, 1));
}
}
event_box_.show();
} else {
event_box_.hide();
}

View File

@ -2,7 +2,7 @@
#include <fmt/chrono.h>
#include <spdlog/spdlog.h>
#include <glibmm/ustring.h>
#include "modules/mpd/state.hpp"
#if defined(MPD_NOINLINE)
namespace waybar::modules {
@ -98,9 +98,9 @@ void waybar::modules::MPD::setLabel() {
}
auto format = format_;
std::string artist, album_artist, album, title, date;
int song_pos = 0, queue_length = 0;
Glib::ustring artist, album_artist, album, title;
std::string date;
int song_pos = 0, queue_length = 0, volume = 0;
std::chrono::seconds elapsedTime, totalTime;
std::string stateIcon = "";
@ -130,6 +130,7 @@ void waybar::modules::MPD::setLabel() {
title = getTag(MPD_TAG_TITLE);
date = getTag(MPD_TAG_DATE);
song_pos = mpd_status_get_song_pos(status_.get());
volume = mpd_status_get_volume(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()));
@ -143,6 +144,10 @@ 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());
try {
label_.set_markup(
@ -152,6 +157,7 @@ void waybar::modules::MPD::setLabel() {
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("volume", volume),
fmt::arg("elapsedTime", elapsedTime),
fmt::arg("totalTime", totalTime),
fmt::arg("songPosition", song_pos),
@ -171,11 +177,12 @@ void waybar::modules::MPD::setLabel() {
: "MPD (connected)";
try {
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("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("volume", volume),
fmt::arg("elapsedTime", elapsedTime),
fmt::arg("totalTime", totalTime),
fmt::arg("songPosition", song_pos),

View File

@ -1,6 +1,7 @@
#include "modules/network.hpp"
#include <spdlog/spdlog.h>
#include <sys/eventfd.h>
#include <linux/if.h>
#include <fstream>
#include <cassert>
#include <optional>
@ -18,6 +19,7 @@ constexpr const char *NETSTAT_FILE =
constexpr std::string_view BANDWIDTH_CATEGORY = "IpExt";
constexpr std::string_view BANDWIDTH_DOWN_TOTAL_KEY = "InOctets";
constexpr std::string_view BANDWIDTH_UP_TOTAL_KEY = "OutOctets";
constexpr const char *DEFAULT_FORMAT = "{ifname}";
std::ifstream netstat(NETSTAT_FILE);
std::optional<unsigned long long> read_netstat(std::string_view category, std::string_view key) {
@ -81,18 +83,29 @@ std::optional<unsigned long long> read_netstat(std::string_view category, std::s
} // namespace
waybar::modules::Network::Network(const std::string &id, const Json::Value &config)
: ALabel(config, "network", id, "{ifname}", 60),
: ALabel(config, "network", id, DEFAULT_FORMAT, 60),
ifid_(-1),
family_(config["family"] == "ipv6" ? AF_INET6 : AF_INET),
efd_(-1),
ev_fd_(-1),
cidr_(-1),
want_route_dump_(false),
want_link_dump_(false),
want_addr_dump_(false),
dump_in_progress_(false),
cidr_(0),
signal_strength_dbm_(0),
signal_strength_(0),
#ifdef WANT_RFKILL
rfkill_{RFKILL_TYPE_WLAN},
#endif
frequency_(0) {
// Start with some "text" in the module's label_, update() will then
// update it. Since the text should be different, update() will be able
// to show or hide the event_box_. This is to work around the case where
// the module start with no text, but the the event_box_ is shown.
label_.set_markup("<s></s>");
auto down_octets = read_netstat(BANDWIDTH_CATEGORY, BANDWIDTH_DOWN_TOTAL_KEY);
auto up_octets = read_netstat(BANDWIDTH_CATEGORY, BANDWIDTH_UP_TOTAL_KEY);
if (down_octets) {
@ -107,17 +120,25 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf
bandwidth_up_total_ = 0;
}
if (!config_["interface"].isString()) {
// "interface" isn't configure, then try to guess the external
// interface currently used for internet.
want_route_dump_ = true;
} else {
// Look for an interface that match "interface"
// and then find the address associated with it.
want_link_dump_ = true;
want_addr_dump_ = true;
}
createEventSocket();
createInfoSocket();
auto default_iface = getPreferredIface(-1, false);
if (default_iface != -1) {
ifid_ = default_iface;
char ifname[IF_NAMESIZE];
if_indextoname(default_iface, ifname);
ifname_ = ifname;
getInterfaceAddress();
}
dp.emit();
// Ask for a dump of interfaces and then addresses to populate our
// information. First the interface dump, and once done, the callback
// will be called again which will ask for addresses dump.
askForStateDump();
worker();
}
@ -148,17 +169,29 @@ void waybar::modules::Network::createEventSocket() {
ev_sock_ = nl_socket_alloc();
nl_socket_disable_seq_check(ev_sock_);
nl_socket_modify_cb(ev_sock_, NL_CB_VALID, NL_CB_CUSTOM, handleEvents, this);
nl_socket_modify_cb(ev_sock_, NL_CB_FINISH, NL_CB_CUSTOM, handleEventsDone, this);
auto groups = RTMGRP_LINK | (family_ == AF_INET ? RTMGRP_IPV4_IFADDR : RTMGRP_IPV6_IFADDR);
nl_join_groups(ev_sock_, groups); // Deprecated
if (nl_connect(ev_sock_, NETLINK_ROUTE) != 0) {
throw std::runtime_error("Can't connect network socket");
}
if (nl_socket_set_nonblocking(ev_sock_)) {
throw std::runtime_error("Can't set non-blocking on network socket");
}
nl_socket_add_membership(ev_sock_, RTNLGRP_LINK);
if (family_ == AF_INET) {
nl_socket_add_membership(ev_sock_, RTNLGRP_IPV4_IFADDR);
} else {
nl_socket_add_membership(ev_sock_, RTNLGRP_IPV6_IFADDR);
}
if (!config_["interface"].isString()) {
if (family_ == AF_INET) {
nl_socket_add_membership(ev_sock_, RTNLGRP_IPV4_ROUTE);
} else {
nl_socket_add_membership(ev_sock_, RTNLGRP_IPV6_ROUTE);
}
}
efd_ = epoll_create1(EPOLL_CLOEXEC);
if (efd_ < 0) {
throw std::runtime_error("Can't create epoll");
@ -212,18 +245,15 @@ void waybar::modules::Network::worker() {
thread_timer_.sleep_for(interval_);
};
#ifdef WANT_RFKILL
thread_rfkill_ = [this] {
rfkill_.waitForEvent();
{
std::lock_guard<std::mutex> lock(mutex_);
if (ifid_ > 0) {
getInfo();
dp.emit();
}
}
};
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.");
spdlog::warn("Waybar has been built without rfkill support.");
#endif
thread_ = [this] {
std::array<struct epoll_event, EPOLL_MAX> events{};
@ -231,7 +261,23 @@ void waybar::modules::Network::worker() {
int ec = epoll_wait(efd_, events.data(), EPOLL_MAX, -1);
if (ec > 0) {
for (auto i = 0; i < ec; i++) {
if (events[i].data.fd != nl_socket_get_fd(ev_sock_) || nl_recvmsgs_default(ev_sock_) < 0) {
if (events[i].data.fd == nl_socket_get_fd(ev_sock_)) {
int rc = 0;
// Read as many message as possible, until the socket blocks
while (true) {
errno = 0;
rc = nl_recvmsgs_default(ev_sock_);
if (rc == -NLE_AGAIN || errno == EAGAIN) {
rc = 0;
break;
}
}
if (rc < 0) {
spdlog::error("nl_recvmsgs_default error: {}", nl_geterror(-rc));
thread_.stop();
break;
}
} else {
thread_.stop();
break;
}
@ -248,6 +294,7 @@ const std::string waybar::modules::Network::getNetworkState() const {
#endif
return "disconnected";
}
if (!carrier_) return "disconnected";
if (ipaddr_.empty()) return "linked";
if (essid_.empty()) return "ethernet";
return "wifi";
@ -277,6 +324,10 @@ auto waybar::modules::Network::update() -> void {
}
if (config_["format-" + state].isString()) {
default_format_ = config_["format-" + state].asString();
} else if (config_["format"].isString()) {
default_format_ = config_["format"].asString();
} else {
default_format_ = DEFAULT_FORMAT;
}
if (config_["tooltip-format-" + state].isString()) {
tooltip_format = config_["tooltip-format-" + state].asString();
@ -345,349 +396,346 @@ auto waybar::modules::Network::update() -> void {
ALabel::update();
}
// Based on https://gist.github.com/Yawning/c70d804d4b8ae78cc698
int waybar::modules::Network::getExternalInterface(int skip_idx) const {
static const uint32_t route_buffer_size = 8192;
struct nlmsghdr * hdr = nullptr;
struct rtmsg * rt = nullptr;
char resp[route_buffer_size] = {0};
int ifidx = -1;
/* Prepare request. */
constexpr uint32_t reqlen = NLMSG_SPACE(sizeof(*rt));
char req[reqlen] = {0};
/* Build the RTM_GETROUTE request. */
hdr = reinterpret_cast<struct nlmsghdr *>(req);
hdr->nlmsg_len = NLMSG_LENGTH(sizeof(*rt));
hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
hdr->nlmsg_type = RTM_GETROUTE;
rt = static_cast<struct rtmsg *>(NLMSG_DATA(hdr));
rt->rtm_family = family_;
rt->rtm_table = RT_TABLE_MAIN;
/* Issue the query. */
if (netlinkRequest(req, reqlen) < 0) {
goto out;
}
/* Read the response(s).
*
* WARNING: All the packets generated by the request must be consumed (as in,
* consume responses till NLMSG_DONE/NLMSG_ERROR is encountered).
*/
do {
auto len = netlinkResponse(resp, route_buffer_size);
if (len < 0) {
goto out;
}
/* Parse the response payload into netlink messages. */
for (hdr = reinterpret_cast<struct nlmsghdr *>(resp); NLMSG_OK(hdr, len);
hdr = NLMSG_NEXT(hdr, len)) {
if (hdr->nlmsg_type == NLMSG_DONE) {
goto out;
}
if (hdr->nlmsg_type == NLMSG_ERROR) {
/* Even if we found the interface index, something is broken with the
* netlink socket, so return an error.
*/
ifidx = -1;
goto out;
}
/* If we found the correct answer, skip parsing the attributes. */
if (ifidx != -1) {
continue;
}
/* Find the message(s) concerting the main routing table, each message
* corresponds to a single routing table entry.
*/
rt = static_cast<struct rtmsg *>(NLMSG_DATA(hdr));
if (rt->rtm_table != RT_TABLE_MAIN) {
continue;
}
/* Parse all the attributes for a single routing table entry. */
struct rtattr *attr = RTM_RTA(rt);
uint64_t attrlen = RTM_PAYLOAD(hdr);
bool has_gateway = false;
bool has_destination = false;
int temp_idx = -1;
for (; RTA_OK(attr, attrlen); attr = RTA_NEXT(attr, attrlen)) {
/* Determine if this routing table entry corresponds to the default
* route by seeing if it has a gateway, and if a destination addr is
* set, that it is all 0s.
*/
switch (attr->rta_type) {
case RTA_GATEWAY:
/* The gateway of the route.
*
* If someone every needs to figure out the gateway address as well,
* it's here as the attribute payload.
*/
has_gateway = true;
break;
case RTA_DST: {
/* The destination address.
* Should be either missing, or maybe all 0s. Accept both.
*/
const uint32_t nr_zeroes = (family_ == AF_INET) ? 4 : 16;
unsigned char c = 0;
size_t dstlen = RTA_PAYLOAD(attr);
if (dstlen != nr_zeroes) {
break;
}
for (uint32_t i = 0; i < dstlen; i += 1) {
c |= *((unsigned char *)RTA_DATA(attr) + i);
}
has_destination = (c == 0);
break;
}
case RTA_OIF:
/* The output interface index. */
temp_idx = *static_cast<int *>(RTA_DATA(attr));
break;
default:
break;
}
}
/* If this is the default route, and we know the interface index,
* we can stop parsing this message.
*/
if (has_gateway && !has_destination && temp_idx != -1 && temp_idx != skip_idx) {
ifidx = temp_idx;
break;
}
}
} while (true);
out:
return ifidx;
}
void waybar::modules::Network::getInterfaceAddress() {
struct ifaddrs *ifaddr, *ifa;
cidr_ = 0;
int success = getifaddrs(&ifaddr);
if (success != 0) {
return;
}
ifa = ifaddr;
while (ifa != nullptr) {
if (ifa->ifa_addr != nullptr && ifa->ifa_addr->sa_family == family_ &&
ifa->ifa_name == ifname_) {
char ipaddr[INET6_ADDRSTRLEN];
char netmask[INET6_ADDRSTRLEN];
unsigned int cidr = 0;
if (family_ == AF_INET) {
ipaddr_ = inet_ntop(AF_INET,
&reinterpret_cast<struct sockaddr_in *>(ifa->ifa_addr)->sin_addr,
ipaddr,
INET_ADDRSTRLEN);
auto net_addr = reinterpret_cast<struct sockaddr_in *>(ifa->ifa_netmask);
netmask_ = inet_ntop(AF_INET, &net_addr->sin_addr, netmask, INET_ADDRSTRLEN);
unsigned int cidrRaw = net_addr->sin_addr.s_addr;
while (cidrRaw) {
cidr += cidrRaw & 1;
cidrRaw >>= 1;
}
} else {
ipaddr_ = inet_ntop(AF_INET6,
&reinterpret_cast<struct sockaddr_in6 *>(ifa->ifa_addr)->sin6_addr,
ipaddr,
INET6_ADDRSTRLEN);
auto net_addr = reinterpret_cast<struct sockaddr_in6 *>(ifa->ifa_netmask);
netmask_ = inet_ntop(AF_INET6, &net_addr->sin6_addr, netmask, INET6_ADDRSTRLEN);
for (size_t i = 0; i < sizeof(net_addr->sin6_addr.s6_addr); ++i) {
unsigned char cidrRaw = net_addr->sin6_addr.s6_addr[i];
while (cidrRaw) {
cidr += cidrRaw & 1;
cidrRaw >>= 1;
}
}
}
cidr_ = cidr;
break;
}
ifa = ifa->ifa_next;
}
freeifaddrs(ifaddr);
}
int waybar::modules::Network::netlinkRequest(void *req, uint32_t reqlen, uint32_t groups) const {
struct sockaddr_nl sa = {};
sa.nl_family = AF_NETLINK;
sa.nl_groups = groups;
struct iovec iov = {req, reqlen};
struct msghdr msg = {
.msg_name = &sa,
.msg_namelen = sizeof(sa),
.msg_iov = &iov,
.msg_iovlen = 1,
};
return sendmsg(nl_socket_get_fd(ev_sock_), &msg, 0);
}
int waybar::modules::Network::netlinkResponse(void *resp, uint32_t resplen, uint32_t groups) const {
struct sockaddr_nl sa = {};
sa.nl_family = AF_NETLINK;
sa.nl_groups = groups;
struct iovec iov = {resp, resplen};
struct msghdr msg = {
.msg_name = &sa,
.msg_namelen = sizeof(sa),
.msg_iov = &iov,
.msg_iovlen = 1,
};
auto ret = recvmsg(nl_socket_get_fd(ev_sock_), &msg, 0);
if (msg.msg_flags & MSG_TRUNC) {
return -1;
}
return ret;
}
bool waybar::modules::Network::checkInterface(struct ifinfomsg *rtif, std::string name) {
bool waybar::modules::Network::checkInterface(std::string name) {
if (config_["interface"].isString()) {
return config_["interface"].asString() == name ||
wildcardMatch(config_["interface"].asString(), name);
}
// getExternalInterface may need some delay to detect external interface
for (uint8_t tries = 0; tries < MAX_RETRY; tries += 1) {
auto external_iface = getExternalInterface();
if (external_iface > 0) {
return external_iface == rtif->ifi_index;
}
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
return false;
}
int waybar::modules::Network::getPreferredIface(int skip_idx, bool wait) const {
int ifid = -1;
if (config_["interface"].isString()) {
ifid = if_nametoindex(config_["interface"].asCString());
if (ifid > 0) {
return ifid;
} else {
// Try with wildcard
struct ifaddrs *ifaddr, *ifa;
int success = getifaddrs(&ifaddr);
if (success != 0) {
return -1;
}
ifa = ifaddr;
ifid = -1;
while (ifa != nullptr) {
if (wildcardMatch(config_["interface"].asString(), ifa->ifa_name)) {
ifid = if_nametoindex(ifa->ifa_name);
break;
}
ifa = ifa->ifa_next;
}
freeifaddrs(ifaddr);
return ifid;
}
}
// getExternalInterface may need some delay to detect external interface
for (uint8_t tries = 0; tries < MAX_RETRY; tries += 1) {
ifid = getExternalInterface(skip_idx);
if (ifid > 0) {
return ifid;
}
if (wait) {
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
}
return -1;
}
void waybar::modules::Network::clearIface() {
ifid_ = -1;
ifname_.clear();
essid_.clear();
ipaddr_.clear();
netmask_.clear();
carrier_ = false;
cidr_ = 0;
signal_strength_dbm_ = 0;
signal_strength_ = 0;
frequency_ = 0;
}
void waybar::modules::Network::checkNewInterface(struct ifinfomsg *rtif) {
auto new_iface = getPreferredIface(rtif->ifi_index);
if (new_iface != -1) {
ifid_ = new_iface;
char ifname[IF_NAMESIZE];
if_indextoname(new_iface, ifname);
ifname_ = ifname;
getInterfaceAddress();
thread_timer_.wake_up();
} else {
ifid_ = -1;
dp.emit();
}
}
int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
auto net = static_cast<waybar::modules::Network *>(data);
std::lock_guard<std::mutex> lock(net->mutex_);
auto nh = nlmsg_hdr(msg);
auto ifi = static_cast<struct ifinfomsg *>(NLMSG_DATA(nh));
if (nh->nlmsg_type == RTM_DELADDR) {
// Check for valid interface
if (ifi->ifi_index == net->ifid_) {
net->ipaddr_.clear();
net->netmask_.clear();
net->cidr_ = 0;
if (!(ifi->ifi_flags & IFF_RUNNING)) {
net->clearIface();
// Check for a new interface and get info
net->checkNewInterface(ifi);
} else {
net->dp.emit();
}
bool is_del_event = false;
switch (nh->nlmsg_type) {
case RTM_DELLINK:
is_del_event = true;
case RTM_NEWLINK: {
struct ifinfomsg *ifi = static_cast<struct ifinfomsg *>(NLMSG_DATA(nh));
ssize_t attrlen = IFLA_PAYLOAD(nh);
struct rtattr *ifla = IFLA_RTA(ifi);
const char *ifname = NULL;
size_t ifname_len = 0;
std::optional<bool> carrier;
if (net->ifid_ != -1 && ifi->ifi_index != net->ifid_) {
return NL_OK;
}
} else if (nh->nlmsg_type == RTM_NEWLINK || nh->nlmsg_type == RTM_DELLINK) {
char ifname[IF_NAMESIZE];
if_indextoname(ifi->ifi_index, ifname);
// Check for valid interface
if (ifi->ifi_index != net->ifid_ && net->checkInterface(ifi, ifname)) {
net->ifname_ = ifname;
net->ifid_ = ifi->ifi_index;
// Get Iface and WIFI info
net->getInterfaceAddress();
net->thread_timer_.wake_up();
return NL_OK;
} else if (ifi->ifi_index == net->ifid_ &&
(!(ifi->ifi_flags & IFF_RUNNING) || !(ifi->ifi_flags & IFF_UP) ||
!net->checkInterface(ifi, ifname))) {
// Check if the interface goes "down" and if we want to detect the
// external interface.
if (net->ifid_ != -1 && !(ifi->ifi_flags & IFF_UP)
&& !net->config_["interface"].isString()) {
// The current interface is now down, all the routes associated with
// it have been deleted, so start looking for a new default route.
spdlog::debug("network: if{} down", net->ifid_);
net->clearIface();
// Check for a new interface and get info
net->checkNewInterface(ifi);
net->dp.emit();
net->want_route_dump_ = true;
net->askForStateDump();
return NL_OK;
}
} else {
char ifname[IF_NAMESIZE];
if_indextoname(ifi->ifi_index, ifname);
// Auto detected network can also be assigned here
if (ifi->ifi_index != net->ifid_ && net->checkInterface(ifi, ifname)) {
// If iface is different, clear data
if (ifi->ifi_index != net->ifid_) {
net->clearIface();
for (; RTA_OK(ifla, attrlen); ifla = RTA_NEXT(ifla, attrlen)) {
switch (ifla->rta_type) {
case IFLA_IFNAME:
ifname = static_cast<const char *>(RTA_DATA(ifla));
ifname_len = RTA_PAYLOAD(ifla) - 1; // minus \0
break;
case IFLA_CARRIER: {
carrier = *(char*)RTA_DATA(ifla) == 1;
break;
}
}
net->ifname_ = ifname;
net->ifid_ = ifi->ifi_index;
}
// Check for valid interface
if (ifi->ifi_index == net->ifid_) {
// Get Iface and WIFI info
net->getInterfaceAddress();
net->thread_timer_.wake_up();
if (!is_del_event && ifi->ifi_index == net->ifid_) {
// Update inferface information
if (net->ifname_.empty() && ifname != NULL) {
std::string new_ifname (ifname, ifname_len);
net->ifname_ = new_ifname;
}
if (carrier.has_value()) {
if (net->carrier_ != *carrier) {
if (*carrier) {
// Ask for WiFi information
net->thread_timer_.wake_up();
} else {
// clear state related to WiFi connection
net->essid_.clear();
net->signal_strength_dbm_ = 0;
net->signal_strength_ = 0;
net->frequency_ = 0;
}
}
net->carrier_ = carrier.value();
}
} else if (!is_del_event && net->ifid_ == -1) {
// Checking if it's an interface we care about.
std::string new_ifname (ifname, ifname_len);
if (net->checkInterface(new_ifname)) {
spdlog::debug("network: selecting new interface {}/{}", new_ifname, ifi->ifi_index);
net->ifname_ = new_ifname;
net->ifid_ = ifi->ifi_index;
if (carrier.has_value()) {
net->carrier_ = carrier.value();
}
net->thread_timer_.wake_up();
/* An address for this new interface should be received via an
* RTM_NEWADDR event either because we ask for a dump of both links
* and addrs, or because this interface has just been created and
* the addr will be sent after the RTM_NEWLINK event.
* So we don't need to do anything. */
}
} else if (is_del_event && net->ifid_ >= 0) {
// Our interface has been deleted, start looking/waiting for one we care.
spdlog::debug("network: interface {}/{} deleted", net->ifname_, net->ifid_);
net->clearIface();
net->dp.emit();
}
break;
}
case RTM_DELADDR:
is_del_event = true;
case RTM_NEWADDR: {
struct ifaddrmsg *ifa = static_cast<struct ifaddrmsg *>(NLMSG_DATA(nh));
ssize_t attrlen = IFA_PAYLOAD(nh);
struct rtattr *ifa_rta = IFA_RTA(ifa);
if ((int)ifa->ifa_index != net->ifid_) {
return NL_OK;
}
if (ifa->ifa_family != net->family_) {
return NL_OK;
}
// We ignore address mark as scope for the link or host,
// which should leave scope global addresses.
if (ifa->ifa_scope >= RT_SCOPE_LINK) {
return NL_OK;
}
for (; RTA_OK(ifa_rta, attrlen); ifa_rta = RTA_NEXT(ifa_rta, attrlen)) {
switch (ifa_rta->rta_type) {
case IFA_ADDRESS: {
char ipaddr[INET6_ADDRSTRLEN];
if (!is_del_event) {
net->ipaddr_ = inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta),
ipaddr, sizeof (ipaddr));
net->cidr_ = ifa->ifa_prefixlen;
switch (ifa->ifa_family) {
case AF_INET: {
struct in_addr netmask;
netmask.s_addr = htonl(~0 << (32 - ifa->ifa_prefixlen));
net->netmask_ = inet_ntop(ifa->ifa_family, &netmask,
ipaddr, sizeof (ipaddr));
}
case AF_INET6: {
struct in6_addr netmask;
for (int i = 0; i < 16; i++) {
int v = (i + 1) * 8 - ifa->ifa_prefixlen;
if (v < 0) v = 0;
if (v > 8) v = 8;
netmask.s6_addr[i] = ~0 << v;
}
net->netmask_ = inet_ntop(ifa->ifa_family, &netmask,
ipaddr, sizeof (ipaddr));
}
}
spdlog::debug("network: {}, new addr {}/{}", net->ifname_, net->ipaddr_, net->cidr_);
} else {
net->ipaddr_.clear();
net->cidr_ = 0;
net->netmask_.clear();
spdlog::debug("network: {} addr deleted {}/{}",
net->ifname_,
inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta),
ipaddr, sizeof (ipaddr)),
ifa->ifa_prefixlen);
}
net->dp.emit();
break;
}
}
}
break;
}
return NL_SKIP;
case RTM_DELROUTE:
is_del_event = true;
case RTM_NEWROUTE: {
// Based on https://gist.github.com/Yawning/c70d804d4b8ae78cc698
// to find the interface used to reach the outside world
struct rtmsg *rtm = static_cast<struct rtmsg *>(NLMSG_DATA(nh));
ssize_t attrlen = RTM_PAYLOAD(nh);
struct rtattr *attr = RTM_RTA(rtm);
bool has_gateway = false;
bool has_destination = false;
int temp_idx = -1;
uint32_t priority = 0;
/* Find the message(s) concerting the main routing table, each message
* corresponds to a single routing table entry.
*/
if (rtm->rtm_table != RT_TABLE_MAIN) {
return NL_OK;
}
/* Parse all the attributes for a single routing table entry. */
for (; RTA_OK(attr, attrlen); attr = RTA_NEXT(attr, attrlen)) {
/* Determine if this routing table entry corresponds to the default
* route by seeing if it has a gateway, and if a destination addr is
* set, that it is all 0s.
*/
switch(attr->rta_type) {
case RTA_GATEWAY:
/* The gateway of the route.
*
* If someone every needs to figure out the gateway address as well,
* it's here as the attribute payload.
*/
has_gateway = true;
break;
case RTA_DST: {
/* The destination address.
* Should be either missing, or maybe all 0s. Accept both.
*/
const uint32_t nr_zeroes = (net->family_ == AF_INET) ? 4 : 16;
unsigned char c = 0;
size_t dstlen = RTA_PAYLOAD(attr);
if (dstlen != nr_zeroes) {
break;
}
for (uint32_t i = 0; i < dstlen; i += 1) {
c |= *((unsigned char *)RTA_DATA(attr) + i);
}
has_destination = (c == 0);
break;
}
case RTA_OIF:
/* The output interface index. */
temp_idx = *static_cast<int *>(RTA_DATA(attr));
break;
case RTA_PRIORITY:
priority = *(uint32_t*)RTA_DATA(attr);
break;
default:
break;
}
}
// Check if we have a default route.
if (has_gateway && !has_destination && temp_idx != -1) {
// Check if this is the first default route we see, or if this new
// route have a higher priority.
if (!is_del_event && ((net->ifid_ == -1) || (priority < net->route_priority))) {
// Clear if's state for the case were there is a higher priority
// route on a different interface.
net->clearIface();
net->ifid_ = temp_idx;
net->route_priority = priority;
spdlog::debug("network: new default route via if{} metric {}", temp_idx, priority);
/* Ask ifname associated with temp_idx as well as carrier status */
struct ifinfomsg ifinfo_hdr = {
.ifi_family = AF_UNSPEC,
.ifi_index = temp_idx,
};
int err;
err = nl_send_simple(net->ev_sock_, RTM_GETLINK, NLM_F_REQUEST,
&ifinfo_hdr, sizeof (ifinfo_hdr));
if (err < 0) {
spdlog::error("network: failed to ask link info: {}", err);
/* Ask for a dump of all links instead */
net->want_link_dump_ = true;
}
/* Also ask for the address. Asking for a addresses of a specific
* interface doesn't seems to work so ask for a dump of all
* addresses. */
net->want_addr_dump_ = true;
net->askForStateDump();
net->thread_timer_.wake_up();
} else if (is_del_event && temp_idx == net->ifid_
&& net->route_priority == priority) {
spdlog::debug("network: default route deleted {}/if{} metric {}",
net->ifname_, temp_idx, priority);
net->clearIface();
net->dp.emit();
/* Ask for a dump of all routes in case another one is already
* setup. If there's none, there'll be an event with new one
* later. */
net->want_route_dump_ = true;
net->askForStateDump();
}
}
break;
}
}
return NL_OK;
}
void waybar::modules::Network::askForStateDump(void) {
/* We need to wait until the current dump is done before sending new
* messages. handleEventsDone() is called when a dump is done. */
if (dump_in_progress_)
return;
struct rtgenmsg rt_hdr = {
.rtgen_family = AF_UNSPEC,
};
if (want_route_dump_) {
rt_hdr.rtgen_family = family_;
nl_send_simple(ev_sock_, RTM_GETROUTE, NLM_F_DUMP,
&rt_hdr, sizeof (rt_hdr));
want_route_dump_ = false;
dump_in_progress_ = true;
} else if (want_link_dump_) {
nl_send_simple(ev_sock_, RTM_GETLINK, NLM_F_DUMP,
&rt_hdr, sizeof (rt_hdr));
want_link_dump_ = false;
dump_in_progress_ = true;
} else if (want_addr_dump_) {
rt_hdr.rtgen_family = family_;
nl_send_simple(ev_sock_, RTM_GETADDR, NLM_F_DUMP,
&rt_hdr, sizeof (rt_hdr));
want_addr_dump_ = false;
dump_in_progress_ = true;
}
}
int waybar::modules::Network::handleEventsDone(struct nl_msg *msg, void *data) {
auto net = static_cast<waybar::modules::Network *>(data);
net->dump_in_progress_ = false;
net->askForStateDump();
return NL_OK;
}
int waybar::modules::Network::handleScan(struct nl_msg *msg, void *data) {

View File

@ -151,8 +151,24 @@ void waybar::modules::Pulseaudio::sourceInfoCb(pa_context * /*context*/, const p
*/
void waybar::modules::Pulseaudio::sinkInfoCb(pa_context * /*context*/, const pa_sink_info *i,
int /*eol*/, void *data) {
if (i == nullptr)
return;
auto pa = static_cast<waybar::modules::Pulseaudio *>(data);
if (i != nullptr && pa->default_sink_name_ == i->name) {
if (pa->current_sink_name_ == i->name) {
if (i->state != PA_SINK_RUNNING) {
pa->current_sink_running_ = false;
} else {
pa->current_sink_running_ = true;
}
}
if (!pa->current_sink_running_ && i->state == PA_SINK_RUNNING) {
pa->current_sink_name_ = i->name;
pa->current_sink_running_ = true;
}
if (pa->current_sink_name_ == i->name) {
pa->pa_volume_ = i->volume;
float volume = static_cast<float>(pa_cvolume_avg(&(pa->pa_volume_))) / float{PA_VOLUME_NORM};
pa->sink_idx_ = i->index;
@ -175,11 +191,11 @@ void waybar::modules::Pulseaudio::sinkInfoCb(pa_context * /*context*/, const pa_
void waybar::modules::Pulseaudio::serverInfoCb(pa_context *context, const pa_server_info *i,
void *data) {
auto pa = static_cast<waybar::modules::Pulseaudio *>(data);
pa->default_sink_name_ = i->default_sink_name;
pa->current_sink_name_ = i->default_sink_name;
pa->default_source_name_ = i->default_source_name;
pa_context_get_sink_info_by_name(context, i->default_sink_name, sinkInfoCb, data);
pa_context_get_source_info_by_name(context, i->default_source_name, sourceInfoCb, data);
pa_context_get_sink_info_list(context, sinkInfoCb, data);
pa_context_get_source_info_list(context, sourceInfoCb, data);
}
static const std::array<std::string, 9> ports = {
@ -194,22 +210,26 @@ static const std::array<std::string, 9> ports = {
"phone",
};
const std::string waybar::modules::Pulseaudio::getPortIcon() const {
const std::vector<std::string> waybar::modules::Pulseaudio::getPulseIcon() const {
std::vector<std::string> res = {default_source_name_};
std::string nameLC = port_name_ + form_factor_;
std::transform(nameLC.begin(), nameLC.end(), nameLC.begin(), ::tolower);
for (auto const &port : ports) {
if (nameLC.find(port) != std::string::npos) {
return port;
res.push_back(port);
return res;
}
}
return port_name_;
return res;
}
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) {
if (monitor_.find("a2dp_sink") != std::string::npos || // PulseAudio
monitor_.find("a2dp-sink") != std::string::npos) { // PipeWire
format_name = format_name + "-bluetooth";
label_.get_style_context()->add_class("bluetooth");
} else {
@ -222,28 +242,53 @@ 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("icon", getIcon(volume_, getPortIcon()))));
fmt::arg("source_volume", source_volume_),
fmt::arg("source_desc", source_desc_),
fmt::arg("icon", getIcon(volume_, getPulseIcon()))));
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_, getPulseIcon()))));
} else {
label_.set_tooltip_text(desc_);
}
}
// Call parent update

View File

@ -3,6 +3,8 @@
#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"
@ -64,8 +66,20 @@ Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &con
// Default to 9 tags
const uint32_t num_tags = config["num-tags"].isUInt() ? config_["num-tags"].asUInt() : 9;
for (uint32_t tag = 1; tag <= num_tags; ++tag) {
Gtk::Button &button = buttons_.emplace_back(std::to_string(tag));
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();

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();
}

View File

@ -1,7 +1,12 @@
#include "modules/sni/item.hpp"
#include <gdkmm/general.h>
#include <glibmm/main.h>
#include <gtkmm/tooltip.h>
#include <spdlog/spdlog.h>
#include <fstream>
#include <map>
template <>
struct fmt::formatter<Glib::ustring> : formatter<std::string> {
@ -39,14 +44,22 @@ Item::Item(const std::string& bn, const std::string& op, const Json::Value& conf
object_path(op),
icon_size(16),
effective_icon_size(0),
icon_theme(Gtk::IconTheme::create()),
update_pending_(false) {
icon_theme(Gtk::IconTheme::create()) {
if (config["icon-size"].isUInt()) {
icon_size = config["icon-size"].asUInt();
}
if (config["smooth-scrolling-threshold"].isNumeric()) {
scroll_threshold_ = config["smooth-scrolling-threshold"].asDouble();
}
if (config["show-passive-items"].isBool()) {
show_passive_ = config["show-passive-items"].asBool();
}
event_box.add(image);
event_box.add_events(Gdk::BUTTON_PRESS_MASK);
event_box.add_events(Gdk::BUTTON_PRESS_MASK | Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);
event_box.signal_button_press_event().connect(sigc::mem_fun(*this, &Item::handleClick));
event_box.signal_scroll_event().connect(sigc::mem_fun(*this, &Item::handleScroll));
// initial visibility
event_box.set_visible(show_passive_);
cancellable_ = Gio::Cancellable::create();
@ -73,12 +86,11 @@ void Item::proxyReady(Glib::RefPtr<Gio::AsyncResult>& result) {
this->proxy_->signal_signal().connect(sigc::mem_fun(*this, &Item::onSignal));
if (this->id.empty() || this->category.empty() || this->status.empty()) {
if (this->id.empty() || this->category.empty()) {
spdlog::error("Invalid Status Notifier Item: {}, {}", bus_name, object_path);
return;
}
this->updateImage();
// this->event_box.set_tooltip_text(this->title);
} catch (const Glib::Error& err) {
spdlog::error("Failed to create DBus Proxy for {} {}: {}", bus_name, object_path, err.what());
@ -88,10 +100,24 @@ void Item::proxyReady(Glib::RefPtr<Gio::AsyncResult>& result) {
}
template <typename T>
T get_variant(Glib::VariantBase& value) {
T get_variant(const Glib::VariantBase& value) {
return Glib::VariantBase::cast_dynamic<Glib::Variant<T>>(value).get();
}
template <>
ToolTip get_variant<ToolTip>(const Glib::VariantBase& value) {
ToolTip result;
// Unwrap (sa(iiay)ss)
auto container = value.cast_dynamic<Glib::VariantContainerBase>(value);
result.icon_name = get_variant<Glib::ustring>(container.get_child(0));
result.text = get_variant<Glib::ustring>(container.get_child(2));
auto description = get_variant<Glib::ustring>(container.get_child(3));
if (!description.empty()) {
result.text = fmt::format("<b>{}</b>\n{}", result.text, description);
}
return result;
}
void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) {
try {
spdlog::trace("Set tray item property: {}.{} = {}", id.empty() ? bus_name : id, name, value);
@ -102,10 +128,11 @@ void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) {
id = get_variant<std::string>(value);
} else if (name == "Title") {
title = get_variant<std::string>(value);
if (tooltip.text.empty()) {
event_box.set_tooltip_markup(title);
}
} else if (name == "Status") {
status = get_variant<std::string>(value);
} else if (name == "WindowId") {
window_id = get_variant<int32_t>(value);
setStatus(get_variant<Glib::ustring>(value));
} else if (name == "IconName") {
icon_name = get_variant<std::string>(value);
} else if (name == "IconPixmap") {
@ -121,7 +148,10 @@ void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) {
} else if (name == "AttentionMovieName") {
attention_movie_name = get_variant<std::string>(value);
} else if (name == "ToolTip") {
// TODO: tooltip
tooltip = get_variant<ToolTip>(value);
if (!tooltip.text.empty()) {
event_box.set_tooltip_markup(tooltip.text);
}
} else if (name == "IconThemePath") {
icon_theme_path = get_variant<std::string>(value);
if (!icon_theme_path.empty()) {
@ -148,9 +178,22 @@ void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) {
}
}
void Item::getUpdatedProperties() {
update_pending_ = false;
void Item::setStatus(const Glib::ustring& value) {
Glib::ustring lower = value.lowercase();
event_box.set_visible(show_passive_ || lower.compare("passive") != 0);
auto style = event_box.get_style_context();
for (const auto& class_name : style->list_classes()) {
style->remove_class(class_name);
}
if (lower.compare("needsattention") == 0) {
// convert status to dash-case for CSS
lower = "needs-attention";
}
style->add_class(lower);
}
void Item::getUpdatedProperties() {
auto params = Glib::VariantContainerBase::create_tuple(
{Glib::Variant<Glib::ustring>::create(SNI_INTERFACE_NAME)});
proxy_->call("org.freedesktop.DBus.Properties.GetAll",
@ -167,33 +210,48 @@ void Item::processUpdatedProperties(Glib::RefPtr<Gio::AsyncResult>& _result) {
auto properties = properties_variant.get();
for (const auto& [name, value] : properties) {
Glib::VariantBase old_value;
proxy_->get_cached_property(old_value, name);
if (!old_value || !value.equal(old_value)) {
proxy_->set_cached_property(name, value);
if (update_pending_.count(name.raw())) {
setProperty(name, const_cast<Glib::VariantBase&>(value));
}
}
this->updateImage();
// this->event_box.set_tooltip_text(this->title);
} catch (const Glib::Error& err) {
spdlog::warn("Failed to update properties: {}", err.what());
} catch (const std::exception& err) {
spdlog::warn("Failed to update properties: {}", err.what());
}
update_pending_.clear();
}
/**
* Mapping from a signal name to a set of possibly changed properties.
* Commented signals are not handled by the tray module at the moment.
*/
static const std::map<std::string_view, std::set<std::string_view>> signal2props = {
{"NewTitle", {"Title"}},
{"NewIcon", {"IconName", "IconPixmap"}},
// {"NewAttentionIcon", {"AttentionIconName", "AttentionIconPixmap", "AttentionMovieName"}},
// {"NewOverlayIcon", {"OverlayIconName", "OverlayIconPixmap"}},
{"NewIconThemePath", {"IconThemePath"}},
{"NewToolTip", {"ToolTip"}},
{"NewStatus", {"Status"}},
// {"XAyatanaNewLabel", {"XAyatanaLabel"}},
};
void Item::onSignal(const Glib::ustring& sender_name, const Glib::ustring& signal_name,
const Glib::VariantContainerBase& arguments) {
spdlog::trace("Tray item '{}' got signal {}", id, signal_name);
if (!update_pending_ && signal_name.compare(0, 3, "New") == 0) {
/* Debounce signals and schedule update of all properties.
* Based on behavior of Plasma dataengine for StatusNotifierItem.
*/
update_pending_ = true;
Glib::signal_timeout().connect_once(sigc::mem_fun(*this, &Item::getUpdatedProperties),
UPDATE_DEBOUNCE_TIME);
auto changed = signal2props.find(signal_name.raw());
if (changed != signal2props.end()) {
if (update_pending_.empty()) {
/* Debounce signals and schedule update of all properties.
* Based on behavior of Plasma dataengine for StatusNotifierItem.
*/
Glib::signal_timeout().connect_once(sigc::mem_fun(*this, &Item::getUpdatedProperties),
UPDATE_DEBOUNCE_TIME);
}
update_pending_.insert(changed->second.begin(), changed->second.end());
}
}
@ -252,35 +310,41 @@ Glib::RefPtr<Gdk::Pixbuf> Item::extractPixBuf(GVariant* variant) {
}
void Item::updateImage() {
image.set_from_icon_name("image-missing", Gtk::ICON_SIZE_MENU);
image.set_pixel_size(icon_size);
if (!icon_name.empty()) {
try {
// Try to find icons specified by path and filename
auto pixbuf = getIconPixbuf();
auto scaled_icon_size = getScaledIconSize();
if (!pixbuf) {
pixbuf = getIconByName("image-missing", getScaledIconSize());
}
// If the loaded icon is not square, assume that the icon height should match the
// requested icon size, but the width is allowed to be different. As such, if the
// height of the image does not match the requested icon size, resize the icon such that
// the aspect ratio is maintained, but the height matches the requested icon size.
if (pixbuf->get_height() != scaled_icon_size) {
int width = scaled_icon_size * pixbuf->get_width() / pixbuf->get_height();
pixbuf = pixbuf->scale_simple(width, scaled_icon_size, Gdk::InterpType::INTERP_BILINEAR);
}
auto surface = Gdk::Cairo::create_surface_from_pixbuf(pixbuf, 0, image.get_window());
image.set(surface);
}
Glib::RefPtr<Gdk::Pixbuf> Item::getIconPixbuf() {
try {
if (!icon_name.empty()) {
std::ifstream temp(icon_name);
if (temp.is_open()) {
auto pixbuf = Gdk::Pixbuf::create_from_file(icon_name);
if (pixbuf->gobj() != nullptr) {
// An icon specified by path and filename may be the wrong size for
// the tray
// Keep the aspect ratio and scale to make the height equal to icon_size
// If people have non square icons, assume they want it to grow in width not height
int width = icon_size * pixbuf->get_width() / pixbuf->get_height();
pixbuf = pixbuf->scale_simple(width, icon_size, Gdk::InterpType::INTERP_BILINEAR);
image.set(pixbuf);
}
} else {
image.set(getIconByName(icon_name, icon_size));
return Gdk::Pixbuf::create_from_file(icon_name);
}
} catch (Glib::Error& e) {
spdlog::error("Item '{}': {}", id, static_cast<std::string>(e.what()));
return getIconByName(icon_name, getScaledIconSize());
} else if (icon_pixmap) {
return icon_pixmap;
}
} else if (icon_pixmap) {
// An icon extracted may be the wrong size for the tray
icon_pixmap = icon_pixmap->scale_simple(icon_size, icon_size, Gdk::InterpType::INTERP_BILINEAR);
image.set(icon_pixmap);
}
} catch (Glib::Error& e) {
spdlog::error("Item '{}': {}", id, static_cast<std::string>(e.what()));
}
return getIconByName("image-missing", getScaledIconSize());
}
Glib::RefPtr<Gdk::Pixbuf> Item::getIconByName(const std::string& name, int request_size) {
@ -315,6 +379,11 @@ Glib::RefPtr<Gdk::Pixbuf> Item::getIconByName(const std::string& name, int reque
name.c_str(), tmp_size, Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE);
}
double Item::getScaledIconSize() {
// apply the scale factor from the Gtk window to the requested icon size
return icon_size * image.get_scale_factor();
}
void Item::onMenuDestroyed(Item* self, GObject* old_menu_pointer) {
if (old_menu_pointer == reinterpret_cast<GObject*>(self->dbus_menu)) {
self->gtk_menu = nullptr;
@ -360,4 +429,52 @@ bool Item::handleClick(GdkEventButton* const& ev) {
return false;
}
bool Item::handleScroll(GdkEventScroll* const& ev) {
int dx = 0, dy = 0;
switch (ev->direction) {
case GDK_SCROLL_UP:
dy = -1;
break;
case GDK_SCROLL_DOWN:
dy = 1;
break;
case GDK_SCROLL_LEFT:
dx = -1;
break;
case GDK_SCROLL_RIGHT:
dx = 1;
break;
case GDK_SCROLL_SMOOTH:
distance_scrolled_x_ += ev->delta_x;
distance_scrolled_y_ += ev->delta_y;
// check against the configured threshold and ensure that the absolute value >= 1
if (distance_scrolled_x_ > scroll_threshold_) {
dx = (int)lround(std::max(distance_scrolled_x_, 1.0));
distance_scrolled_x_ = 0;
} else if (distance_scrolled_x_ < -scroll_threshold_) {
dx = (int)lround(std::min(distance_scrolled_x_, -1.0));
distance_scrolled_x_ = 0;
}
if (distance_scrolled_y_ > scroll_threshold_) {
dy = (int)lround(std::max(distance_scrolled_y_, 1.0));
distance_scrolled_y_ = 0;
} else if (distance_scrolled_y_ < -scroll_threshold_) {
dy = (int)lround(std::min(distance_scrolled_y_, -1.0));
distance_scrolled_y_ = 0;
}
break;
}
if (dx != 0) {
auto parameters = Glib::VariantContainerBase::create_tuple(
{Glib::Variant<int>::create(dx), Glib::Variant<Glib::ustring>::create("horizontal")});
proxy_->call("Scroll", parameters);
}
if (dy != 0) {
auto parameters = Glib::VariantContainerBase::create_tuple(
{Glib::Variant<int>::create(dy), Glib::Variant<Glib::ustring>::create("vertical")});
proxy_->call("Scroll", parameters);
}
return true;
}
} // namespace waybar::modules::SNI

View File

@ -1,10 +1,28 @@
#include "modules/sway/language.hpp"
#include <fmt/core.h>
#include <json/json.h>
#include <spdlog/spdlog.h>
#include <xkbcommon/xkbregistry.h>
#include <cstring>
#include <string>
#include <vector>
#include "modules/sway/ipc/ipc.hpp"
#include "util/string.hpp"
namespace waybar::modules::sway {
const std::string Language::XKB_LAYOUT_NAMES_KEY = "xkb_layout_names";
const std::string Language::XKB_ACTIVE_LAYOUT_NAME_KEY = "xkb_active_layout_name";
Language::Language(const std::string& id, const Json::Value& config)
: ALabel(config, "language", id, "{}", 0, true) {
is_variant_displayed = format_.find("{variant}") != std::string::npos;
if (config.isMember("tooltip-format")) {
tooltip_format_ = config["tooltip-format"].asString();
}
ipc_.subscribe(R"(["input"])");
ipc_.signal_event.connect(sigc::mem_fun(*this, &Language::onEvent));
ipc_.signal_cmd.connect(sigc::mem_fun(*this, &Language::onCmd));
@ -21,18 +39,31 @@ Language::Language(const std::string& id, const Json::Value& config)
}
void Language::onCmd(const struct Ipc::ipc_response& res) {
if (res.type != static_cast<uint32_t>(IPC_GET_INPUTS)) {
return;
}
try {
auto payload = parser_.parse(res.payload);
//Display current layout of a device with a maximum count of layouts, expecting that all will be OK
Json::Value::ArrayIndex maxId = 0, max = 0;
for(Json::Value::ArrayIndex i = 0; i < payload.size(); i++) {
if(payload[i]["xkb_layout_names"].size() > max) {
max = payload[i]["xkb_layout_names"].size();
maxId = i;
std::lock_guard<std::mutex> lock(mutex_);
auto payload = parser_.parse(res.payload);
std::vector<std::string> used_layouts;
// Display current layout of a device with a maximum count of layouts, expecting that all will
// be OK
Json::ArrayIndex max_id = 0, max = 0;
for (Json::ArrayIndex i = 0; i < payload.size(); i++) {
auto size = payload[i][XKB_LAYOUT_NAMES_KEY].size();
if (size > max) {
max = size;
max_id = i;
}
}
auto layout_name = payload[maxId]["xkb_active_layout_name"].asString().substr(0,2);
lang_ = Glib::Markup::escape_text(layout_name);
for (const auto& layout : payload[max_id][XKB_LAYOUT_NAMES_KEY]) {
used_layouts.push_back(layout.asString());
}
init_layouts_map(used_layouts);
set_current_layout(payload[max_id][XKB_ACTIVE_LAYOUT_NAME_KEY].asString());
dp.emit();
} catch (const std::exception& e) {
spdlog::error("Language: {}", e.what());
@ -40,12 +71,15 @@ void Language::onCmd(const struct Ipc::ipc_response& res) {
}
void Language::onEvent(const struct Ipc::ipc_response& res) {
if (res.type != static_cast<uint32_t>(IPC_EVENT_INPUT)) {
return;
}
try {
std::lock_guard<std::mutex> lock(mutex_);
auto payload = parser_.parse(res.payload)["input"];
auto payload = parser_.parse(res.payload)["input"];
if (payload["type"].asString() == "keyboard") {
auto layout_name = payload["xkb_active_layout_name"].asString().substr(0,2);
lang_ = Glib::Markup::escape_text(layout_name);
set_current_layout(payload[XKB_ACTIVE_LAYOUT_NAME_KEY].asString());
}
dp.emit();
} catch (const std::exception& e) {
@ -54,17 +88,102 @@ void Language::onEvent(const struct Ipc::ipc_response& res) {
}
auto Language::update() -> void {
if (lang_.empty()) {
event_box_.hide();
} else {
label_.set_markup(fmt::format(format_, lang_));
if (tooltipEnabled()) {
label_.set_tooltip_text(lang_);
}
event_box_.show();
auto display_layout = trim(fmt::format(format_,
fmt::arg("short", layout_.short_name),
fmt::arg("long", layout_.full_name),
fmt::arg("variant", layout_.variant)));
label_.set_markup(display_layout);
if (tooltipEnabled()) {
if (tooltip_format_ != "") {
auto tooltip_display_layout = trim(fmt::format(tooltip_format_,
fmt::arg("short", layout_.short_name),
fmt::arg("long", layout_.full_name),
fmt::arg("variant", layout_.variant)));
label_.set_tooltip_markup(tooltip_display_layout);
} else {
label_.set_tooltip_markup(display_layout);
}
}
event_box_.show();
// Call parent update
ALabel::update();
}
auto Language::set_current_layout(std::string current_layout) -> void {
layout_ = layouts_map_[current_layout];
}
auto Language::init_layouts_map(const std::vector<std::string>& used_layouts) -> void {
std::map<std::string, std::vector<Layout*>> found_by_short_names;
auto layout = xkb_context_.next_layout();
for (; layout != nullptr; layout = xkb_context_.next_layout()) {
if (std::find(used_layouts.begin(), used_layouts.end(), layout->full_name) ==
used_layouts.end()) {
continue;
}
if (!is_variant_displayed) {
auto short_name = layout->short_name;
if (found_by_short_names.count(short_name) > 0) {
found_by_short_names[short_name].push_back(layout);
} else {
found_by_short_names[short_name] = {layout};
}
}
layouts_map_.emplace(layout->full_name, *layout);
}
if (is_variant_displayed || found_by_short_names.size() == 0) {
return;
}
std::map<std::string, int> short_name_to_number_map;
for (const auto& used_layout_name : used_layouts) {
auto used_layout = &layouts_map_.find(used_layout_name)->second;
auto layouts_with_same_name_list = found_by_short_names[used_layout->short_name];
spdlog::info("SIZE: " + std::to_string(layouts_with_same_name_list.size()));
if (layouts_with_same_name_list.size() < 2) {
continue;
}
if (short_name_to_number_map.count(used_layout->short_name) == 0) {
short_name_to_number_map[used_layout->short_name] = 1;
}
used_layout->short_name =
used_layout->short_name + std::to_string(short_name_to_number_map[used_layout->short_name]++);
}
}
Language::XKBContext::XKBContext() {
context_ = rxkb_context_new(RXKB_CONTEXT_NO_DEFAULT_INCLUDES);
rxkb_context_include_path_append_default(context_);
rxkb_context_parse_default_ruleset(context_);
}
auto Language::XKBContext::next_layout() -> Layout* {
if (xkb_layout_ == nullptr) {
xkb_layout_ = rxkb_layout_first(context_);
} else {
xkb_layout_ = rxkb_layout_next(xkb_layout_);
}
if (xkb_layout_ == nullptr) {
return nullptr;
}
auto description = std::string(rxkb_layout_get_description(xkb_layout_));
auto name = std::string(rxkb_layout_get_name(xkb_layout_));
auto variant_ = rxkb_layout_get_variant(xkb_layout_);
std::string variant = variant_ == nullptr ? "" : std::string(variant_);
layout_ = new Layout{description, name, variant};
return layout_;
}
Language::XKBContext::~XKBContext() { rxkb_context_unref(context_); }
} // namespace waybar::modules::sway

View File

@ -1,5 +1,6 @@
#include "modules/sway/window.hpp"
#include <spdlog/spdlog.h>
#include <regex>
namespace waybar::modules::sway {
@ -56,7 +57,8 @@ auto Window::update() -> void {
bar_.window.get_style_context()->remove_class("solo");
bar_.window.get_style_context()->remove_class("empty");
}
label_.set_markup(fmt::format(format_, window_));
label_.set_markup(fmt::format(format_, fmt::arg("title", rewriteTitle(window_)),
fmt::arg("app_id", app_id_)));
if (tooltipEnabled()) {
label_.set_tooltip_text(window_);
}
@ -64,29 +66,51 @@ auto Window::update() -> void {
ALabel::update();
}
std::tuple<std::size_t, int, std::string, std::string> Window::getFocusedNode(
const Json::Value& nodes, std::string& output) {
for (auto const& node : nodes) {
int leafNodesInWorkspace(const Json::Value& node) {
auto const& nodes = node["nodes"];
if(nodes.empty()) {
if(node["type"] == "workspace")
return 0;
else
return 1;
}
int sum = 0;
for(auto const& node : nodes)
sum += leafNodesInWorkspace(node);
return sum;
}
std::tuple<std::size_t, int, std::string, std::string> gfnWithWorkspace(
const Json::Value& nodes, std::string& output, const Json::Value& config_,
const Bar& bar_, Json::Value& parentWorkspace) {
for(auto const& node : nodes) {
if (node["output"].isString()) {
output = node["output"].asString();
}
// found node
if (node["focused"].asBool() && (node["type"] == "con" || node["type"] == "floating_con")) {
if ((!config_["all-outputs"].asBool() && output == bar_.output->name) ||
config_["all-outputs"].asBool()) {
auto app_id = node["app_id"].isString() ? node["app_id"].asString()
: node["window_properties"]["instance"].asString();
return {nodes.size(),
node["id"].asInt(),
Glib::Markup::escape_text(node["name"].asString()),
app_id};
: node["window_properties"]["instance"].asString();
int nb = node.size();
if(parentWorkspace != 0)
nb = leafNodesInWorkspace(parentWorkspace);
return {nb,
node["id"].asInt(),
Glib::Markup::escape_text(node["name"].asString()),
app_id};
}
}
auto [nb, id, name, app_id] = getFocusedNode(node["nodes"], output);
// iterate
if(node["type"] == "workspace")
parentWorkspace = node;
auto [nb, id, name, app_id] = gfnWithWorkspace(node["nodes"], output, config_, bar_, parentWorkspace);
if (id > -1 && !name.empty()) {
return {nb, id, name, app_id};
}
// Search for floating node
std::tie(nb, id, name, app_id) = getFocusedNode(node["floating_nodes"], output);
std::tie(nb, id, name, app_id) = gfnWithWorkspace(node["floating_nodes"], output, config_, bar_, parentWorkspace);
if (id > -1 && !name.empty()) {
return {nb, id, name, app_id};
}
@ -94,6 +118,12 @@ std::tuple<std::size_t, int, std::string, std::string> Window::getFocusedNode(
return {0, -1, "", ""};
}
std::tuple<std::size_t, int, std::string, std::string> Window::getFocusedNode(
const Json::Value& nodes, std::string& output) {
Json::Value placeholder = 0;
return gfnWithWorkspace(nodes, output, config_, bar_, placeholder);
}
void Window::getTree() {
try {
ipc_.sendCmd(IPC_GET_TREE);
@ -102,4 +132,30 @@ void Window::getTree() {
}
}
std::string Window::rewriteTitle(const std::string& title) {
const auto& rules = config_["rewrite"];
if (!rules.isObject()) {
return title;
}
std::string res = title;
for (auto it = rules.begin(); it != rules.end(); ++it) {
if (it.key().isString() && it->isString()) {
try {
// malformated regexes will cause an exception.
// in this case, log error and try the next rule.
const std::regex rule{it.key().asString()};
if (std::regex_match(title, rule)) {
res = std::regex_replace(res, rule, it->asString());
}
} catch (const std::regex_error& e) {
spdlog::error("Invalid rule {}: {}", it.key().asString(), e.what());
}
}
}
return res;
}
} // namespace waybar::modules::sway

View File

@ -257,11 +257,19 @@ Gtk::Button &Workspaces::addButton(const Json::Value &node) {
ipc_.sendCmd(
IPC_COMMAND,
fmt::format(workspace_switch_cmd_ + "; move workspace to output \"{}\"; " + workspace_switch_cmd_,
"--no-auto-back-and-forth",
node["name"].asString(),
node["target_output"].asString(),
"--no-auto-back-and-forth",
node["name"].asString()));
} else {
ipc_.sendCmd(IPC_COMMAND, fmt::format(workspace_switch_cmd_, node["name"].asString()));
ipc_.sendCmd(
IPC_COMMAND,
fmt::format("workspace {} \"{}\"",
config_["disable-auto-back-and-forth"].asBool()
? "--no-auto-back-and-forth"
: "",
node["name"].asString()));
}
} catch (const std::exception &e) {
spdlog::error("Workspaces: {}", e.what());
@ -322,7 +330,9 @@ bool Workspaces::handleScroll(GdkEventScroll *e) {
}
}
try {
ipc_.sendCmd(IPC_COMMAND, fmt::format(workspace_switch_cmd_, name));
ipc_.sendCmd(
IPC_COMMAND,
fmt::format(workspace_switch_cmd_, "--no-auto-back-and-forth", name));
} catch (const std::exception &e) {
spdlog::error("Workspaces: {}", e.what());
}

View File

@ -40,6 +40,16 @@ auto waybar::modules::Temperature::update() -> void {
fmt::arg("temperatureF", temperature_f),
fmt::arg("temperatureK", temperature_k),
fmt::arg("icon", getIcon(temperature_c, "", max_temp))));
if (tooltipEnabled()) {
std::string tooltip_format = "{temperatureC}°C";
if (config_["tooltip-format"].isString()) {
tooltip_format = config_["tooltip-format"].asString();
}
label_.set_tooltip_text(fmt::format(tooltip_format,
fmt::arg("temperatureC", temperature_c),
fmt::arg("temperatureF", temperature_f),
fmt::arg("temperatureK", temperature_k)));
}
// Call parent update
ALabel::update();
}

View File

@ -1,5 +1,7 @@
#include "modules/wlr/taskbar.hpp"
#include "glibmm/error.h"
#include "glibmm/fileutils.h"
#include "glibmm/refptr.h"
#include "util/format.hpp"
@ -15,6 +17,7 @@
#include <gtkmm/icontheme.h>
#include <giomm/desktopappinfo.h>
#include <gio/gdesktopappinfo.h>
#include <spdlog/spdlog.h>
@ -26,19 +29,19 @@ const std::string WHITESPACE = " \n\r\t\f\v";
static std::string ltrim(const std::string& s)
{
size_t start = s.find_first_not_of(WHITESPACE);
return (start == std::string::npos) ? "" : s.substr(start);
size_t start = s.find_first_not_of(WHITESPACE);
return (start == std::string::npos) ? "" : s.substr(start);
}
static std::string rtrim(const std::string& s)
{
size_t end = s.find_last_not_of(WHITESPACE);
return (end == std::string::npos) ? "" : s.substr(0, end + 1);
size_t end = s.find_last_not_of(WHITESPACE);
return (end == std::string::npos) ? "" : s.substr(0, end + 1);
}
static std::string trim(const std::string& s)
{
return rtrim(ltrim(s));
return rtrim(ltrim(s));
}
@ -64,12 +67,25 @@ static std::vector<std::string> search_prefix()
} while(end != std::string::npos);
}
std::string home_dir = std::getenv("HOME");
prefixes.push_back(home_dir + "/.local/share/");
for (auto& p : prefixes)
spdlog::debug("Using 'desktop' search path prefix: {}", p);
return prefixes;
}
static Glib::RefPtr<Gdk::Pixbuf> load_icon_from_file(std::string icon_path, int size)
{
try {
auto pb = Gdk::Pixbuf::create_from_file(icon_path, size, size);
return pb;
} catch(...) {
return {};
}
}
/* Method 1 - get the correct icon name from the desktop file */
static std::string get_from_desktop_app_info(const std::string &app_id)
{
@ -103,14 +119,41 @@ static std::string get_from_desktop_app_info(const std::string &app_id)
/* Method 2 - use the app_id and check whether there is an icon with this name in the icon theme */
static std::string get_from_icon_theme(const Glib::RefPtr<Gtk::IconTheme>& icon_theme,
const std::string &app_id) {
const std::string &app_id)
{
if (icon_theme->lookup_icon(app_id, 24))
return app_id;
return "";
}
/* Method 3 - as last resort perform a search for most appropriate desktop info file */
static std::string get_from_desktop_app_info_search(const std::string &app_id)
{
std::string desktop_file = "";
gchar*** desktop_list = g_desktop_app_info_search(app_id.c_str());
if (desktop_list != nullptr && desktop_list[0] != nullptr) {
for (size_t i=0; desktop_list[0][i]; i++) {
if (desktop_file == "") {
desktop_file = desktop_list[0][i];
} else {
auto tmp_info = Gio::DesktopAppInfo::create(desktop_list[0][i]);
auto startup_class = tmp_info->get_startup_wm_class();
if (startup_class == app_id) {
desktop_file = desktop_list[0][i];
break;
}
}
}
g_strfreev(desktop_list[0]);
}
g_free(desktop_list);
return get_from_desktop_app_info(desktop_file);
}
static bool image_load_icon(Gtk::Image& image, const Glib::RefPtr<Gtk::IconTheme>& icon_theme,
const std::string &app_id_list, int size)
{
@ -122,6 +165,10 @@ static bool image_load_icon(Gtk::Image& image, const Glib::RefPtr<Gtk::IconTheme
* send a single app-id, but in any case this works fine */
while (stream >> app_id)
{
size_t start = 0, end = app_id.size();
start = app_id.rfind(".", end);
std::string app_name = app_id.substr(start+1, app_id.size());
auto lower_app_id = app_id;
std::transform(lower_app_id.begin(), lower_app_id.end(), lower_app_id.begin(),
[](char c){ return std::tolower(c); });
@ -130,15 +177,31 @@ static bool image_load_icon(Gtk::Image& image, const Glib::RefPtr<Gtk::IconTheme
if (icon_name.empty())
icon_name = get_from_icon_theme(icon_theme, lower_app_id);
if (icon_name.empty())
icon_name = get_from_icon_theme(icon_theme, app_name);
if (icon_name.empty())
icon_name = get_from_desktop_app_info(app_id);
if (icon_name.empty())
icon_name = get_from_desktop_app_info(lower_app_id);
if (icon_name.empty())
icon_name = get_from_desktop_app_info(app_name);
if (icon_name.empty())
icon_name = get_from_desktop_app_info_search(app_id);
if (icon_name.empty())
continue;
icon_name = "unknown";
Glib::RefPtr<Gdk::Pixbuf> pixbuf;
try {
pixbuf = icon_theme->load_icon(icon_name, size, Gtk::ICON_LOOKUP_FORCE_SIZE);
} catch(...) {
if (Glib::file_test(icon_name, Glib::FILE_TEST_EXISTS))
pixbuf = load_icon_from_file(icon_name, size);
else
pixbuf = {};
}
auto pixbuf = icon_theme->load_icon(icon_name, size, Gtk::ICON_LOOKUP_FORCE_SIZE);
if (pixbuf) {
image.set(pixbuf);
found = true;
@ -214,7 +277,7 @@ Task::Task(const waybar::Bar &bar, const Json::Value &config, Taskbar *tbar,
bar_{bar}, config_{config}, tbar_{tbar}, handle_{tl_handle}, seat_{seat},
id_{global_id++},
content_{bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0},
button_visible_{false}
button_visible_{false}, ignored_{false}
{
zwlr_foreign_toplevel_handle_v1_add_listener(handle_, &toplevel_handle_impl, this);
@ -320,6 +383,21 @@ void Task::handle_app_id(const char *app_id)
{
app_id_ = app_id;
if (tbar_->ignore_list().count(app_id)) {
ignored_ = true;
if (button_visible_) {
auto output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj());
handle_output_leave(output);
}
} else {
bool is_was_ignored = ignored_;
ignored_ = false;
if (is_was_ignored) {
auto output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj());
handle_output_enter(output);
}
}
if (!with_icon_)
return;
@ -342,6 +420,11 @@ void Task::handle_output_enter(struct wl_output *output)
{
spdlog::debug("{} entered output {}", repr(), (void*)output);
if (ignored_) {
spdlog::debug("{} is ignored", repr());
return;
}
if (!button_visible_ && (tbar_->all_outputs() || tbar_->show_output(output))) {
/* The task entered the output of the current bar make the button visible */
tbar_->add_button(button_);
@ -367,16 +450,16 @@ void Task::handle_output_leave(struct wl_output *output)
void Task::handle_state(struct wl_array *state)
{
state_ = 0;
for (auto* entry = static_cast<uint32_t*>(state->data);
entry < static_cast<uint32_t*>(state->data) + state->size;
entry++) {
if (*entry == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MAXIMIZED)
size_t size = state->size / sizeof(uint32_t);
for (size_t i = 0; i < size; ++i) {
auto entry = static_cast<uint32_t*>(state->data)[i];
if (entry == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MAXIMIZED)
state_ |= MAXIMIZED;
if (*entry == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MINIMIZED)
if (entry == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MINIMIZED)
state_ |= MINIMIZED;
if (*entry == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED)
if (entry == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED)
state_ |= ACTIVE;
if (*entry == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN)
if (entry == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN)
state_ |= FULLSCREEN;
}
}
@ -489,7 +572,7 @@ void Task::update()
fmt::arg("state", state_string()),
fmt::arg("short_state", state_string(true))
);
if (markup)
if (markup)
text_before_.set_markup(txt);
else
text_before_.set_label(txt);
@ -502,7 +585,7 @@ void Task::update()
fmt::arg("state", state_string()),
fmt::arg("short_state", state_string(true))
);
if (markup)
if (markup)
text_after_.set_markup(txt);
else
text_after_.set_label(txt);
@ -516,7 +599,7 @@ void Task::update()
fmt::arg("state", state_string()),
fmt::arg("short_state", state_string(true))
);
if (markup)
if (markup)
button_.set_tooltip_markup(txt);
else
button_.set_tooltip_text(txt);
@ -584,7 +667,7 @@ static const wl_registry_listener registry_listener_impl = {
.global_remove = handle_global_remove
};
Taskbar::Taskbar(const std::string &id, const waybar::Bar &bar, const Json::Value &config)
Taskbar::Taskbar(const std::string &id, const waybar::Bar &bar, const Json::Value &config)
: waybar::AModule(config, "taskbar", id, false, false),
bar_(bar),
box_{bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0},
@ -631,14 +714,34 @@ Taskbar::Taskbar(const std::string &id, const waybar::Bar &bar, const Json::Valu
icon_themes_.push_back(it);
}
// Load ignore-list
if (config_["ignore-list"].isArray()) {
for (auto& app_name : config_["ignore-list"]) {
ignore_list_.emplace(app_name.asString());
}
}
icon_themes_.push_back(Gtk::IconTheme::get_default());
}
Taskbar::~Taskbar()
{
if (manager_) {
zwlr_foreign_toplevel_manager_v1_destroy(manager_);
manager_ = nullptr;
struct wl_display *display = Client::inst()->wl_display;
/*
* Send `stop` request and wait for one roundtrip.
* This is not quite correct as the protocol encourages us to wait for the .finished event,
* but it should work with wlroots foreign toplevel manager implementation.
*/
zwlr_foreign_toplevel_manager_v1_stop(manager_);
wl_display_roundtrip(display);
if (manager_) {
spdlog::warn("Foreign toplevel manager destroyed before .finished event");
zwlr_foreign_toplevel_manager_v1_destroy(manager_);
manager_ = nullptr;
}
}
}
@ -754,5 +857,6 @@ std::vector<Glib::RefPtr<Gtk::IconTheme>> Taskbar::icon_themes() const
{
return icon_themes_;
}
const std::unordered_set<std::string> &Taskbar::ignore_list() const { return ignore_list_; }
} /* namespace waybar::modules::wlr */

View File

@ -19,60 +19,64 @@
#include "util/rfkill.hpp"
#include <fcntl.h>
#include <glibmm/main.h>
#include <linux/rfkill.h>
#include <poll.h>
#include <stdlib.h>
#include <spdlog/spdlog.h>
#include <unistd.h>
#include <cerrno>
#include <cstring>
#include <stdexcept>
waybar::util::Rfkill::Rfkill(const enum rfkill_type rfkill_type) : rfkill_type_(rfkill_type) {}
void waybar::util::Rfkill::waitForEvent() {
struct rfkill_event event;
struct pollfd p;
ssize_t len;
int fd, n;
fd = open("/dev/rfkill", O_RDONLY);
if (fd < 0) {
throw std::runtime_error("Can't open RFKILL control device");
waybar::util::Rfkill::Rfkill(const enum rfkill_type rfkill_type) : rfkill_type_(rfkill_type) {
fd_ = open("/dev/rfkill", O_RDONLY);
if (fd_ < 0) {
spdlog::error("Can't open RFKILL control device");
return;
}
memset(&p, 0, sizeof(p));
p.fd = fd;
p.events = POLLIN | POLLHUP;
while (1) {
n = poll(&p, 1, -1);
if (n < 0) {
throw std::runtime_error("Failed to poll RFKILL control device");
break;
}
if (n == 0) continue;
len = read(fd, &event, sizeof(event));
if (len < 0) {
throw std::runtime_error("Reading of RFKILL events failed");
break;
}
if (len != RFKILL_EVENT_SIZE_V1) {
throw std::runtime_error("Wrong size of RFKILL event");
continue;
}
if (event.type == rfkill_type_ && event.op == RFKILL_OP_CHANGE) {
state_ = event.soft || event.hard;
break;
}
int rc = fcntl(fd_, F_SETFL, O_NONBLOCK);
if (rc < 0) {
spdlog::error("Can't set RFKILL control device to non-blocking: {}", errno);
close(fd_);
fd_ = -1;
return;
}
Glib::signal_io().connect(
sigc::mem_fun(*this, &Rfkill::on_event), fd_, Glib::IO_IN | Glib::IO_ERR | Glib::IO_HUP);
}
close(fd);
waybar::util::Rfkill::~Rfkill() {
if (fd_ >= 0) {
close(fd_);
}
}
bool waybar::util::Rfkill::on_event(Glib::IOCondition cond) {
if (cond & Glib::IO_IN) {
struct rfkill_event event;
ssize_t len;
len = read(fd_, &event, sizeof(event));
if (len < 0) {
if (errno == EAGAIN) {
return true;
}
spdlog::error("Reading of RFKILL events failed: {}", errno);
return false;
}
if (len < RFKILL_EVENT_SIZE_V1) {
spdlog::error("Wrong size of RFKILL event: {} < {}", len, RFKILL_EVENT_SIZE_V1);
return true;
}
if (event.type == rfkill_type_ && (event.op == RFKILL_OP_ADD || event.op == RFKILL_OP_CHANGE)) {
state_ = event.soft || event.hard;
on_update.emit(event);
}
return true;
} else {
spdlog::error("Failed to poll RFKILL control device");
return false;
}
}
bool waybar::util::Rfkill::getState() const { return state_; }

View File

@ -0,0 +1,9 @@
#include "util/ustring_clen.hpp"
int ustring_clen(const Glib::ustring &str){
int total = 0;
for (auto i = str.begin(); i != str.end(); ++i) {
total += g_unichar_iswide(*i) + 1;
}
return total;
}

View File

@ -1,13 +1,11 @@
[wrap-file]
directory = spdlog-1.8.1
source_url = https://github.com/gabime/spdlog/archive/v1.8.1.tar.gz
source_filename = v1.8.1.tar.gz
source_hash = 5197b3147cfcfaa67dd564db7b878e4a4b3d9f3443801722b3915cdeced656cb
patch_url = https://github.com/mesonbuild/spdlog/releases/download/1.8.1-1/spdlog.zip
patch_filename = spdlog-1.8.1-1-wrap.zip
patch_hash = 76844292a8e912aec78450618271a311841b33b17000988f215ddd6c64dd71b3
directory = spdlog-1.8.5
source_url = https://github.com/gabime/spdlog/archive/v1.8.5.tar.gz
source_filename = v1.8.5.tar.gz
source_hash = 944d0bd7c763ac721398dca2bb0f3b5ed16f67cef36810ede5061f35a543b4b8
patch_url = https://wrapdb.mesonbuild.com/v1/projects/spdlog/1.8.5/1/get_zip
patch_filename = spdlog-1.8.5-1-wrap.zip
patch_hash = 3c38f275d5792b1286391102594329e98b17737924b344f98312ab09929b74be
[provide]
spdlog = spdlog_dep