Compare commits

..

155 Commits

Author SHA1 Message Date
0405c00602 Merge branch 'master' of https://github.com/Alexays/Waybar
Some checks failed
clang-format / lint (push) Has been cancelled
freebsd / build (push) Has been cancelled
linux / build (c++20, alpine) (push) Has been cancelled
linux / build (c++20, archlinux) (push) Has been cancelled
linux / build (c++20, debian) (push) Has been cancelled
linux / build (c++20, fedora) (push) Has been cancelled
linux / build (c++20, gentoo) (push) Has been cancelled
linux / build (c++20, opensuse) (push) Has been cancelled
Nix-Tests / nix-flake-check (push) Has been cancelled
2026-04-01 14:57:33 -07:00
Alexis Rouillard
1e965ccce0 Merge pull request #4960 from keepo-dot/feature/8bit-hex-support
feat(client): add support for 8-bit hex color codes in CSS
2026-03-31 09:54:24 +02:00
Alexis Rouillard
b3f4281aa4 Merge pull request #4962 from B2krobbery/fix-dwl-global-leak
fix: prevent resource leak when rebinding dwl globals
2026-03-31 09:53:44 +02:00
Visal Vijay
3f69bacff0 fix: prevent resource leak when rebinding dwl globals 2026-03-29 22:12:16 +05:30
Keepo
a9aab4e356 fix: type consistency 2026-03-28 23:44:41 -04:00
Keepo
72d6a51fb7 fix: fix compile/run bugs from trying to be safer. rip 2026-03-28 23:26:34 -04:00
Keepo
3533265675 fix: removed unnecessary function, update header file for signature change. 2026-03-28 23:02:16 -04:00
Keepo
937ef176ff fix: memory issues & duplicate file loads. 2026-03-28 22:53:01 -04:00
Keepo
3b512d1a2c feat(client): add support for 8-bit hex color codes in CSS
This allows users to use #RRGGBBAA format in their style.css.
The client now detects 8-bit hex codes, transforms them into
GTK-compatible rgba() syntax, and loads the modified data
into the CSS provider.

- Added utility to detect 8-bit hex patterns.
- Added transformation logic to convert hex alpha to decimal.
- Intercepted CSS loading in Client::setupCss to handle the conversion.
2026-03-28 21:11:15 -04:00
Alexis Rouillard
49460defdc Merge pull request #4953 from B2krobbery/revert-menu-actions
Fix unnecessary allocation in GTK callback
2026-03-25 22:57:40 +01:00
Visal Vijay
e55bff662e Remove unnecessary g_strdup in GTK callback 2026-03-25 21:45:14 +05:30
Alexis Rouillard
06c3d4393c Merge pull request #4943 from sgruendel/network_multi_monitor
Fix network bandwidth rate calculation for early updates
2026-03-25 12:30:17 +01:00
Alexis Rouillard
3bd46da7c3 Merge pull request #4945 from B2krobbery/clean-parentheses-fix
clarify logical condition in wlr taskbar module
2026-03-25 12:30:03 +01:00
Alexis Rouillard
e3174a0e3e Merge pull request #4948 from tsukasa-au/fix-network-selection
Network: Fix default interface selection.
2026-03-25 12:29:45 +01:00
Alexis Rouillard
be2ad4a858 Merge pull request #4946 from B2krobbery/fix-client-wayland-global-leak-clean
fix: prevent resource leak when rebinding Wayland globals
2026-03-23 13:25:39 +01:00
Greg Darke
b64265bdf7 Network: Fix default interface selection.
When an interface is not specified for the network module, we parse the
routing table to look for default routes.

We have defined a default route to:
- have a gateway specified, and
- have no destination specified, or have an all-zero destination.

Previous versions of Waybar had the second condition inverted,
causing it to incorrectly pick interfaces are used to route a
subnet/single host.

For example, with the following routing table, we should pick `eth0` to
show information about, not `wg0`.

```
ip -4 route
default via 192.168.252.1 dev eth0 proto dhcp src 192.168.252.200 metric 100
192.168.252.0/24 dev eth0 proto kernel scope link src 192.168.252.200 metric 100
192.168.2.254 via 192.168.1.1 dev wg0 proto static metric 50
192.168.1.0/24 dev wg0 proto static scope link metric 50
192.168.1.0/24 dev wg0 proto kernel scope link src 192.168.1.254 metric 50
```
2026-03-23 15:04:47 +11:00
Visal Vijay
1aa32def3b fix: prevent resource leak when rebinding Wayland globals 2026-03-22 15:41:50 +05:30
Visal Vijay
60c57b7195 clarify logical condition in wlr taskbar module 2026-03-22 11:40:44 +05:30
Stefan Gründel
dc31db6d0c fix: linting error 2026-03-21 18:38:33 +01:00
Stefan Gründel
c0c1a4223a Fix network bandwidth rate calculation for early updates 2026-03-21 13:17:06 +01:00
Alexis Rouillard
ab7bfdb297 Merge pull request #4937 from sjudin/group-add-start-expanded
Add start-expanded option to group
2026-03-20 08:21:26 +01:00
Alexis Rouillard
081fb73345 Merge pull request #4938 from jayshozie/hyprland/window/segfault-fix
fix(hyprland/window): Fix segfault caused by use-after-free
2026-03-20 08:20:41 +01:00
Alexis Rouillard
a144a3086e Merge pull request #4939 from BryceBeagle/removed-nerdfonts-codepoints
chore: Stop using deprecated/removed Nerdfonts codepoints
2026-03-20 08:20:20 +01:00
Jakob Sjudin
6afe108642 Update manpages 2026-03-20 08:15:23 +01:00
ignormies
a05c7bc28f chore: Stop using deprecated/removed Nerdfonts codepoints
These Nerdfonts codepoints were removed in [3.0.0](https://www.nerdfonts.com/releases#v3.0.0):

> **Breaking 2: Material Design Icons Codepoints**
>
> The old Material Design Icon codepoints are finally dropped. Due to an historic mistake we placed them in between some asiatic glyphs, breaking that script. Since v2.3.0 the (updated and expanded) Material Design Icons have new codepoints in the 5 digit region.
>
> - Dropped codepoints `F500`… and class names `nf-mdi-*`
> - New codepoints `F0001`… and class names `nf-md-*`
> - The whole discussions are here: https://github.com/ryanoasis/nerd-fonts/issues/365
> - A translation table is available here: https://github.com/ryanoasis/nerd-fonts/issues/1059#issuecomment-1404891287
> - There are tools out there that probably can update your configuration.

For the majority of the removed symbols, it was as easy as finding the
replacement with the exact same name. For example, `f76b` (`nf-mdi-format_header_2`)
became `f026c` (`nf-md-format_header_2`).

There was one symbol that was completely removed (it was removed from
Material Design): `f5fc` (`nf-mdi-camcorder_box`). I have substituted it
with `f03d` (`nf-fa-video_camera`) which is not Material Design, but the
closest icon I could find.

Here's some example output from [nerdfix](https://github.com/loichyan/nerdfix)
```
╭─(ignormies)(~/g/fork-waybar)  removed-nerdfonts-codepoints
╰──▪ nix-shell -p nerdfix --run "nerdfix check resources/config.jsonc"
 INFO Check input from 'resources/config.jsonc'
  ☞ Found obsolete icon U+F76B
     ╭─[resources/config.jsonc:131:27]
 130 │         "format": "{temperatureC}°C {icon}",
 131 │         "format-icons": ["", "", ""]
     ·                           ┬
     ·                           ╰── Icon 'mdi-format_header_2' is marked as obsolete
 132 │     },
     ╰────
  help: You could replace it with:
          1. 󰉬 U+F026C md-format_header_2
          2. 󰉫 U+F026B md-format_header_1
          3. 󰉭 U+F026D md-format_header_3
          4. 󰉮 U+F026E md-format_header_4

  ☞ Found obsolete icon U+F769
     ╭─[resources/config.jsonc:131:41]
 130 │         "format": "{temperatureC}°C {icon}",
 131 │         "format-icons": ["", "", ""]
     ·                                     ┬
     ·                                     ╰── Icon 'mdi-format_float_right' is marked as obsolete
 132 │     },
     ╰────
  help: You could replace it with:
          1. 󰉪 U+F026A md-format_float_right
          2. 󰉨 U+F0268 md-format_float_left
          3. 󰉩 U+F0269 md-format_float_none
          4. 󰉣 U+F0263 md-format_align_right

  ☞ Found obsolete icon U+F5E7
     ╭─[resources/config.jsonc:146:41]
 145 │         "format-full": "{capacity}% {icon}",
 146 │         "format-charging": "{capacity}% ",
     ·                                         ┬
     ·                                         ╰── Icon 'mdi-cached' is marked as obsolete
 147 │         "format-plugged": "{capacity}% ",
     ╰────
  help: You could replace it with:
          1. 󰃨 U+F00E8 md-cached

  ☞ Found obsolete icon U+F796
     ╭─[resources/config.jsonc:170:45]
 169 │         "format-wifi": "{essid} ({signalStrength}%) ",
 170 │         "format-ethernet": "{ipaddr}/{cidr} ",
     ·                                             ┬
     ·                                             ╰── Icon 'mdi-gamepad_variant' is marked as obsolete
 171 │         "tooltip-format": "{ifname} via {gwaddr} ",
     ╰────
  help: You could replace it with:
          1. 󰊗 U+F0297 md-gamepad_variant
          2. 󰺷 U+F0EB7 md-gamepad_variant_outline
          3. 󰑢 U+F0462 md-road_variant
          4. 󰉜 U+F025C md-food_variant

  ☞ Found obsolete icon U+F796
     ╭─[resources/config.jsonc:171:50]
 170 │         "format-ethernet": "{ipaddr}/{cidr} ",
 171 │         "tooltip-format": "{ifname} via {gwaddr} ",
     ·                                                  ┬
     ·                                                  ╰── Icon 'mdi-gamepad_variant' is marked as obsolete
 172 │         "format-linked": "{ifname} (No IP) ",
     ╰────
  help: You could replace it with:
          1. 󰊗 U+F0297 md-gamepad_variant
          2. 󰺷 U+F0EB7 md-gamepad_variant_outline
          3. 󰑢 U+F0462 md-road_variant
          4. 󰉜 U+F025C md-food_variant

  ☞ Found obsolete icon U+F796
     ╭─[resources/config.jsonc:172:44]
 171 │         "tooltip-format": "{ifname} via {gwaddr} ",
 172 │         "format-linked": "{ifname} (No IP) ",
     ·                                            ┬
     ·                                            ╰── Icon 'mdi-gamepad_variant' is marked as obsolete
 173 │         "format-disconnected": "Disconnected ⚠",
     ╰────
  help: You could replace it with:
          1. 󰊗 U+F0297 md-gamepad_variant
          2. 󰺷 U+F0EB7 md-gamepad_variant_outline
          3. 󰑢 U+F0462 md-road_variant
          4. 󰉜 U+F025C md-food_variant

  ☞ Found obsolete icon U+F6A9
     ╭─[resources/config.jsonc:180:36]
 179 │         "format-bluetooth": "{volume}% {icon} {format_source}",
 180 │         "format-bluetooth-muted": " {icon} {format_source}",
     ·                                    ┬
     ·                                    ╰── Icon 'mdi-cup' is marked as obsolete
 181 │         "format-muted": " {format_source}",
     ╰────
  help: You could replace it with:
          1. 󰆪 U+F01AA md-cup

  ☞ Found obsolete icon U+F6A9
     ╭─[resources/config.jsonc:181:26]
 180 │         "format-bluetooth-muted": " {icon} {format_source}",
 181 │         "format-muted": " {format_source}",
     ·                          ┬
     ·                          ╰── Icon 'mdi-cup' is marked as obsolete
 182 │         "format-source": "{volume}% ",
     ╰────
  help: You could replace it with:
          1. 󰆪 U+F01AA md-cup

  ☞ Found obsolete icon U+F590
     ╭─[resources/config.jsonc:186:28]
 185 │             "headphone": "",
 186 │             "hands-free": "",
     ·                            ┬
     ·                            ╰── Icon 'mdi-battery_unknown' is marked as obsolete
 187 │             "headset": "",
     ╰────
  help: You could replace it with:
          1. 󰂑 U+F0091 md-battery_unknown
          2. 󰥊 U+F094A md-battery_unknown_bluetooth
          3. 󱟞 U+F17DE md-battery_arrow_down
          4. 󰝐 U+F0750 md-microsoft_xbox_controller_battery_unknown

  ☞ Found obsolete icon U+F590
     ╭─[resources/config.jsonc:187:25]
 186 │             "hands-free": "",
 187 │             "headset": "",
     ·                         ┬
     ·                         ╰── Icon 'mdi-battery_unknown' is marked as obsolete
 188 │             "phone": "",
     ╰────
  help: You could replace it with:
          1. 󰂑 U+F0091 md-battery_unknown
          2. 󰥊 U+F094A md-battery_unknown_bluetooth
          3. 󱟞 U+F17DE md-battery_arrow_down
          4. 󰝐 U+F0750 md-microsoft_xbox_controller_battery_unknown
```
2026-03-19 17:39:57 -07:00
Emir Baha Yıldırım
83e1949dd8 fix(hyprland/window): Fix segfault caused by use-after-free
The window module registers itself with the Hyprland IPC singleton at
the start of its constructor, before calling update(). If update()
throws an exception (e.g. from an invalid format string), the object is
destroyed without the destructor running, leaving a dangling pointer in
the IPC callback list. When the IPC thread receives an event, it
attempts to call onEvent() on this invalid memory, causing a crash.

Moving the update() call before IPC registration ensures that any
initialization errors occur before the pointer is shared. If the
configuration is invalid, the module fails to construct and is
gracefully disabled by the factory without leaving a "landmine" in the
background IPC thread.

Fixes: #4923

Signed-off-by: Emir Baha Yıldırım <jayshozie@gmail.com>
2026-03-19 19:49:48 +03:00
Jakob Sjudin
50c1431348 Add start-expanded option to group 2026-03-19 15:36:32 +01:00
Alexis Rouillard
6cb68737e6 Merge pull request #4932 from LukashonakV/cavaBump
cava bump
2026-03-19 09:54:58 +01:00
Alexis Rouillard
311b0fb157 Merge pull request #4931 from khaneliman/cleanup
chore: remove heaptrack dump
2026-03-19 09:16:33 +01:00
Viktar Lukashonak
95f9922ccc cava bump 2026-03-19 11:03:08 +03:00
Austin Horstman
86234a8946 feat: gitignore heaptrack dumps
Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2026-03-19 00:29:01 -05:00
Austin Horstman
215c952137 chore: remove heaptrack dump
Accidentally committed...

Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2026-03-19 00:28:50 -05:00
Alexis Rouillard
b77b1818f6 Merge pull request #4925 from B2krobbery/fix-menu-actions-pointer
Fix menu-actions GTK callback pointer handling
2026-03-18 20:58:33 +01:00
Alexis Rouillard
069c8cfb66 Merge pull request #4929 from c4rlo/failed-units-tooltip
systemd-failed-units: add tooltip with list of failed units
2026-03-18 09:05:54 +01:00
Carlo Teubner
e7c077ab9a clang-format 2026-03-17 21:39:55 +00:00
Carlo Teubner
3b1262061d systemd-failed-units: tooltip w/ failed units list 2026-03-17 21:35:20 +00:00
Carlo Teubner
d046c19b85 systemd-failed-units: small tweaks
- Remove unneeded destructor impl
- Rename member variables for consistench with other files
- manpage wording fixes
- updateData(): small logic tweak
2026-03-17 21:34:59 +00:00
Alexis Rouillard
1019c9d2fe Merge pull request #4924 from B2krobbery/master
Add simple calendar tooltip example for clock module
2026-03-16 23:06:03 +01:00
Visal Vijay
196589cf32 Update ALabel.cpp 2026-03-17 01:17:08 +05:30
Visal Vijay
acf6f117ea Update ALabel.cpp 2026-03-17 01:13:14 +05:30
Visal Vijay
3cfb622660 Fix menu-actions GTK callback pointer handling 2026-03-16 23:45:28 +05:30
Visal Vijay
558c2753d7 Add simple calendar tooltip example for clock module 2026-03-16 21:04:59 +05:30
Alexis Rouillard
100349a5c7 Merge pull request #4910 from khaneliman/hyprland
fix(hyprland): misc hardening with ipc socket and events
2026-03-08 22:21:40 +01:00
Alexis Rouillard
8dfdf4d1fe Merge pull request #4913 from khaneliman/tray
fix(tray): complete attention/overlay implementation
2026-03-08 22:21:19 +01:00
Austin Horstman
8e2e437ec6 fix(sni): silence duplicate item registration warnings
Some tray items re-register the same bus name and object path during normal
operation. Treat that path as an idempotent registration instead of logging a
warning, while still completing the DBus method successfully.

Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2026-03-08 01:42:31 -06:00
Austin Horstman
f6d92fd708 fix(sni): render attention and overlay tray icon assets
Load attention and overlay pixmaps from item properties, watch the
corresponding update signals, and prefer attention artwork while an item is in
NeedsAttention state.

When an item only exports an attention movie asset, fall back to loading that
asset as a static pixbuf so the tray still shows the alert state.

Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2026-03-08 01:42:31 -06:00
Austin Horstman
78f6cde232 fix(sni): correct watcher host teardown signaling
Return the host registration method correctly on duplicate host registration
and emit HostUnregistered instead of HostRegistered when the last host
vanishes.

Also free the corresponding name watch once the tracked host/item disappears so
the watcher does not leak stale watch records.

Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2026-03-08 01:20:15 -06:00
Austin Horstman
2a748f1a56 fix(sni): delay tray item insertion until proxies are ready
Only add tray widgets after the SNI proxy has finished initializing and the
item has a valid id/category pair.

This also removes invalid items through the host teardown path, refreshes the
tray when item status changes, and avoids calling DBus methods through a null
proxy during early clicks or scroll events.

Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2026-03-08 01:20:15 -06:00
Austin Horstman
6317022304 fix(hyprland): guard malformed module events
The language and submap modules assumed their Hyprland payload delimiters were
always present. When that assumption is violated, the old code could perform
invalid iterator math or throw while slicing the event string.

Validate the expected separators up front and bail out with a warning when the
event is malformed so the modules degrade safely instead of crashing the update
path.

Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2026-03-07 21:06:30 -06:00
Austin Horstman
dd47a2b826 fix(hyprland/workspaces): stabilize reload and event handling
Hyprland workspace reloads could stack duplicate scroll-event connections,
causing a single wheel gesture to switch multiple workspaces after repeated
config reloads. The persistent-workspaces monitor-array form also created the
monitor name instead of the configured workspace name.

Disconnect and replace the scroll handler on reinit, fix the persistent
workspace name selection, normalize urgent-window address matching, and reject
malformed workspace payloads before they corrupt the local state machine.

Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2026-03-07 21:06:30 -06:00
Austin Horstman
b1a87f943c fix(hyprland/window): avoid stale state during IPC refresh
The window module re-entered the same shared_mutex while refreshing IPC state:
update() took the lock and then called queryActiveWorkspace(), which tried to
lock it again. That is undefined behavior for std::shared_mutex and could
manifest as a deadlock.

Remove the recursive lock path and reset the derived window state before each
IPC refresh. That keeps solo/floating/swallowing/fullscreen classes from
sticking around when the client lookup fails or a workspace becomes empty.

Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2026-03-07 21:06:30 -06:00
Austin Horstman
0a35b86e20 fix(hyprland/ipc): honor the requested instance signature
The Hyprland IPC helper cached the socket folder with the first instance
signature already appended, so later calls ignored their instanceSig argument
and always reused the first path. That made the helper violate its own API even
though most real Waybar sessions only talk to a single Hyprland instance.

Cache only the base socket directory and append the requested signature per
lookup. This fixes correctness for tests, nested or debug multi-instance
setups, and future code that needs to resolve a different signature, without
claiming support for one Waybar process managing multiple Hyprland sessions.

Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2026-03-07 21:06:30 -06:00
Alexis Rouillard
e425423648 Merge pull request #4911 from khaneliman/gtkmenu
fix(menu): keep popup menus alive after builder teardown
2026-03-07 17:46:27 +01:00
Austin Horstman
790101f824 chore: format
Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2026-03-06 18:49:02 -06:00
Austin Horstman
f48fce57dc fix(menu): keep popup menus alive after builder teardown
The popup menu was retrieved from GtkBuilder and stored in menu_, but the builder was unref'd immediately after construction. That left the later popup path operating on a builder-owned GtkMenu whose lifetime was no longer guaranteed, which matches the GTK_IS_WIDGET and GTK_IS_MENU assertions from the regression report.

Take an owned reference to the built menu and release it in AModule teardown so popup menus stay valid without extending the lifetime of the whole builder.

Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2026-03-06 18:39:16 -06:00
Alexis Rouillard
68d4360c26 Merge pull request #4905 from sw1nn/master
fix(mpris): disconnect GLib signals before destroying objects
2026-03-04 22:42:43 +01:00
Alexis Rouillard
22b2aff374 Merge pull request #4900 from AlisonB319/ab319/fix-hover-2
fix: sync tooltip updates without resetting hover state
2026-03-04 22:42:16 +01:00
Alexis Rouillard
100d4ec4a4 Merge pull request #4891 from khaneliman/bugfix/stab-003-test-001-hyprland-ipc
fix(hyprland-ipc): harden fd lifecycle and listener loop
2026-03-04 22:41:30 +01:00
Alexis Rouillard
b31292dee2 Merge pull request #4898 from khaneliman/memory
perf(memory): optimize string operations; remove deep copies, memdup, and icon theme rescanning
2026-03-04 22:40:55 +01:00
Alexis Rouillard
a4a7fbbe09 Merge pull request #4897 from khaneliman/network
fix(network): align tooltip and tooltip text
2026-03-04 22:40:20 +01:00
Alexis Rouillard
66c9212217 Merge pull request #4892 from khaneliman/bugfix/stab-004-command-exec-failure
fix(command): return non-zero when child exec fails
2026-03-04 22:39:31 +01:00
Alexis Rouillard
4cc4c902da Merge pull request #4895 from BryceGust/fix/interval-cast
fix: float interval cast
2026-03-04 22:39:00 +01:00
Neale Swinnerton
a816218637 fix(mpris): disconnect GLib signals before destroying objects
Waybar SEGVs in Glib::DispatchNotifier::pipe_io_handler when the MPRIS
module is enabled. The crash is intermittent because it requires a race
between signal emission and object destruction: a playerctl GLib signal
callback (e.g. onPlayerPlay) calls dp.emit(), which writes a pointer to
the Dispatcher into an internal pipe. If the Mpris object is destroyed
before the GLib main loop reads that pipe entry, pipe_io_handler
dereferences a dangling pointer. This typically occurs when a media
player appears or vanishes on D-Bus (browser closing, player quitting)
or during waybar shutdown/config reload.

The root cause is that ~Mpris() calls g_object_unref() on the manager
and player GObjects without first disconnecting the signal handlers that
hold raw `this` pointers. If playerctl holds additional references to
these GObjects, they survive the unref and can still fire signals
targeting the already-destroyed Mpris instance.

Adopt the same cleanup pattern used by the Wireplumber module: call
g_signal_handlers_disconnect_by_data() to sever all signal connections
referencing `this` before releasing the GObjects with g_clear_object().
This guarantees no callbacks can enqueue stale Dispatcher notifications
after teardown begins.

Additionally:
- Clean up old player in onPlayerNameAppeared before replacing it,
  fixing a GObject leak and accumulation of dangling signal connections
- Remove duplicate onPlayerStop signal registration (copy-paste bug)
2026-03-03 16:02:04 +00:00
Alison
3eb2c7e8f4 sync tooltip updates without resetting hover state 2026-03-03 00:24:04 -08:00
Alison
a97e8dad7c Revert "Fix tooltip sync issue by removing conditional checks"
This reverts commit 2b29c9a5d6.
2026-03-03 00:23:57 -08:00
Austin Horstman
c5449bd361 fix(network): log new address only when state changes
Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2026-03-02 23:12:54 -06:00
Austin Horstman
fe03dfaa3b perf(memory): eliminate deep copies in range-based for loops and lambdas
This commit addresses memory churn caused by implicit deep copies during traversal and allocation of complex structures:

- Replaced pass-by-value 'Json::Value' in std::ranges and range-based for loops with 'const auto&' or 'const Json::Value&' in Hyprland modules, preventing large JSON tree duplications on every update.

- Fixed implicit string and pair copies in UPower and CPU Frequency loops by converting 'auto' to 'const auto&' where possible.

- Added 'std::vector::reserve' calls before 'push_back' loops in MPRIS, Niri, and CFFI modules to prevent exponential vector reallocation during initialization.

Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2026-03-02 22:54:07 -06:00
Austin Horstman
7d8be29f97 perf(sni): eliminate icon theme rescanning from system tray hotpath
Valgrind Massif profiling revealed that invoking Gtk::IconTheme::rescan_if_needed() inside SNI updateImage() and getIconByName() loops caused considerable memory churn and potential filesystem stat overhead whenever a system tray app pushed a metadata update.

This commit removes the rescan polling from the SNI proxy callback pipeline and the DefaultGtkIconThemeWrapper, restricting icon theme caching to load boundaries.

Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2026-03-02 22:54:07 -06:00
Austin Horstman
e684e701df perf(sni): eliminate redundant g_memdup2 allocation for D-Bus pixbufs
Memory profiling via Valgrind Massif indicated that 10-20% of peak memory allocations within the SNI loop resulted from copying DBus image data payloads via g_memdup2 before modifying them from ARGB to RGBA.

This commit optimizes the pixel conversion by directly allocating the final array via g_malloc and running the ARGB->RGBA transposition in a single pass while copying from the read-only GVariant buffer, entirely eliminating the intermediate g_memdup stage.

Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2026-03-02 22:54:07 -06:00
Austin Horstman
4c71b2bf9f perf(memory): optimize C++ string operations to reduce heap fragmentation
- Replaced pass-by-value std::string parameters with const std::string&
or std::string_view to prevent SSO overallocations.

- Refactored static mapping functions in UPower to return
std::string_view instead of constructing std::string literals, enabling
perfect cache locality.

- Optimized string concatenation in hot loops (network IPs, inhibitor
lists, sway window marks) by using std::string::append() and
pre-reserving capacity instead of overloaded operator+ which produces
temporary heap instances.

These optimizations reduce high-frequency memory churn and overall heap
fragmentation within the main rendering loops.

Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2026-03-02 22:54:07 -06:00
Austin Horstman
25089b2456 fix(network): align tooltip and tooltip text
Closes https://github.com/Alexays/Waybar/issues/4867

Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2026-03-02 21:24:12 -06:00
Bryce Gust
d929f1a62c Fix 4894 - float interval cast 2026-03-02 17:58:00 -06:00
Austin Horstman
79fb1d9f58 test(command): cover exec failure paths
Command tests did not assert behavior when exec fails in child processes.

I added deterministic regression coverage that forces execl/execlp failure and
verifies non-zero exit status propagation for both open() and forkExec paths.

Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2026-03-02 08:44:54 -06:00
Austin Horstman
5e7dbf1715 fix(command): return non-zero when child exec fails
Child exec failure paths were returning success, which masked command launch
errors from callers.

I switched the child-side failure exits to _exit(127) and added errno-specific
logging so failures propagate with actionable diagnostics.

Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2026-03-02 08:41:43 -06:00
Austin Horstman
8d22d3e07a refactor(sway): use shared ScopedFd for IPC sockets
Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2026-03-02 08:23:06 -06:00
Austin Horstman
39e09118f9 refactor(wayfire): replace custom Sock with shared ScopedFd
Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2026-03-02 08:21:20 -06:00
Austin Horstman
2ff77fb73d refactor(niri): use shared ScopedFd utility
Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2026-03-02 08:21:17 -06:00
Austin Horstman
e83ab7609c refactor(hyprland): use shared ScopedFd utility
Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2026-03-02 08:21:15 -06:00
Austin Horstman
6dfe1c3111 feat(util): add ScopedFd RAII utility for file descriptors
Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2026-03-02 08:21:12 -06:00
Austin Horstman
04ddc5fd23 chore(test-hyprland): remove unused IPC test fixture
The hyprland IPC fixture was no longer used by the current test setup.

I removed the dead fixture so the test code reflects the actual execution path
and is easier to maintain.

Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2026-03-02 08:17:29 -06:00
Austin Horstman
87a5b7ed0f test(hyprland): add failure-path fd-leak coverage
Hyprland tests did not explicitly verify descriptor behavior on key failure
paths.

I added focused tests for missing instance signature and connect-failure paths
that assert file descriptor counts stay stable across repeated attempts.

Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2026-03-02 08:17:29 -06:00
Austin Horstman
d0363313b8 fix(hyprland-ipc): harden fd lifecycle and listener loop
Hyprland IPC had fd lifecycle risks on failure/shutdown paths and used a
spin-sleep listener model.

I initialized fd state defensively, tightened connect/close/shutdown handling,
moved to blocking read with newline framing, and added RAII-style fd cleanup in
socket1 reply paths.

Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2026-03-02 08:09:44 -06:00
Alexis Rouillard
fd086d0f33 Merge pull request #4890 from khaneliman/bugfix/perf-001-safe-signal-bounded-queue
fix(util): bound SafeSignal queue growth under burst load
2026-03-01 18:19:25 +01:00
Alexis Rouillard
47b2dfc6db Merge pull request #4889 from khaneliman/bugfix/stab-001-002-sleeper-thread
fix(util): ensure SleeperThread lifecycle safety and thread sync
2026-03-01 18:19:03 +01:00
Austin Horstman
e4ff024fa8 fix(util): bound SafeSignal queue growth under burst load
SafeSignal could queue events forever when worker threads emitted faster than
the main loop could consume, which risks memory growth and stale updates.

I added a queue cap with a drop-oldest policy so growth stays bounded under
burst load, plus a regression test that validates bounded delivery.

Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2026-02-28 22:57:49 -06:00
Austin Horstman
864523772d test(utils): stress SleeperThread wake and stop control flow
SleeperThread concurrency paths needed stress coverage around wake/stop races.

I added a subprocess stress test that repeatedly interleaves wake_up() and
stop() and verifies the worker exits cleanly.

Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2026-02-28 22:47:52 -06:00
Austin Horstman
44eed7afea fix(sleeper-thread): synchronize control flags with atomics
SleeperThread control flags were shared across threads without consistent
synchronization.

I converted the run/signal flags to atomics and updated wait predicates and
lifecycle transitions to use explicit atomic loads/stores.

Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2026-02-28 22:47:52 -06:00
Austin Horstman
1c61ecf864 test(utils): add SleeperThread reassignment regression
We needed a regression test for reassignment safety after lifecycle fixes.

I added a subprocess test that reassigns SleeperThread workers and verifies the
process exits normally instead of terminating.

Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2026-02-28 22:47:52 -06:00
Austin Horstman
dbbad059f7 fix(sleeper-thread): stop and join before worker reassignment
Reassigning SleeperThread could replace a joinable std::thread and trigger
std::terminate.

I now stop and join any existing worker before reassignment, then reset control
state before starting the replacement worker.

Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2026-02-28 22:28:22 -06:00
Alexis Rouillard
31b373b984 Merge pull request #4888 from Alexays/copilot/fix-waybar-deadlock-issue
[WIP] Fix waybar responsiveness and interaction issues
2026-02-28 15:29:02 +01:00
copilot-swe-agent[bot]
d5297bc424 fix: resolve PulseAudio/WirePlumber deadlock and freeze issues
- Fix AudioBackend destructor: properly lock the PA mainloop before
  disconnecting the context to prevent race conditions with PA callbacks

- Fix context leak on reconnect: call pa_context_unref() when the old
  context is replaced after PA_CONTEXT_FAILED to avoid resource leaks

- Fix PA mainloop killed on reconnect (critical): PA_CONTEXT_TERMINATED
  was unconditionally calling quit() on the mainloop, even during
  reconnection when the old context fires TERMINATED after the new one
  was created. This was killing the new context and preventing successful
  reconnection, causing Waybar to appear frozen. The fix only quits
  the mainloop when the terminating context is still the active one.

- Fix Wireplumber use-after-free: explicitly disconnect GObject signal
  handlers for mixer_api_, def_nodes_api_, and om_ before clearing the
  object references in the destructor to prevent callbacks from firing
  with a destroyed self pointer.

- Fix GVariant memory leak in Wireplumber::handleScroll: unref the
  GVariant created for the set-volume signal after the emit call.

Co-authored-by: Alexays <13947260+Alexays@users.noreply.github.com>
2026-02-28 14:27:22 +00:00
copilot-swe-agent[bot]
c504b7f437 Initial plan 2026-02-28 14:00:25 +00:00
6a503745fe Fix tooltips broken by 2b29c9a
Some checks failed
clang-format / lint (push) Has been cancelled
freebsd / build (push) Has been cancelled
linux / build (c++20, alpine) (push) Has been cancelled
linux / build (c++20, archlinux) (push) Has been cancelled
linux / build (c++20, debian) (push) Has been cancelled
linux / build (c++20, fedora) (push) Has been cancelled
linux / build (c++20, gentoo) (push) Has been cancelled
linux / build (c++20, opensuse) (push) Has been cancelled
Nix-Tests / nix-flake-check (push) Has been cancelled
update-flake-lock / lockfile (push) Has been cancelled
Build and Push Docker Image / build-and-push (alpine) (push) Has been cancelled
Build and Push Docker Image / build-and-push (archlinux) (push) Has been cancelled
Build and Push Docker Image / build-and-push (debian) (push) Has been cancelled
Build and Push Docker Image / build-and-push (fedora) (push) Has been cancelled
Build and Push Docker Image / build-and-push (gentoo) (push) Has been cancelled
Build and Push Docker Image / build-and-push (opensuse) (push) Has been cancelled
2026-02-27 08:57:41 -08:00
267c327db9 Merge upstream/master
Some checks failed
clang-format / lint (push) Has been cancelled
freebsd / build (push) Has been cancelled
linux / build (c++20, alpine) (push) Has been cancelled
linux / build (c++20, archlinux) (push) Has been cancelled
linux / build (c++20, debian) (push) Has been cancelled
linux / build (c++20, fedora) (push) Has been cancelled
linux / build (c++20, gentoo) (push) Has been cancelled
linux / build (c++20, opensuse) (push) Has been cancelled
Nix-Tests / nix-flake-check (push) Has been cancelled
2026-02-27 08:36:07 -08:00
57a9bd83ff Fix group signal-triggered group closing incorrectly
Some checks failed
clang-format / lint (push) Has been cancelled
freebsd / build (push) Has been cancelled
linux / build (c++20, alpine) (push) Has been cancelled
linux / build (c++20, archlinux) (push) Has been cancelled
linux / build (c++20, debian) (push) Has been cancelled
linux / build (c++20, fedora) (push) Has been cancelled
linux / build (c++20, gentoo) (push) Has been cancelled
linux / build (c++20, opensuse) (push) Has been cancelled
Nix-Tests / nix-flake-check (push) Has been cancelled
2026-02-27 06:46:18 -08:00
Alex
7744320ab2 fix: build 2026-02-24 00:55:27 +01:00
Alex
802bf184fb fix: lint 2026-02-24 00:49:23 +01:00
Alex
ef3d55980e fix: some crashes 2026-02-24 00:49:08 +01:00
Alexis Rouillard
a32413a74f Merge pull request #4880 from Alexays/copilot/fix-mpd-module-crash
mpd: fix socket FD leak on system-level connection errors
2026-02-24 00:06:11 +01:00
copilot-swe-agent[bot]
82f076c6c2 mpd: fix FD leak by resetting connection on MPD_ERROR_SYSTEM errors
Co-authored-by: Alexays <13947260+Alexays@users.noreply.github.com>
2026-02-23 23:04:36 +00:00
Alex
e18939210b fix: lint 2026-02-24 00:00:57 +01:00
Alexis Rouillard
aacf0cbc99 Merge pull request #4881 from Alexays/copilot/fix-sigabrt-usb-unplug-issue
battery: fix SIGABRT on USB device unplug race condition
2026-02-24 00:00:19 +01:00
Alexis Rouillard
3e2a50d59f Merge pull request #4879 from Alexays/copilot/fix-waybar-bluetooth-crash
bluetooth: fix segfault when DBus manager fails to initialize
2026-02-23 23:58:51 +01:00
copilot-swe-agent[bot]
49d4049ea3 Fix SIGABRT on USB unplug race condition in battery module
Co-authored-by: Alexays <13947260+Alexays@users.noreply.github.com>
2026-02-23 22:55:05 +00:00
copilot-swe-agent[bot]
0c46818e95 Fix crash when bluetooth DBus manager returns null on timeout
Co-authored-by: Alexays <13947260+Alexays@users.noreply.github.com>
2026-02-23 22:54:23 +00:00
copilot-swe-agent[bot]
37d6541592 Initial plan 2026-02-23 22:53:14 +00:00
copilot-swe-agent[bot]
65fadcf94b Initial plan 2026-02-23 22:52:00 +00:00
copilot-swe-agent[bot]
f806ec03ed Initial plan 2026-02-23 22:51:45 +00:00
Alexis Rouillard
64ecdcfa87 Merge pull request #4846 from BlueManCZ/fix-mpris-fallback-player
fix(mpris): fall back to next non-ignored player and prefer playing players
2026-02-23 23:51:04 +01:00
Alexis Rouillard
4aa8f98552 Merge pull request #4821 from cebem1nt/group-scroll-cutout
fix: owerwrite handleScroll to remove any scrolling handling from group
2026-02-23 23:46:02 +01:00
Alexis Rouillard
730e558cf4 Merge pull request #4834 from khaneliman/niri
feat(niri): niri depends on socket
2026-02-23 23:43:30 +01:00
Alexis Rouillard
22fd2da40e Merge pull request #4836 from Cprakhar/fix/tooltip-return-type-in-format-strings
feat: add `{tooltip}` in format replacements in custom module
2026-02-23 23:26:07 +01:00
Alexis Rouillard
6fa8ad3430 Merge pull request #4849 from 44vladimirov/power_switch_events
battery: power switch events
2026-02-22 18:32:08 +01:00
Alexis Rouillard
54e7451cf0 Merge pull request #4856 from Cprakhar/feat/tooltip-format-cpu
feat: add tooltip-format config option in cpu module
2026-02-20 17:36:17 +01:00
Austin Horstman
ae60ca6233 refactor(niri): declared constructor
Move constructor from hpp to cpp to align with other modules

Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2026-02-20 08:16:56 -06:00
Austin Horstman
a194b576be feat(niri): niri depends on socket
Don't attempt to use niri modules when socket connection fails. Prevents
rendering modules when running another compositor. In same concept as
previous Hyprland change
4295faa7c4

Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2026-02-20 08:16:56 -06:00
Alexis Rouillard
d31b131f80 Merge pull request #4874 from GG2R10/fix/cava-silence-fix
fix(cava): remove silent CSS class on reactivation
2026-02-19 21:55:22 +01:00
GG2R10
9c871c90a7 fix: move silence_ = false inside idle lambda 2026-02-19 15:12:23 -05:00
Alexis Rouillard
a74adc54e5 Merge pull request #4863 from olliestone/fix-aappiconlabel-image-visibility
fix: ensure AAppIconLabel image remains not visibile if icons are disabled
2026-02-18 20:47:27 +01:00
Alexis Rouillard
14a30cd4b7 Merge pull request #4869 from cebem1nt/master
bump: niri/workspaces: change icons priority
2026-02-17 15:45:35 +01:00
cebem1nt
f1140f00f9 niri/workspaces: change icons priority 2026-02-17 11:23:27 -03:00
Ollie Stone
6f9dee979b fix: ensure AAppIconLabel image remains not visibile if icons are
disabled
2026-02-15 18:39:28 +00:00
Alexis Rouillard
e4e47cad5c Merge pull request #3088 from VAWVAW/hyprland-bar-scroll
hyprland/workspaces: Add `enable-bar-scroll` option
2026-02-13 23:43:12 +01:00
Alexis Rouillard
01628dda85 Merge pull request #4852 from tobixen/fix/keyboard-state-hotplug-crash
fix(keyboard-state): fix segfault on device hotplug removal
2026-02-13 23:27:05 +01:00
Alexis Rouillard
9e57d75fe5 Merge pull request #4854 from sliedes/ipc-safesignal
fix: use SafeSignal in ipc in order to not call GTK funcs from a thread
2026-02-13 23:26:46 +01:00
Prakhar Chhalotre
2337d308ce Merge branch 'master' into feat/tooltip-format-cpu 2026-02-14 03:28:29 +05:30
Alexis Rouillard
fc4e2a3534 Merge pull request #4857 from Cprakhar/fix/pango-markup-all
fix: use pango markup for consistent formatting in format and tooltip-format
2026-02-13 22:44:49 +01:00
Alexis Rouillard
a3d7902337 Merge pull request #4858 from chrisvittal/memory-module/tooltip-markup
memory: use markup rather than text for memory tooltip
2026-02-13 22:44:24 +01:00
vawvaw
5b595a4dfe hyprland/workspaces: Add enable-bar-scroll option 2026-02-13 20:48:59 +01:00
Chris Vittal
e0e36b6d81 memory: use markup rather than text for memory tooltip
Seems a little strange to me that we can format the tooltip text, but
can't use markup like we can elsewhere.
2026-02-13 14:17:48 -05:00
Prakhar Chhalotre
3e7976c8eb fix: use pango markup for consistent formatting in format and tooltip-format 2026-02-13 02:23:16 +05:30
Prakhar Chhalotre
4ac539206f feat: add tooltip-format config option in cpu module 2026-02-13 02:00:17 +05:30
Sami Liedes
a70651ed05 fix: use SafeSignal in ipc 2026-02-12 18:11:30 +01:00
Tobias Brox
13469a8847 fix(keyboard-state): fix segfault on device hotplug
The keyboard-state module crashes with SIGSEGV in libinput_device_ref
when a new input device appears in /dev/input/.

Three bugs fixed:

1. Missing NULL check: tryAddDevice() calls libinput_path_add_device()
   which returns NULL on failure, then immediately passes the result to
   libinput_device_ref() without checking.  On laptops, virtual input
   devices (power buttons, lid switch, etc.) appear and disappear in
   /dev/input/ triggering the hotplug handler; if libinput can't open
   one of these, the NULL return causes the segfault.

2. Missing cleanup on device removal: The IN_DELETE handler erased
   devices from the map without calling libinput_path_remove_device(),
   leaving dangling pointers in the libinput context.

3. Thread safety: libinput_devices_ was accessed from 3 threads
   (main/GTK, libinput_thread_, hotplug_thread_) without any mutex.

Fixes #4851

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 11:57:23 +01:00
Alexey Vladimirov
7110c455e2 fix of example config typos 2026-02-12 02:12:00 +03:00
Alexey Vladimirov
3bcadfdf5a battery: power switch events 2026-02-12 01:42:30 +03:00
Alexis Rouillard
03a77c592b Merge pull request #4844 from Cprakhar/fix/remove-battery-status-console-log
fix: remove unnecessary console log for battery status
2026-02-11 21:22:26 +01:00
Alexis Rouillard
3833028697 Merge pull request #4847 from esensar/patch-1
docs: fix typo in waybar.5 in `expand-right` field info
2026-02-11 21:21:49 +01:00
Ensar Sarajčić
c388208e21 docs: fix typo in waybar.5 in expand-right field info 2026-02-11 13:01:07 +01:00
BlueManCZ
a871d90161 Fix button action handling to consistently use the active player 2026-02-11 12:09:20 +01:00
BlueManCZ
0a50e82d0d Prioritize currently playing player 2026-02-11 11:47:05 +01:00
BlueManCZ
a69b7a5536 Handle fallback player for ignored MPRIS players 2026-02-11 10:53:48 +01:00
Prakhar Chhalotre
bd222984bb fix: remove unnecessary console log for battery status 2026-02-11 01:12:21 +05:30
Alexis Rouillard
d527ccd4c1 Merge pull request #4838 from khaneliman/image
fix(image): treat missing interval as once
2026-02-10 14:55:45 +01:00
Austin Horstman
3b478ee6a5 chore: format
Some unrelated files failed formatting.

Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2026-02-09 15:53:44 -06:00
Austin Horstman
c9a7cbbdb3 fix(image): treat missing interval as once
PR #4390 enabled millisecond intervals but changed image interval parsing so a missing interval could resolve to 1ms. That creates a hot update loop and can spike CPU and destabilize rendering in drawer/group setups (issue #4835).

Use explicit semantics: missing, null, non-numeric, or <=0 interval is treated as "once" (max sleep), while positive numeric values still support ms precision with a 1ms floor. This keeps the intended feature (sub-second polling when requested) without defaulting to busy looping.

Also align waybar-image(5) docs with runtime behavior.

Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2026-02-09 15:27:35 -06:00
Prakhar Chhalotre
f96fb53eeb feat: add tooltip in format replacments 2026-02-10 01:00:07 +05:30
Alexis Rouillard
306f970684 Merge pull request #4828 from hannesbraun/fix-poll-include
Fix include of poll.h
2026-02-08 21:07:26 +01:00
Alexis Rouillard
00ccfce6f5 Merge pull request #4831 from Cprakhar/fix/pango-markup-temperature
fix: update tooltip method to use pango markup formatting
2026-02-08 20:59:13 +01:00
Prakhar Chhalotre
7e6da1adb2 fix: update tooltip method to use pango markup formatting 2026-02-09 00:16:11 +05:30
Hannes Braun
f373ebfcbb Fix include of poll.h 2026-02-08 13:32:41 +01:00
Alexis Rouillard
2616785d18 Merge pull request #4822 from khaneliman/ci
ci: bump flake lock actions
2026-02-07 21:20:28 +01:00
Austin Horstman
833c40a84b feat(ci): enable concurrency for nix-tests
Don't let multiple tests run, cancel existing for same pr

Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2026-02-06 15:19:04 -06:00
Austin Horstman
d95809e11b chore(ci): bump flake lock actions
Multiple releases old, keep up to date.

Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2026-02-06 15:16:20 -06:00
cebem1nt
40200afb68 fix: owerwrite handleScroll to remove any scrolling handling from group module 2026-02-06 14:43:12 -03:00
135 changed files with 2006 additions and 767 deletions

View File

@@ -10,7 +10,7 @@ jobs:
lint: lint:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v6
- uses: RafikFarhad/clang-format-github-action@v6 - uses: RafikFarhad/clang-format-github-action@v6
name: clang-format name: clang-format
with: with:

View File

@@ -12,7 +12,7 @@ jobs:
container: container:
image: alexays/waybar:debian image: alexays/waybar:debian
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v6
- name: configure - name: configure
run: | run: |
meson -Dcpp_std=c++20 build # necessary to generate compile_commands.json meson -Dcpp_std=c++20 build # necessary to generate compile_commands.json

View File

@@ -17,7 +17,7 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Login to Docker Hub - name: Login to Docker Hub
uses: docker/login-action@v3 uses: docker/login-action@v3

View File

@@ -12,7 +12,7 @@ jobs:
# https://github.com/actions/runner/issues/385 - for FreeBSD runner support # https://github.com/actions/runner/issues/385 - for FreeBSD runner support
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v6
- name: Test in FreeBSD VM - name: Test in FreeBSD VM
uses: cross-platform-actions/action@v0.28.0 uses: cross-platform-actions/action@v0.28.0
timeout-minutes: 180 timeout-minutes: 180

View File

@@ -25,7 +25,7 @@ jobs:
image: alexays/waybar:${{ matrix.distro }} image: alexays/waybar:${{ matrix.distro }}
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v6
- name: configure - name: configure
run: meson setup -Dman-pages=enabled -Dcpp_std=${{matrix.cpp_std}} build run: meson setup -Dman-pages=enabled -Dcpp_std=${{matrix.cpp_std}} build
- name: build - name: build

View File

@@ -2,12 +2,15 @@ name: "Nix-Tests"
on: on:
pull_request: pull_request:
push: push:
concurrency:
group: ${{ github.workflow }}-nix-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs: jobs:
nix-flake-check: nix-flake-check:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- uses: cachix/install-nix-action@v27 - uses: cachix/install-nix-action@v31
with: with:
extra_nix_config: | extra_nix_config: |
experimental-features = nix-command flakes experimental-features = nix-command flakes

View File

@@ -12,11 +12,11 @@ jobs:
if: github.event_name != 'schedule' || github.repository == 'Alexays/Waybar' if: github.event_name != 'schedule' || github.repository == 'Alexays/Waybar'
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Install Nix - name: Install Nix
uses: cachix/install-nix-action@v27 uses: cachix/install-nix-action@v31
with: with:
extra_nix_config: | extra_nix_config: |
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
- name: Update flake.lock - name: Update flake.lock
uses: DeterminateSystems/update-flake-lock@v21 uses: DeterminateSystems/update-flake-lock@v28

1
.gitignore vendored
View File

@@ -52,3 +52,4 @@ result-*
.ccls-cache .ccls-cache
_codeql_detected_source_root _codeql_detected_source_root
heaptrack*

View File

@@ -48,7 +48,7 @@ class AModule : public IModule {
virtual bool handleMouseLeave(GdkEventCrossing* const& ev); virtual bool handleMouseLeave(GdkEventCrossing* const& ev);
virtual bool handleScroll(GdkEventScroll*); virtual bool handleScroll(GdkEventScroll*);
virtual bool handleRelease(GdkEventButton* const& ev); virtual bool handleRelease(GdkEventButton* const& ev);
GObject* menu_; GObject* menu_ = nullptr;
private: private:
bool handleUserEvent(GdkEventButton* const& ev); bool handleUserEvent(GdkEventButton* const& ev);

View File

@@ -35,6 +35,7 @@ class Group : public AModule {
bool handleMouseLeave(GdkEventCrossing* const& ev) override; bool handleMouseLeave(GdkEventCrossing* const& ev) override;
bool handleToggle(GdkEventButton* const& ev) override; bool handleToggle(GdkEventButton* const& ev) override;
void toggle(); void toggle();
bool handleScroll(GdkEventScroll* e) override;
void show_group(); void show_group();
void hide_group(); void hide_group();
void set_visible(bool v) { void set_visible(bool v) {

View File

@@ -6,7 +6,7 @@
#if defined(__linux__) #if defined(__linux__)
#include <sys/inotify.h> #include <sys/inotify.h>
#endif #endif
#include <sys/poll.h> #include <poll.h>
#include <algorithm> #include <algorithm>
#include <fstream> #include <fstream>

View File

@@ -35,6 +35,7 @@ class Custom : public ALabel {
std::string id_; std::string id_;
std::string alt_; std::string alt_;
std::string tooltip_; std::string tooltip_;
std::string last_tooltip_markup_;
const bool tooltip_format_enabled_; const bool tooltip_format_enabled_;
std::vector<std::string> class_; std::vector<std::string> class_;
int percentage_; int percentage_;

View File

@@ -22,7 +22,7 @@ class Disk : public ALabel {
std::string path_; std::string path_;
std::string unit_; std::string unit_;
float calc_specific_divisor(const std::string divisor); float calc_specific_divisor(const std::string& divisor);
}; };
} // namespace waybar::modules } // namespace waybar::modules

View File

@@ -26,7 +26,7 @@ class Gamemode : public AModule {
const std::string DEFAULT_FORMAT = "{glyph}"; const std::string DEFAULT_FORMAT = "{glyph}";
const std::string DEFAULT_FORMAT_ALT = "{glyph} {count}"; const std::string DEFAULT_FORMAT_ALT = "{glyph} {count}";
const std::string DEFAULT_TOOLTIP_FORMAT = "Games running: {count}"; const std::string DEFAULT_TOOLTIP_FORMAT = "Games running: {count}";
const std::string DEFAULT_GLYPH = ""; const std::string DEFAULT_GLYPH = "󰊴";
void appear(const Glib::RefPtr<Gio::DBus::Connection>& connection, const Glib::ustring& name, void appear(const Glib::RefPtr<Gio::DBus::Connection>& connection, const Glib::ustring& name,
const Glib::ustring& name_owner); const Glib::ustring& name_owner);

View File

@@ -1,5 +1,6 @@
#pragma once #pragma once
#include <atomic>
#include <filesystem> #include <filesystem>
#include <list> #include <list>
#include <mutex> #include <mutex>
@@ -43,10 +44,11 @@ class IPC {
std::thread ipcThread_; std::thread ipcThread_;
std::mutex callbackMutex_; std::mutex callbackMutex_;
std::mutex socketMutex_;
util::JsonParser parser_; util::JsonParser parser_;
std::list<std::pair<std::string, EventHandler*>> callbacks_; std::list<std::pair<std::string, EventHandler*>> callbacks_;
int socketfd_; // the hyprland socket file descriptor int socketfd_ = -1; // the hyprland socket file descriptor
pid_t socketOwnerPid_; pid_t socketOwnerPid_ = -1;
bool running_ = true; // the ipcThread will stop running when this is false std::atomic<bool> running_ = true; // the ipcThread will stop running when this is false
}; };
}; // namespace waybar::modules::hyprland }; // namespace waybar::modules::hyprland

View File

@@ -20,8 +20,8 @@ class Window : public waybar::AAppIconLabel, public EventHandler {
private: private:
struct Workspace { struct Workspace {
int id; int id = 0;
int windows; int windows = 0;
std::string last_window; std::string last_window;
std::string last_window_title; std::string last_window_title;
@@ -29,14 +29,14 @@ class Window : public waybar::AAppIconLabel, public EventHandler {
}; };
struct WindowData { struct WindowData {
bool floating; bool floating = false;
int monitor = -1; int monitor = -1;
std::string class_name; std::string class_name;
std::string initial_class_name; std::string initial_class_name;
std::string title; std::string title;
std::string initial_title; std::string initial_title;
bool fullscreen; bool fullscreen = false;
bool grouped; bool grouped = false;
static auto parse(const Json::Value&) -> WindowData; static auto parse(const Json::Value&) -> WindowData;
}; };
@@ -47,7 +47,7 @@ class Window : public waybar::AAppIconLabel, public EventHandler {
void queryActiveWorkspace(); void queryActiveWorkspace();
void setClass(const std::string&, bool enable); void setClass(const std::string&, bool enable);
bool separateOutputs_; bool separateOutputs_ = false;
std::mutex mutex_; std::mutex mutex_;
const Bar& bar_; const Bar& bar_;
util::JsonParser parser_; util::JsonParser parser_;
@@ -55,11 +55,11 @@ class Window : public waybar::AAppIconLabel, public EventHandler {
Workspace workspace_; Workspace workspace_;
std::string soloClass_; std::string soloClass_;
std::string lastSoloClass_; std::string lastSoloClass_;
bool solo_; bool solo_ = false;
bool allFloating_; bool allFloating_ = false;
bool swallowing_; bool swallowing_ = false;
bool fullscreen_; bool fullscreen_ = false;
bool focused_; bool focused_ = false;
IPC& m_ipc; IPC& m_ipc;
}; };

View File

@@ -40,10 +40,11 @@ struct WindowRepr {
class WindowCreationPayload { class WindowCreationPayload {
public: public:
WindowCreationPayload(std::string workspace_name, WindowAddress window_address, WindowCreationPayload(const std::string& workspace_name, WindowAddress window_address,
WindowRepr window_repr); WindowRepr window_repr);
WindowCreationPayload(std::string workspace_name, WindowAddress window_address, WindowCreationPayload(const std::string& workspace_name, WindowAddress window_address,
std::string window_class, std::string window_title, bool is_active); const std::string& window_class, const std::string& window_title,
bool is_active);
WindowCreationPayload(Json::Value const& client_data); WindowCreationPayload(Json::Value const& client_data);
int incrementTimeSpentUncreated(); int incrementTimeSpentUncreated();

View File

@@ -4,6 +4,7 @@
#include <gtkmm/enums.h> #include <gtkmm/enums.h>
#include <gtkmm/label.h> #include <gtkmm/label.h>
#include <json/value.h> #include <json/value.h>
#include <sigc++/connection.h>
#include <cstdint> #include <cstdint>
#include <map> #include <map>
@@ -43,6 +44,7 @@ class Workspaces : public AModule, public EventHandler {
auto moveToMonitor() const -> bool { return m_moveToMonitor; } auto moveToMonitor() const -> bool { return m_moveToMonitor; }
auto enableTaskbar() const -> bool { return m_enableTaskbar; } auto enableTaskbar() const -> bool { return m_enableTaskbar; }
auto taskbarWithIcon() const -> bool { return m_taskbarWithIcon; } auto taskbarWithIcon() const -> bool { return m_taskbarWithIcon; }
auto barScroll() const -> bool { return m_barScroll; }
auto getBarOutput() const -> std::string { return m_bar.output->name; } auto getBarOutput() const -> std::string { return m_bar.output->name; }
auto formatBefore() const -> std::string { return m_formatBefore; } auto formatBefore() const -> std::string { return m_formatBefore; }
@@ -58,7 +60,7 @@ class Workspaces : public AModule, public EventHandler {
enum class ActiveWindowPosition { NONE, FIRST, LAST }; enum class ActiveWindowPosition { NONE, FIRST, LAST };
auto activeWindowPosition() const -> ActiveWindowPosition { return m_activeWindowPosition; } auto activeWindowPosition() const -> ActiveWindowPosition { return m_activeWindowPosition; }
std::string getRewrite(std::string window_class, std::string window_title); std::string getRewrite(const std::string& window_class, const std::string& window_title);
std::string& getWindowSeparator() { return m_formatWindowSeparator; } std::string& getWindowSeparator() { return m_formatWindowSeparator; }
bool isWorkspaceIgnored(std::string const& workspace_name); bool isWorkspaceIgnored(std::string const& workspace_name);
@@ -122,6 +124,8 @@ class Workspaces : public AModule, public EventHandler {
static std::pair<std::string, std::string> splitDoublePayload(std::string const& payload); static std::pair<std::string, std::string> splitDoublePayload(std::string const& payload);
static std::tuple<std::string, std::string, std::string> splitTriplePayload( static std::tuple<std::string, std::string, std::string> splitTriplePayload(
std::string const& payload); std::string const& payload);
// scroll events
bool handleScroll(GdkEventScroll* e) override;
// Update methods // Update methods
void doUpdate(); void doUpdate();
@@ -145,6 +149,7 @@ class Workspaces : public AModule, public EventHandler {
bool m_specialVisibleOnly = false; bool m_specialVisibleOnly = false;
bool m_persistentOnly = false; bool m_persistentOnly = false;
bool m_moveToMonitor = false; bool m_moveToMonitor = false;
bool m_barScroll = false;
Json::Value m_persistentWorkspaceConfig; Json::Value m_persistentWorkspaceConfig;
// Map for windows stored in workspaces not present in the current bar. // Map for windows stored in workspaces not present in the current bar.
@@ -204,6 +209,7 @@ class Workspaces : public AModule, public EventHandler {
std::mutex m_mutex; std::mutex m_mutex;
const Bar& m_bar; const Bar& m_bar;
Gtk::Box m_box; Gtk::Box m_box;
sigc::connection m_scrollEventConnection_;
IPC& m_ipc; IPC& m_ipc;
}; };

View File

@@ -3,6 +3,7 @@
#include <fmt/chrono.h> #include <fmt/chrono.h>
#include <gtkmm/label.h> #include <gtkmm/label.h>
#include <mutex>
#include <set> #include <set>
#include <unordered_map> #include <unordered_map>
@@ -41,6 +42,7 @@ class KeyboardState : public AModule {
struct libinput* libinput_; struct libinput* libinput_;
std::unordered_map<std::string, struct libinput_device*> libinput_devices_; std::unordered_map<std::string, struct libinput_device*> libinput_devices_;
std::mutex devices_mutex_; // protects libinput_devices_
std::set<int> binding_keys; std::set<int> binding_keys;
util::SleeperThread libinput_thread_, hotplug_thread_; util::SleeperThread libinput_thread_, hotplug_thread_;

View File

@@ -44,7 +44,7 @@ class MPD : public ALabel {
std::string getFilename() const; std::string getFilename() const;
void setLabel(); void setLabel();
std::string getStateIcon() const; std::string getStateIcon() const;
std::string getOptionIcon(std::string optionName, bool activated) const; std::string getOptionIcon(const std::string& optionName, bool activated) const;
// GUI-side methods // GUI-side methods
bool handlePlayPause(GdkEventButton* const&); bool handlePlayPause(GdkEventButton* const&);

View File

@@ -78,6 +78,7 @@ class Mpris : public ALabel {
PlayerctlPlayerManager* manager; PlayerctlPlayerManager* manager;
PlayerctlPlayer* player; PlayerctlPlayer* player;
PlayerctlPlayer* last_active_player_ = nullptr;
std::string lastStatus; std::string lastStatus;
std::string lastPlayer; std::string lastPlayer;

View File

@@ -70,6 +70,7 @@ class Network : public ALabel {
unsigned long long bandwidth_down_total_{0}; unsigned long long bandwidth_down_total_{0};
unsigned long long bandwidth_up_total_{0}; unsigned long long bandwidth_up_total_{0};
std::chrono::steady_clock::time_point bandwidth_last_sample_time_;
std::string state_; std::string state_;
std::string essid_; std::string essid_;

View File

@@ -17,7 +17,7 @@ class EventHandler {
class IPC { class IPC {
public: public:
IPC() { startIPC(); } IPC();
void registerForIPC(const std::string& ev, EventHandler* ev_handler); void registerForIPC(const std::string& ev, EventHandler* ev_handler);
void unregisterForIPC(EventHandler* handler); void unregisterForIPC(EventHandler* handler);

View File

@@ -16,7 +16,7 @@ class Host {
public: public:
Host(const std::size_t id, const Json::Value&, const Bar&, Host(const std::size_t id, const Json::Value&, const Bar&,
const std::function<void(std::unique_ptr<Item>&)>&, const std::function<void(std::unique_ptr<Item>&)>&,
const std::function<void(std::unique_ptr<Item>&)>&); const std::function<void(std::unique_ptr<Item>&)>&, const std::function<void()>&);
~Host(); ~Host();
private: private:
@@ -28,9 +28,13 @@ class Host {
static void registerHost(GObject*, GAsyncResult*, gpointer); static void registerHost(GObject*, GAsyncResult*, gpointer);
static void itemRegistered(SnWatcher*, const gchar*, gpointer); static void itemRegistered(SnWatcher*, const gchar*, gpointer);
static void itemUnregistered(SnWatcher*, const gchar*, gpointer); static void itemUnregistered(SnWatcher*, const gchar*, gpointer);
void itemReady(Item&);
void itemInvalidated(Item&);
void removeItem(std::vector<std::unique_ptr<Item>>::iterator);
void clearItems();
std::tuple<std::string, std::string> getBusNameAndObjectPath(const std::string); std::tuple<std::string, std::string> getBusNameAndObjectPath(const std::string);
void addRegisteredItem(std::string service); void addRegisteredItem(const std::string& service);
std::vector<std::unique_ptr<Item>> items_; std::vector<std::unique_ptr<Item>> items_;
const std::string bus_name_; const std::string bus_name_;
@@ -43,6 +47,7 @@ class Host {
const Bar& bar_; const Bar& bar_;
const std::function<void(std::unique_ptr<Item>&)> on_add_; const std::function<void(std::unique_ptr<Item>&)> on_add_;
const std::function<void(std::unique_ptr<Item>&)> on_remove_; const std::function<void(std::unique_ptr<Item>&)> on_remove_;
const std::function<void()> on_update_;
}; };
} // namespace waybar::modules::SNI } // namespace waybar::modules::SNI

View File

@@ -11,6 +11,7 @@
#include <libdbusmenu-gtk/dbusmenu-gtk.h> #include <libdbusmenu-gtk/dbusmenu-gtk.h>
#include <sigc++/trackable.h> #include <sigc++/trackable.h>
#include <functional>
#include <set> #include <set>
#include <string_view> #include <string_view>
@@ -25,9 +26,13 @@ struct ToolTip {
class Item : public sigc::trackable { class Item : public sigc::trackable {
public: public:
Item(const std::string&, const std::string&, const Json::Value&, const Bar&); Item(const std::string&, const std::string&, const Json::Value&, const Bar&,
const std::function<void(Item&)>&, const std::function<void(Item&)>&,
const std::function<void()>&);
~Item(); ~Item();
bool isReady() const;
std::string bus_name; std::string bus_name;
std::string object_path; std::string object_path;
@@ -43,7 +48,9 @@ class Item : public sigc::trackable {
Glib::RefPtr<Gdk::Pixbuf> icon_pixmap; Glib::RefPtr<Gdk::Pixbuf> icon_pixmap;
Glib::RefPtr<Gtk::IconTheme> icon_theme; Glib::RefPtr<Gtk::IconTheme> icon_theme;
std::string overlay_icon_name; std::string overlay_icon_name;
Glib::RefPtr<Gdk::Pixbuf> overlay_icon_pixmap;
std::string attention_icon_name; std::string attention_icon_name;
Glib::RefPtr<Gdk::Pixbuf> attention_icon_pixmap;
std::string attention_movie_name; std::string attention_movie_name;
std::string icon_theme_path; std::string icon_theme_path;
std::string menu; std::string menu;
@@ -62,6 +69,8 @@ class Item : public sigc::trackable {
void proxyReady(Glib::RefPtr<Gio::AsyncResult>& result); void proxyReady(Glib::RefPtr<Gio::AsyncResult>& result);
void setProperty(const Glib::ustring& name, Glib::VariantBase& value); void setProperty(const Glib::ustring& name, Glib::VariantBase& value);
void setStatus(const Glib::ustring& value); void setStatus(const Glib::ustring& value);
void setReady();
void invalidate();
void setCustomIcon(const std::string& id); void setCustomIcon(const std::string& id);
void getUpdatedProperties(); void getUpdatedProperties();
void processUpdatedProperties(Glib::RefPtr<Gio::AsyncResult>& result); void processUpdatedProperties(Glib::RefPtr<Gio::AsyncResult>& result);
@@ -69,8 +78,13 @@ class Item : public sigc::trackable {
const Glib::VariantContainerBase& arguments); const Glib::VariantContainerBase& arguments);
void updateImage(); void updateImage();
Glib::RefPtr<Gdk::Pixbuf> extractPixBuf(GVariant* variant); static Glib::RefPtr<Gdk::Pixbuf> extractPixBuf(GVariant* variant);
Glib::RefPtr<Gdk::Pixbuf> getIconPixbuf(); Glib::RefPtr<Gdk::Pixbuf> getIconPixbuf();
Glib::RefPtr<Gdk::Pixbuf> getAttentionIconPixbuf();
Glib::RefPtr<Gdk::Pixbuf> getOverlayIconPixbuf();
Glib::RefPtr<Gdk::Pixbuf> loadIconFromNameOrFile(const std::string& name, bool log_failure);
static Glib::RefPtr<Gdk::Pixbuf> overlayPixbufs(const Glib::RefPtr<Gdk::Pixbuf>&,
const Glib::RefPtr<Gdk::Pixbuf>&);
Glib::RefPtr<Gdk::Pixbuf> getIconByName(const std::string& name, int size); Glib::RefPtr<Gdk::Pixbuf> getIconByName(const std::string& name, int size);
double getScaledIconSize(); double getScaledIconSize();
static void onMenuDestroyed(Item* self, GObject* old_menu_pointer); static void onMenuDestroyed(Item* self, GObject* old_menu_pointer);
@@ -86,8 +100,13 @@ class Item : public sigc::trackable {
gdouble distance_scrolled_y_ = 0; gdouble distance_scrolled_y_ = 0;
// visibility of items with Status == Passive // visibility of items with Status == Passive
bool show_passive_ = false; bool show_passive_ = false;
bool ready_ = false;
Glib::ustring status_ = "active";
const Bar& bar_; const Bar& bar_;
const std::function<void(Item&)> on_ready_;
const std::function<void(Item&)> on_invalidate_;
const std::function<void()> on_updated_;
Glib::RefPtr<Gio::DBus::Proxy> proxy_; Glib::RefPtr<Gio::DBus::Proxy> proxy_;
Glib::RefPtr<Gio::Cancellable> cancellable_; Glib::RefPtr<Gio::Cancellable> cancellable_;

View File

@@ -19,6 +19,7 @@ class Tray : public AModule {
private: private:
void onAdd(std::unique_ptr<Item>& item); void onAdd(std::unique_ptr<Item>& item);
void onRemove(std::unique_ptr<Item>& item); void onRemove(std::unique_ptr<Item>& item);
void queueUpdate();
static inline std::size_t nb_hosts_ = 0; static inline std::size_t nb_hosts_ = 0;
bool show_passive_ = false; bool show_passive_ = false;

View File

@@ -37,7 +37,7 @@ class BarIpcClient {
void onModeUpdate(bool visible_by_modifier); void onModeUpdate(bool visible_by_modifier);
void onUrgencyUpdate(bool visible_by_urgency); void onUrgencyUpdate(bool visible_by_urgency);
void update(); void update();
bool isModuleEnabled(std::string name); bool isModuleEnabled(const std::string& name);
Bar& bar_; Bar& bar_;
util::JsonParser parser_; util::JsonParser parser_;

View File

@@ -12,6 +12,8 @@
#include <string> #include <string>
#include "ipc.hpp" #include "ipc.hpp"
#include "util/SafeSignal.hpp"
#include "util/scoped_fd.hpp"
#include "util/sleeper_thread.hpp" #include "util/sleeper_thread.hpp"
namespace waybar::modules::sway { namespace waybar::modules::sway {
@@ -27,8 +29,8 @@ class Ipc {
std::string payload; std::string payload;
}; };
sigc::signal<void, const struct ipc_response&> signal_event; ::waybar::SafeSignal<const struct ipc_response&> signal_event;
sigc::signal<void, const struct ipc_response&> signal_cmd; ::waybar::SafeSignal<const struct ipc_response&> signal_cmd;
void sendCmd(uint32_t type, const std::string& payload = ""); void sendCmd(uint32_t type, const std::string& payload = "");
void subscribe(const std::string& payload); void subscribe(const std::string& payload);
@@ -44,8 +46,8 @@ class Ipc {
struct ipc_response send(int fd, uint32_t type, const std::string& payload = ""); struct ipc_response send(int fd, uint32_t type, const std::string& payload = "");
struct ipc_response recv(int fd); struct ipc_response recv(int fd);
int fd_; util::ScopedFd fd_;
int fd_event_; util::ScopedFd fd_event_;
std::mutex mutex_; std::mutex mutex_;
util::SleeperThread thread_; util::SleeperThread thread_;
}; };

View File

@@ -47,7 +47,7 @@ class Language : public ALabel, public sigc::trackable {
void onEvent(const struct Ipc::ipc_response&); void onEvent(const struct Ipc::ipc_response&);
void onCmd(const struct Ipc::ipc_response&); void onCmd(const struct Ipc::ipc_response&);
auto set_current_layout(std::string current_layout) -> void; auto set_current_layout(const std::string& current_layout) -> void;
auto init_layouts_map(const std::vector<std::string>& used_layouts) -> 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_LAYOUT_NAMES_KEY;

View File

@@ -27,7 +27,7 @@ class Workspaces : public AModule, public sigc::trackable {
static constexpr std::string_view persistent_workspace_switch_cmd_ = static constexpr std::string_view persistent_workspace_switch_cmd_ =
R"(workspace {} "{}"; move workspace to output "{}"; workspace {} "{}")"; R"(workspace {} "{}"; move workspace to output "{}"; workspace {} "{}")";
static int convertWorkspaceNameToNum(std::string name); static int convertWorkspaceNameToNum(const std::string& name);
static int windowRewritePriorityFunction(std::string const& window_rule); static int windowRewritePriorityFunction(std::string const& window_rule);
void onCmd(const struct Ipc::ipc_response&); void onCmd(const struct Ipc::ipc_response&);
@@ -40,7 +40,7 @@ class Workspaces : public AModule, public sigc::trackable {
std::string getIcon(const std::string&, const Json::Value&); std::string getIcon(const std::string&, const Json::Value&);
std::string getCycleWorkspace(std::vector<Json::Value>::iterator, bool prev) const; std::string getCycleWorkspace(std::vector<Json::Value>::iterator, bool prev) const;
uint16_t getWorkspaceIndex(const std::string& name) const; uint16_t getWorkspaceIndex(const std::string& name) const;
static std::string trimWorkspaceName(std::string); static std::string trimWorkspaceName(const std::string&);
bool handleScroll(GdkEventScroll* /*unused*/) override; bool handleScroll(GdkEventScroll* /*unused*/) override;
const Bar& bar_; const Bar& bar_;

View File

@@ -3,6 +3,7 @@
#include <giomm/dbusproxy.h> #include <giomm/dbusproxy.h>
#include <string> #include <string>
#include <vector>
#include "ALabel.hpp" #include "ALabel.hpp"
@@ -11,23 +12,42 @@ namespace waybar::modules {
class SystemdFailedUnits : public ALabel { class SystemdFailedUnits : public ALabel {
public: public:
SystemdFailedUnits(const std::string&, const Json::Value&); SystemdFailedUnits(const std::string&, const Json::Value&);
virtual ~SystemdFailedUnits(); virtual ~SystemdFailedUnits() = default;
auto update() -> void override; auto update() -> void override;
private: private:
bool hide_on_ok; struct FailedUnit {
std::string format_ok; std::string name;
std::string description;
std::string load_state;
std::string active_state;
std::string sub_state;
std::string scope;
};
bool update_pending; bool hide_on_ok_;
std::string system_state, user_state, overall_state; std::string format_ok_;
uint32_t nr_failed_system, nr_failed_user, nr_failed; std::string tooltip_format_;
std::string last_status; std::string tooltip_format_ok_;
Glib::RefPtr<Gio::DBus::Proxy> system_proxy, user_proxy; std::string tooltip_unit_format_;
bool update_pending_;
std::string system_state_, user_state_, overall_state_;
uint32_t nr_failed_system_, nr_failed_user_, nr_failed_;
std::string last_status_;
Glib::RefPtr<Gio::DBus::Proxy> system_props_proxy_, user_props_proxy_;
Glib::RefPtr<Gio::DBus::Proxy> system_manager_proxy_, user_manager_proxy_;
std::vector<FailedUnit> failed_units_;
void notify_cb(const Glib::ustring& sender_name, const Glib::ustring& signal_name, void notify_cb(const Glib::ustring& sender_name, const Glib::ustring& signal_name,
const Glib::VariantContainerBase& arguments); const Glib::VariantContainerBase& arguments);
void RequestFailedUnits(); void RequestFailedUnits();
void RequestFailedUnitsList();
void RequestSystemState(); void RequestSystemState();
std::vector<FailedUnit> LoadFailedUnitsList(const char* kind,
Glib::RefPtr<Gio::DBus::Proxy>& proxy,
const std::string& scope);
std::string BuildTooltipFailedList() const;
void updateData(); void updateData();
}; };

View File

@@ -12,6 +12,8 @@
#include <unordered_map> #include <unordered_map>
#include <utility> #include <utility>
#include "util/scoped_fd.hpp"
namespace waybar::modules::wayfire { namespace waybar::modules::wayfire {
using EventHandler = std::function<void(const std::string& event)>; using EventHandler = std::function<void(const std::string& event)>;
@@ -71,25 +73,9 @@ struct State {
auto update_view(const Json::Value& view) -> void; auto update_view(const Json::Value& view) -> void;
}; };
struct Sock { using Sock = util::ScopedFd;
int fd;
Sock(int fd) : fd{fd} {} class IPC : public std::enable_shared_from_this<IPC> {
~Sock() { close(fd); }
Sock(const Sock&) = delete;
auto operator=(const Sock&) = delete;
Sock(Sock&& rhs) noexcept {
fd = rhs.fd;
rhs.fd = -1;
}
auto& operator=(Sock&& rhs) noexcept {
fd = rhs.fd;
rhs.fd = -1;
return *this;
}
};
class IPC {
static std::weak_ptr<IPC> instance; static std::weak_ptr<IPC> instance;
Json::CharReaderBuilder reader_builder; Json::CharReaderBuilder reader_builder;
Json::StreamWriterBuilder writer_builder; Json::StreamWriterBuilder writer_builder;
@@ -98,7 +84,7 @@ class IPC {
State state; State state;
std::mutex state_mutex; std::mutex state_mutex;
IPC() { start(); } IPC() = default;
static auto connect() -> Sock; static auto connect() -> Sock;
auto receive(Sock& sock) -> Json::Value; auto receive(Sock& sock) -> Json::Value;

View File

@@ -3,6 +3,7 @@
#include <glibmm/dispatcher.h> #include <glibmm/dispatcher.h>
#include <sigc++/signal.h> #include <sigc++/signal.h>
#include <cstddef>
#include <functional> #include <functional>
#include <mutex> #include <mutex>
#include <queue> #include <queue>
@@ -27,6 +28,12 @@ struct SafeSignal : sigc::signal<void(std::decay_t<Args>...)> {
public: public:
SafeSignal() { dp_.connect(sigc::mem_fun(*this, &SafeSignal::handle_event)); } SafeSignal() { dp_.connect(sigc::mem_fun(*this, &SafeSignal::handle_event)); }
void set_max_queued_events(std::size_t max_queued_events) {
std::unique_lock lock(mutex_);
max_queued_events_ = max_queued_events;
trim_queue_locked();
}
template <typename... EmitArgs> template <typename... EmitArgs>
void emit(EmitArgs&&... args) { void emit(EmitArgs&&... args) {
if (main_tid_ == std::this_thread::get_id()) { if (main_tid_ == std::this_thread::get_id()) {
@@ -41,6 +48,9 @@ struct SafeSignal : sigc::signal<void(std::decay_t<Args>...)> {
} else { } else {
{ {
std::unique_lock lock(mutex_); std::unique_lock lock(mutex_);
if (max_queued_events_ != 0 && queue_.size() >= max_queued_events_) {
queue_.pop();
}
queue_.emplace(std::forward<EmitArgs>(args)...); queue_.emplace(std::forward<EmitArgs>(args)...);
} }
dp_.emit(); dp_.emit();
@@ -60,6 +70,15 @@ struct SafeSignal : sigc::signal<void(std::decay_t<Args>...)> {
using signal_t::emit_reverse; using signal_t::emit_reverse;
using signal_t::make_slot; using signal_t::make_slot;
void trim_queue_locked() {
if (max_queued_events_ == 0) {
return;
}
while (queue_.size() > max_queued_events_) {
queue_.pop();
}
}
void handle_event() { void handle_event() {
for (std::unique_lock lock(mutex_); !queue_.empty(); lock.lock()) { for (std::unique_lock lock(mutex_); !queue_.empty(); lock.lock()) {
auto args = queue_.front(); auto args = queue_.front();
@@ -72,6 +91,7 @@ struct SafeSignal : sigc::signal<void(std::decay_t<Args>...)> {
Glib::Dispatcher dp_; Glib::Dispatcher dp_;
std::mutex mutex_; std::mutex mutex_;
std::queue<arg_tuple_t> queue_; std::queue<arg_tuple_t> queue_;
std::size_t max_queued_events_ = 4096;
const std::thread::id main_tid_ = std::this_thread::get_id(); const std::thread::id main_tid_ = std::this_thread::get_id();
// cache functor for signal emission to avoid recreating it on each event // cache functor for signal emission to avoid recreating it on each event
const slot_t cached_fn_ = make_slot(); const slot_t cached_fn_ = make_slot();

View File

@@ -20,6 +20,8 @@ extern std::list<pid_t> reap;
namespace waybar::util::command { namespace waybar::util::command {
constexpr int kExecFailureExitCode = 127;
struct res { struct res {
int exit_code; int exit_code;
std::string out; std::string out;
@@ -59,6 +61,7 @@ inline int close(FILE* fp, pid_t pid) {
spdlog::debug("Cmd continued"); spdlog::debug("Cmd continued");
} else if (ret == -1) { } else if (ret == -1) {
spdlog::debug("waitpid failed: {}", strerror(errno)); spdlog::debug("waitpid failed: {}", strerror(errno));
break;
} else { } else {
break; break;
} }
@@ -113,7 +116,9 @@ inline FILE* open(const std::string& cmd, int& pid, const std::string& output_na
setenv("WAYBAR_OUTPUT_NAME", output_name.c_str(), 1); setenv("WAYBAR_OUTPUT_NAME", output_name.c_str(), 1);
} }
execlp("/bin/sh", "sh", "-c", cmd.c_str(), (char*)0); execlp("/bin/sh", "sh", "-c", cmd.c_str(), (char*)0);
exit(0); const int saved_errno = errno;
spdlog::error("execlp(/bin/sh) failed in open: {}", strerror(saved_errno));
_exit(kExecFailureExitCode);
} else { } else {
::close(fd[1]); ::close(fd[1]);
} }
@@ -161,7 +166,9 @@ inline int32_t forkExec(const std::string& cmd, const std::string& output_name)
setenv("WAYBAR_OUTPUT_NAME", output_name.c_str(), 1); setenv("WAYBAR_OUTPUT_NAME", output_name.c_str(), 1);
} }
execl("/bin/sh", "sh", "-c", cmd.c_str(), (char*)0); execl("/bin/sh", "sh", "-c", cmd.c_str(), (char*)0);
exit(0); const int saved_errno = errno;
spdlog::error("execl(/bin/sh) failed in forkExec: {}", strerror(saved_errno));
_exit(kExecFailureExitCode);
} else { } else {
reap_mtx.lock(); reap_mtx.lock();
reap.push_back(pid); reap.push_back(pid);
@@ -172,8 +179,6 @@ inline int32_t forkExec(const std::string& cmd, const std::string& output_name)
return pid; return pid;
} }
inline int32_t forkExec(const std::string& cmd) { inline int32_t forkExec(const std::string& cmd) { return forkExec(cmd, ""); }
return forkExec(cmd, "");
}
} // namespace waybar::util::command } // namespace waybar::util::command

View File

@@ -0,0 +1,17 @@
#pragma once
#include <string>
/**
* Result of transforming 8-bit hex codes to rgba().
*/
struct TransformResult {
std::string css;
bool was_transformed;
};
/**
* Reads a CSS file, searches for 8-bit hex codes (#RRGGBBAA),
* and transforms them into GTK-compatible rgba() syntax.
*/
TransformResult transform_8bit_to_hex(const std::string& file_path);

View File

@@ -0,0 +1,54 @@
#pragma once
#include <unistd.h>
namespace waybar::util {
class ScopedFd {
public:
explicit ScopedFd(int fd = -1) : fd_(fd) {}
~ScopedFd() {
if (fd_ != -1) {
close(fd_);
}
}
// ScopedFd is non-copyable
ScopedFd(const ScopedFd&) = delete;
ScopedFd& operator=(const ScopedFd&) = delete;
// ScopedFd is moveable
ScopedFd(ScopedFd&& other) noexcept : fd_(other.fd_) { other.fd_ = -1; }
ScopedFd& operator=(ScopedFd&& other) noexcept {
if (this != &other) {
if (fd_ != -1) {
close(fd_);
}
fd_ = other.fd_;
other.fd_ = -1;
}
return *this;
}
int get() const { return fd_; }
operator int() const { return fd_; }
void reset(int fd = -1) {
if (fd_ != -1) {
close(fd_);
}
fd_ = fd;
}
int release() {
int fd = fd_;
fd_ = -1;
return fd;
}
private:
int fd_;
};
} // namespace waybar::util

View File

@@ -1,5 +1,6 @@
#pragma once #pragma once
#include <atomic>
#include <chrono> #include <chrono>
#include <condition_variable> #include <condition_variable>
#include <ctime> #include <ctime>
@@ -31,8 +32,8 @@ class SleeperThread {
SleeperThread(std::function<void()> func) SleeperThread(std::function<void()> func)
: thread_{[this, func] { : thread_{[this, func] {
while (do_run_) { while (do_run_.load(std::memory_order_relaxed)) {
signal_ = false; signal_.store(false, std::memory_order_relaxed);
func(); func();
} }
}} { }} {
@@ -42,9 +43,18 @@ class SleeperThread {
} }
SleeperThread& operator=(std::function<void()> func) { SleeperThread& operator=(std::function<void()> func) {
if (thread_.joinable()) {
stop();
thread_.join();
}
{
std::lock_guard<std::mutex> lck(mutex_);
do_run_.store(true, std::memory_order_relaxed);
signal_.store(false, std::memory_order_relaxed);
}
thread_ = std::thread([this, func] { thread_ = std::thread([this, func] {
while (do_run_) { while (do_run_.load(std::memory_order_relaxed)) {
signal_ = false; signal_.store(false, std::memory_order_relaxed);
func(); func();
} }
}); });
@@ -56,12 +66,14 @@ class SleeperThread {
return *this; return *this;
} }
bool isRunning() const { return do_run_; } bool isRunning() const { return do_run_.load(std::memory_order_relaxed); }
auto sleep() { auto sleep() {
std::unique_lock lk(mutex_); std::unique_lock lk(mutex_);
CancellationGuard cancel_lock; CancellationGuard cancel_lock;
return condvar_.wait(lk, [this] { return signal_ || !do_run_; }); return condvar_.wait(lk, [this] {
return signal_.load(std::memory_order_relaxed) || !do_run_.load(std::memory_order_relaxed);
});
} }
auto sleep_for(std::chrono::system_clock::duration dur) { auto sleep_for(std::chrono::system_clock::duration dur) {
@@ -73,7 +85,9 @@ class SleeperThread {
if (now < max_time_point - dur) { if (now < max_time_point - dur) {
wait_end = now + dur; wait_end = now + dur;
} }
return condvar_.wait_until(lk, wait_end, [this] { return signal_ || !do_run_; }); return condvar_.wait_until(lk, wait_end, [this] {
return signal_.load(std::memory_order_relaxed) || !do_run_.load(std::memory_order_relaxed);
});
} }
auto sleep_until( auto sleep_until(
@@ -81,22 +95,24 @@ class SleeperThread {
time_point) { time_point) {
std::unique_lock lk(mutex_); std::unique_lock lk(mutex_);
CancellationGuard cancel_lock; CancellationGuard cancel_lock;
return condvar_.wait_until(lk, time_point, [this] { return signal_ || !do_run_; }); return condvar_.wait_until(lk, time_point, [this] {
return signal_.load(std::memory_order_relaxed) || !do_run_.load(std::memory_order_relaxed);
});
} }
void wake_up() { void wake_up() {
{ {
std::lock_guard<std::mutex> lck(mutex_); std::lock_guard<std::mutex> lck(mutex_);
signal_ = true; signal_.store(true, std::memory_order_relaxed);
} }
condvar_.notify_all(); condvar_.notify_all();
} }
auto stop() { void stop() {
{ {
std::lock_guard<std::mutex> lck(mutex_); std::lock_guard<std::mutex> lck(mutex_);
signal_ = true; signal_.store(true, std::memory_order_relaxed);
do_run_ = false; do_run_.store(false, std::memory_order_relaxed);
} }
condvar_.notify_all(); condvar_.notify_all();
auto handle = thread_.native_handle(); auto handle = thread_.native_handle();
@@ -118,8 +134,8 @@ class SleeperThread {
std::thread thread_; std::thread thread_;
std::condition_variable condvar_; std::condition_variable condvar_;
std::mutex mutex_; std::mutex mutex_;
bool do_run_ = true; std::atomic<bool> do_run_ = true;
bool signal_ = false; std::atomic<bool> signal_ = false;
sigc::connection connection_; sigc::connection connection_;
}; };

View File

@@ -182,6 +182,7 @@ Every entry in the *events* object consists of a *<event-name>* (typeof: *string
- *on-<status>-<state>* - *on-<status>-<state>*
- *on-<status>-<capacity>* - *on-<status>-<capacity>*
- *on-<status>*
Where: Where:
@@ -203,7 +204,9 @@ Where:
"events": { "events": {
"on-discharging-warning": "notify-send -u normal 'Low Battery'", "on-discharging-warning": "notify-send -u normal 'Low Battery'",
"on-discharging-critical": "notify-send -u critical 'Very Low Battery'", "on-discharging-critical": "notify-send -u critical 'Very Low Battery'",
"on-charging-100": "notify-send -u normal 'Battery Full!'" "on-charging-100": "notify-send -u normal 'Battery Full!'",
"on-discharging": "notify-send -u normal 'Power Switch' Discharging",
"on-charging": "notify-send -u normal 'Power Switch' Charging'"
}, },
"format": "{capacity}% {icon}", "format": "{capacity}% {icon}",
"format-icons": ["", "", "", "", ""], "format-icons": ["", "", "", "", ""],

View File

@@ -204,7 +204,7 @@ View all valid format options in *strftime(3)* or have a look https://en.cpprefe
``` ```
"clock": { "clock": {
"format": "{:%H:%M}  ", "format": "{:%H:%M}  ",
"format-alt": "{:%A, %B %d, %Y (%R)} ", "format-alt": "{:%A, %B %d, %Y (%R)} 󰃰 ",
"tooltip-format": "<tt><small>{calendar}</small></tt>", "tooltip-format": "<tt><small>{calendar}</small></tt>",
"calendar": { "calendar": {
"mode" : "year", "mode" : "year",
@@ -259,7 +259,19 @@ View all valid format options in *strftime(3)* or have a look https://en.cpprefe
"tooltip-format": "{tz_list}" "tooltip-format": "{tz_list}"
} }
``` ```
5. Simple calendar tooltip
```
"clock": {
"format": "{:%H:%M}",
"tooltip-format": "<tt>{calendar}</tt>",
"calendar": {
"format": {
"today": "<span color='#ffcc66'><b>{}</b></span>"
}
}
}
```
# STYLE # STYLE
- *#clock* - *#clock*
@@ -287,7 +299,7 @@ Example of working config
``` ```
"clock": { "clock": {
"format": "{:%H:%M}  ", "format": "{:%H:%M}  ",
"format-alt": "{:%A, %B %d, %Y (%R)} ", "format-alt": "{:%A, %B %d, %Y (%R)} 󰃰 ",
"tooltip-format": "\n<span size='9pt' font='WenQuanYi Zen Hei Mono'>{calendar}</span>", "tooltip-format": "\n<span size='9pt' font='WenQuanYi Zen Hei Mono'>{calendar}</span>",
"calendar": { "calendar": {
"mode" : "year", "mode" : "year",

View File

@@ -43,7 +43,7 @@ Feral Gamemode optimizations.
*glyph*: ++ *glyph*: ++
typeof: string ++ typeof: string ++
default: ++ default: 󰊴 ++
The string icon to display. Only visible if *use-icon* is set to false. The string icon to display. Only visible if *use-icon* is set to false.
*icon-name*: ++ *icon-name*: ++
@@ -82,7 +82,7 @@ Feral Gamemode optimizations.
"gamemode": { "gamemode": {
"format": "{glyph}", "format": "{glyph}",
"format-alt": "{glyph} {count}", "format-alt": "{glyph} {count}",
"glyph": "", "glyph": "󰊴",
"hide-not-running": true, "hide-not-running": true,
"use-icon": true, "use-icon": true,
"icon-name": "input-gaming-symbolic", "icon-name": "input-gaming-symbolic",

View File

@@ -130,6 +130,11 @@ This setting is ignored if *workspace-taskbar.enable* is set to true.
Otherwise, the workspace will open on the monitor where it was previously assigned. Otherwise, the workspace will open on the monitor where it was previously assigned.
Analog to using `focusworkspaceoncurrentmonitor` dispatcher instead of `workspace` in Hyprland. Analog to using `focusworkspaceoncurrentmonitor` dispatcher instead of `workspace` in Hyprland.
*enable-bar-scroll*: ++
typeof: bool ++
default: false ++
If set to false, you can't scroll to cycle throughout workspaces from the entire bar. If set to true this behaviour is enabled.
*ignore-workspaces*: ++ *ignore-workspaces*: ++
typeof: array ++ typeof: array ++
default: [] ++ default: [] ++

View File

@@ -26,7 +26,8 @@ The *image* module displays an image from a path.
*interval*: ++ *interval*: ++
typeof: integer or float ++ typeof: integer or float ++
The interval (in seconds) to re-render the image. ++ The interval (in seconds) to re-render the image. ++
Minimum value is 0.001 (1ms). Values smaller than 1ms will be set to 1ms. ++ If set to a positive value, the minimum is 0.001 (1ms). Values smaller than 1ms will be set to 1ms. ++
Zero or negative values are treated as "once". ++
This is useful if the contents of *path* changes. ++ This is useful if the contents of *path* changes. ++
If no *interval* is defined, the image will only be rendered once. If no *interval* is defined, the image will only be rendered once.

View File

@@ -178,8 +178,8 @@ to be selected when the corresponding audio device is muted. This applies to *de
"alsa_output.pci-0000_00_1f.3.analog-stereo": "", "alsa_output.pci-0000_00_1f.3.analog-stereo": "",
"alsa_output.pci-0000_00_1f.3.analog-stereo-muted": "", "alsa_output.pci-0000_00_1f.3.analog-stereo-muted": "",
"headphone": "", "headphone": "",
"hands-free": "", "hands-free": "󰂑",
"headset": "", "headset": "󰂑",
"phone": "", "phone": "",
"phone-muted": "", "phone-muted": "",
"portable": "", "portable": "",

View File

@@ -91,7 +91,7 @@ Addressed by *river/mode*
``` ```
"river/mode": { "river/mode": {
"format": " {}" "format": " {}"
} }
``` ```

View File

@@ -92,7 +92,7 @@ Addressed by *sway/mode*
``` ```
"sway/mode": { "sway/mode": {
"format": " {}", "format": " {}",
"max-length": 50 "max-length": 50
} }
``` ```

View File

@@ -19,7 +19,7 @@ Addressed by *systemd-failed-units*
*format-ok*: ++ *format-ok*: ++
typeof: string ++ typeof: string ++
This format is used when there is no failing units. This format is used when there are no failing units.
*user*: ++ *user*: ++
typeof: bool ++ typeof: bool ++
@@ -34,15 +34,30 @@ Addressed by *systemd-failed-units*
*hide-on-ok*: ++ *hide-on-ok*: ++
typeof: bool ++ typeof: bool ++
default: *true* ++ default: *true* ++
Option to hide this module when there is no failing units. Option to hide this module when there are no failed units.
*tooltip-format*: ++
typeof: string ++
default: *System: {system_state}\nUser: {user_state}\nFailed units ({nr_failed}):\n{failed_units_list}* ++
Tooltip format shown when there are failed units.
*tooltip-format-ok*: ++
typeof: string ++
default: *System: {system_state}\nUser: {user_state}* ++
Tooltip format used when there are no failed units.
*tooltip-unit-format*: ++
typeof: string ++
default: *{name}: {description}* ++
Format used to render each failed unit inside the tooltip. Each item is prefixed with a bullet.
*menu*: ++ *menu*: ++
typeof: string ++ typeof: string ++
Action that popups the menu. Action that pops up the menu.
*menu-file*: ++ *menu-file*: ++
typeof: string ++ typeof: string ++
Location of the menu descriptor file. There need to be an element of type Location of the menu descriptor file. There needs to be an element of type
GtkMenu with id *menu* GtkMenu with id *menu*
*menu-actions*: ++ *menu-actions*: ++
@@ -52,7 +67,7 @@ Addressed by *systemd-failed-units*
*expand*: ++ *expand*: ++
typeof: bool ++ typeof: bool ++
default: false ++ default: false ++
Enables this module to consume all left over space dynamically. Enables this module to consume all leftover space dynamically.
# FORMAT REPLACEMENTS # FORMAT REPLACEMENTS
@@ -62,11 +77,23 @@ Addressed by *systemd-failed-units*
*{nr_failed}*: Number of total failed units. *{nr_failed}*: Number of total failed units.
*{systemd_state}:* State of the systemd system session *{system_state}:* State of the systemd system session.
*{user_state}:* State of the systemd user session *{user_state}:* State of the systemd user session.
*{overall_state}:* Overall state of the systemd and user session. ("Ok" or "Degraded") *{overall_state}:* Overall state of the systemd and user session. ("ok" or "degraded")
*{failed_units_list}:* Bulleted list of failed units using *tooltip-unit-format*. Empty when
there are no failed units.
The *tooltip-unit-format* string supports the following replacements:
*{name}*: Unit name ++
*{description}*: Unit description ++
*{load_state}*: Unit load state ++
*{active_state}*: Unit active state ++
*{sub_state}*: Unit sub state ++
*{scope}*: Either *system* or *user* depending on where the unit originated
# EXAMPLES # EXAMPLES
@@ -77,6 +104,8 @@ Addressed by *systemd-failed-units*
"format-ok": "✓", "format-ok": "✓",
"system": true, "system": true,
"user": false, "user": false,
"tooltip-format": "{nr_failed} failed units:\n{failed_units_list}",
"tooltip-unit-format": "{scope}: {name} ({active_state})",
} }
``` ```

View File

@@ -128,7 +128,7 @@ The *wireplumber* module displays the current volume reported by WirePlumber.
``` ```
"wireplumber#sink": { "wireplumber#sink": {
"format": "{volume}% {icon}", "format": "{volume}% {icon}",
"format-muted": "", "format-muted": "󰅶",
"format-icons": ["", "", ""], "format-icons": ["", "", ""],
"on-click": "helvum", "on-click": "helvum",
"on-click-right": "wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle", "on-click-right": "wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle",

View File

@@ -36,7 +36,7 @@ The visual display elements for waybar use a CSS stylesheet, see *waybar-styles(
*expand-right* ++ *expand-right* ++
typeof: bool ++ typeof: bool ++
default: false ++ default: false ++
Enables the modules-left to consume all left over space dynamically. Enables the modules-right to consume all left over space dynamically.
*layer* ++ *layer* ++
typeof: string ++ typeof: string ++
@@ -363,6 +363,11 @@ A group may hide all but one element, showing them only on mouse hover. In order
default: false ++ default: false ++
Whether left click should reveal the content rather than mouse over. Note that grouped modules may still process their own on-click events. Whether left click should reveal the content rather than mouse over. Note that grouped modules may still process their own on-click events.
*start-expanded*: ++
typeof: bool ++
default: false ++
Defines whether the drawer should initialize in an expanded state.
*transition-left-to-right*: ++ *transition-left-to-right*: ++
typeof: bool ++ typeof: bool ++
default: true ++ default: true ++

View File

@@ -185,7 +185,8 @@ src_files = files(
'src/util/gtk_icon.cpp', 'src/util/gtk_icon.cpp',
'src/util/icon_loader.cpp', 'src/util/icon_loader.cpp',
'src/util/regex_collection.cpp', 'src/util/regex_collection.cpp',
'src/util/css_reload_helper.cpp' 'src/util/css_reload_helper.cpp',
'src/util/transform_8bit_to_rgba.cpp'
) )
man_files = files( man_files = files(

View File

@@ -6,13 +6,13 @@
}: }:
let let
libcava = rec { libcava = rec {
version = "0.10.7-beta"; version = "0.10.7";
src = pkgs.fetchFromGitHub { src = pkgs.fetchFromGitHub {
owner = "LukashonakV"; owner = "LukashonakV";
repo = "cava"; repo = "cava";
# NOTE: Needs to match the cava.wrap # NOTE: Needs to match the cava.wrap
tag = "v${version}"; tag = "${version}";
hash = "sha256-IX1B375gTwVDRjpRfwKGuzTAZOV2pgDWzUd4bW2cTDU="; hash = "sha256-zkyj1vBzHtoypX4Bxdh1Vmwh967DKKxN751v79hzmgQ=";
}; };
}; };
in in

View File

@@ -128,7 +128,7 @@
"critical-threshold": 80, "critical-threshold": 80,
// "format-critical": "{temperatureC}°C {icon}", // "format-critical": "{temperatureC}°C {icon}",
"format": "{temperatureC}°C {icon}", "format": "{temperatureC}°C {icon}",
"format-icons": ["", "", ""] "format-icons": ["󰉬", "", "󰉪"]
}, },
"backlight": { "backlight": {
// "device": "acpi_video1", // "device": "acpi_video1",
@@ -143,7 +143,7 @@
}, },
"format": "{capacity}% {icon}", "format": "{capacity}% {icon}",
"format-full": "{capacity}% {icon}", "format-full": "{capacity}% {icon}",
"format-charging": "{capacity}% ", "format-charging": "{capacity}% 󰃨",
"format-plugged": "{capacity}% ", "format-plugged": "{capacity}% ",
"format-alt": "{time} {icon}", "format-alt": "{time} {icon}",
// "format-good": "", // An empty format will hide the module // "format-good": "", // An empty format will hide the module
@@ -167,9 +167,9 @@
"network": { "network": {
// "interface": "wlp2*", // (Optional) To force the use of this interface // "interface": "wlp2*", // (Optional) To force the use of this interface
"format-wifi": "{essid} ({signalStrength}%) ", "format-wifi": "{essid} ({signalStrength}%) ",
"format-ethernet": "{ipaddr}/{cidr} ", "format-ethernet": "{ipaddr}/{cidr} 󰊗",
"tooltip-format": "{ifname} via {gwaddr} ", "tooltip-format": "{ifname} via {gwaddr} 󰊗",
"format-linked": "{ifname} (No IP) ", "format-linked": "{ifname} (No IP) 󰊗",
"format-disconnected": "Disconnected ⚠", "format-disconnected": "Disconnected ⚠",
"format-alt": "{ifname}: {ipaddr}/{cidr}" "format-alt": "{ifname}: {ipaddr}/{cidr}"
}, },
@@ -177,14 +177,14 @@
// "scroll-step": 1, // %, can be a float // "scroll-step": 1, // %, can be a float
"format": "{volume}% {icon} {format_source}", "format": "{volume}% {icon} {format_source}",
"format-bluetooth": "{volume}% {icon} {format_source}", "format-bluetooth": "{volume}% {icon} {format_source}",
"format-bluetooth-muted": " {icon} {format_source}", "format-bluetooth-muted": "󰅶 {icon} {format_source}",
"format-muted": " {format_source}", "format-muted": "󰅶 {format_source}",
"format-source": "{volume}% ", "format-source": "{volume}% ",
"format-source-muted": "", "format-source-muted": "",
"format-icons": { "format-icons": {
"headphone": "", "headphone": "",
"hands-free": "", "hands-free": "󰂑",
"headset": "", "headset": "󰂑",
"phone": "", "phone": "",
"portable": "", "portable": "",
"car": "", "car": "",

View File

@@ -151,19 +151,24 @@ void AAppIconLabel::updateAppIconName(const std::string& app_identifier,
} }
void AAppIconLabel::updateAppIcon() { void AAppIconLabel::updateAppIcon() {
if (update_app_icon_) { if (update_app_icon_ || (!iconEnabled() && image_.get_visible())) {
update_app_icon_ = false; update_app_icon_ = false;
if (app_icon_name_.empty()) { if (app_icon_name_.empty()) {
image_.set_visible(false); image_.set_visible(false);
} else if (app_icon_name_.front() == '/') { } else if (app_icon_name_.front() == '/') {
auto pixbuf = Gdk::Pixbuf::create_from_file(app_icon_name_); try {
int scaled_icon_size = app_icon_size_ * image_.get_scale_factor(); int scaled_icon_size = app_icon_size_ * image_.get_scale_factor();
pixbuf = Gdk::Pixbuf::create_from_file(app_icon_name_, scaled_icon_size, scaled_icon_size); auto pixbuf =
Gdk::Pixbuf::create_from_file(app_icon_name_, scaled_icon_size, scaled_icon_size);
auto surface = Gdk::Cairo::create_surface_from_pixbuf(pixbuf, image_.get_scale_factor(), auto surface = Gdk::Cairo::create_surface_from_pixbuf(pixbuf, image_.get_scale_factor(),
image_.get_window()); image_.get_window());
image_.set(surface); image_.set(surface);
image_.set_visible(true); image_.set_visible(true);
} catch (const Glib::Exception& e) {
spdlog::warn("Failed to load app icon {}: {}", app_icon_name_, std::string(e.what()));
image_.set_visible(false);
}
} else { } else {
image_.set_from_icon_name(app_icon_name_, Gtk::ICON_SIZE_INVALID); image_.set_from_icon_name(app_icon_name_, Gtk::ICON_SIZE_INVALID);
image_.set_visible(true); image_.set_visible(true);

View File

@@ -26,7 +26,7 @@ ALabel::ALabel(const Json::Value& config, const std::string& name, const std::st
: std::chrono::milliseconds( : std::chrono::milliseconds(
(config_["interval"].isNumeric() (config_["interval"].isNumeric()
? std::max(1L, // Minimum 1ms due to millisecond precision ? std::max(1L, // Minimum 1ms due to millisecond precision
static_cast<long>(config_["interval"].asDouble()) * 1000) static_cast<long>(config_["interval"].asDouble() * 1000))
: 1000 * (long)interval))), : 1000 * (long)interval))),
default_format_(format_) { default_format_(format_) {
label_.set_name(name); label_.set_name(name);
@@ -91,13 +91,17 @@ ALabel::ALabel(const Json::Value& config, const std::string& name, const std::st
// Make the GtkBuilder and check for errors in his parsing // Make the GtkBuilder and check for errors in his parsing
if (gtk_builder_add_from_string(builder, fileContent.str().c_str(), -1, nullptr) == 0U) { if (gtk_builder_add_from_string(builder, fileContent.str().c_str(), -1, nullptr) == 0U) {
g_object_unref(builder);
throw std::runtime_error("Error found in the file " + menuFile); throw std::runtime_error("Error found in the file " + menuFile);
} }
menu_ = gtk_builder_get_object(builder, "menu"); menu_ = gtk_builder_get_object(builder, "menu");
if (menu_ == nullptr) { if (menu_ == nullptr) {
g_object_unref(builder);
throw std::runtime_error("Failed to get 'menu' object from GtkBuilder"); throw std::runtime_error("Failed to get 'menu' object from GtkBuilder");
} }
// Keep the menu alive after dropping the transient GtkBuilder.
g_object_ref(menu_);
submenus_ = std::map<std::string, GtkMenuItem*>(); submenus_ = std::map<std::string, GtkMenuItem*>();
menuActionsMap_ = std::map<std::string, std::string>(); menuActionsMap_ = std::map<std::string, std::string>();
@@ -105,11 +109,17 @@ ALabel::ALabel(const Json::Value& config, const std::string& name, const std::st
for (Json::Value::const_iterator it = config_["menu-actions"].begin(); for (Json::Value::const_iterator it = config_["menu-actions"].begin();
it != config_["menu-actions"].end(); ++it) { it != config_["menu-actions"].end(); ++it) {
std::string key = it.key().asString(); std::string key = it.key().asString();
submenus_[key] = GTK_MENU_ITEM(gtk_builder_get_object(builder, key.c_str())); auto* item = gtk_builder_get_object(builder, key.c_str());
if (item == nullptr) {
spdlog::warn("Menu item '{}' not found in builder file", key);
continue;
}
submenus_[key] = GTK_MENU_ITEM(item);
menuActionsMap_[key] = it->asString(); menuActionsMap_[key] = it->asString();
g_signal_connect(submenus_[key], "activate", G_CALLBACK(handleGtkMenuEvent), g_signal_connect(submenus_[key], "activate", G_CALLBACK(handleGtkMenuEvent),
(gpointer)menuActionsMap_[key].c_str()); (gpointer)menuActionsMap_[key].c_str());
} }
g_object_unref(builder);
} catch (std::runtime_error& e) { } catch (std::runtime_error& e) {
spdlog::warn("Error while creating the menu : {}. Menu popup not activated.", e.what()); spdlog::warn("Error while creating the menu : {}. Menu popup not activated.", e.what());
} }
@@ -141,7 +151,8 @@ std::string ALabel::getIcon(uint16_t percentage, const std::string& alt, uint16_
if (format_icons.isArray()) { if (format_icons.isArray()) {
auto size = format_icons.size(); auto size = format_icons.size();
if (size != 0U) { if (size != 0U) {
auto idx = std::clamp(percentage / ((max == 0 ? 100 : max) / size), 0U, size - 1); auto divisor = std::max(1U, (max == 0 ? 100U : static_cast<unsigned>(max)) / size);
auto idx = std::clamp(percentage / divisor, 0U, size - 1);
format_icons = format_icons[idx]; format_icons = format_icons[idx];
} }
} }
@@ -167,7 +178,8 @@ std::string ALabel::getIcon(uint16_t percentage, const std::vector<std::string>&
if (format_icons.isArray()) { if (format_icons.isArray()) {
auto size = format_icons.size(); auto size = format_icons.size();
if (size != 0U) { if (size != 0U) {
auto idx = std::clamp(percentage / ((max == 0 ? 100 : max) / size), 0U, size - 1); auto divisor = std::max(1U, (max == 0 ? 100U : static_cast<unsigned>(max)) / size);
auto idx = std::clamp(percentage / divisor, 0U, size - 1);
format_icons = format_icons[idx]; format_icons = format_icons[idx];
} }
} }

View File

@@ -88,6 +88,10 @@ AModule::~AModule() {
killpg(pid, SIGTERM); killpg(pid, SIGTERM);
} }
} }
if (menu_ != nullptr) {
g_object_unref(menu_);
menu_ = nullptr;
}
} }
auto AModule::update() -> void { auto AModule::update() -> void {
@@ -166,9 +170,9 @@ bool AModule::handleUserEvent(GdkEventButton* const& e) {
} }
// Check that a menu has been configured // Check that a menu has been configured
if (config_["menu"].isString()) { if (rec != eventMap_.cend() && config_["menu"].isString()) {
// Check if the event is the one specified for the "menu" option // Check if the event is the one specified for the "menu" option
if (rec->second == config_["menu"].asString()) { if (rec->second == config_["menu"].asString() && menu_ != nullptr) {
// Popup the menu // Popup the menu
gtk_widget_show_all(GTK_WIDGET(menu_)); gtk_widget_show_all(GTK_WIDGET(menu_));
gtk_menu_popup_at_pointer(GTK_MENU(menu_), reinterpret_cast<GdkEvent*>(e)); gtk_menu_popup_at_pointer(GTK_MENU(menu_), reinterpret_cast<GdkEvent*>(e));

View File

@@ -11,6 +11,7 @@
#include "idle-inhibit-unstable-v1-client-protocol.h" #include "idle-inhibit-unstable-v1-client-protocol.h"
#include "util/clara.hpp" #include "util/clara.hpp"
#include "util/format.hpp" #include "util/format.hpp"
#include "util/hex_checker.hpp"
waybar::Client* waybar::Client::inst() { waybar::Client* waybar::Client::inst() {
static auto* c = new Client(); static auto* c = new Client();
@@ -20,11 +21,23 @@ waybar::Client* waybar::Client::inst() {
void waybar::Client::handleGlobal(void* data, struct wl_registry* registry, uint32_t name, void waybar::Client::handleGlobal(void* data, struct wl_registry* registry, uint32_t name,
const char* interface, uint32_t version) { const char* interface, uint32_t version) {
auto* client = static_cast<Client*>(data); auto* client = static_cast<Client*>(data);
if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0 && if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0 &&
version >= ZXDG_OUTPUT_V1_NAME_SINCE_VERSION) { version >= ZXDG_OUTPUT_V1_NAME_SINCE_VERSION) {
if (client->xdg_output_manager != nullptr) {
zxdg_output_manager_v1_destroy(client->xdg_output_manager);
client->xdg_output_manager = nullptr;
}
client->xdg_output_manager = static_cast<struct zxdg_output_manager_v1*>(wl_registry_bind( client->xdg_output_manager = static_cast<struct zxdg_output_manager_v1*>(wl_registry_bind(
registry, name, &zxdg_output_manager_v1_interface, ZXDG_OUTPUT_V1_NAME_SINCE_VERSION)); registry, name, &zxdg_output_manager_v1_interface, ZXDG_OUTPUT_V1_NAME_SINCE_VERSION));
} else if (strcmp(interface, zwp_idle_inhibit_manager_v1_interface.name) == 0) { } else if (strcmp(interface, zwp_idle_inhibit_manager_v1_interface.name) == 0) {
if (client->idle_inhibit_manager != nullptr) {
zwp_idle_inhibit_manager_v1_destroy(client->idle_inhibit_manager);
client->idle_inhibit_manager = nullptr;
}
client->idle_inhibit_manager = static_cast<struct zwp_idle_inhibit_manager_v1*>( client->idle_inhibit_manager = static_cast<struct zwp_idle_inhibit_manager_v1*>(
wl_registry_bind(registry, name, &zwp_idle_inhibit_manager_v1_interface, 1)); wl_registry_bind(registry, name, &zwp_idle_inhibit_manager_v1_interface, 1));
} }
@@ -195,11 +208,15 @@ auto waybar::Client::setupCss(const std::string& css_file) -> void {
} }
css_provider_ = Gtk::CssProvider::create(); css_provider_ = Gtk::CssProvider::create();
auto [modified_css, was_transformed] = transform_8bit_to_hex(css_file);
if (was_transformed) {
css_provider_->load_from_data(modified_css);
} else {
if (!css_provider_->load_from_path(css_file)) { if (!css_provider_->load_from_path(css_file)) {
css_provider_.reset(); css_provider_.reset();
throw std::runtime_error("Can't open style file"); throw std::runtime_error("Can't open style file");
} }
}
Gtk::StyleContext::add_provider_for_screen(screen, css_provider_, Gtk::StyleContext::add_provider_for_screen(screen, css_provider_,
GTK_STYLE_PROVIDER_PRIORITY_USER); GTK_STYLE_PROVIDER_PRIORITY_USER);
} }

View File

@@ -67,13 +67,25 @@ Group::Group(const std::string& name, const std::string& id, const Json::Value&
const bool left_to_right = (drawer_config["transition-left-to-right"].isBool() const bool left_to_right = (drawer_config["transition-left-to-right"].isBool()
? drawer_config["transition-left-to-right"].asBool() ? drawer_config["transition-left-to-right"].asBool()
: true); : true);
click_to_reveal = drawer_config["click-to-reveal"].asBool() || toggle_signal.has_value(); click_to_reveal = drawer_config["click-to-reveal"].asBool();
if (click_to_reveal && toggle_signal) {
throw std::runtime_error(
R"(A group cannot have both "click-to-reveal" and "toggle-signal".)");
}
const bool start_expanded =
(drawer_config["start-expanded"].isBool() ? drawer_config["start-expanded"].asBool()
: false);
auto transition_type = getPreferredTransitionType(vertical); auto transition_type = getPreferredTransitionType(vertical);
revealer.set_transition_type(transition_type); revealer.set_transition_type(transition_type);
revealer.set_transition_duration(transition_duration); revealer.set_transition_duration(transition_duration);
revealer.set_reveal_child(false); revealer.set_reveal_child(start_expanded);
if (start_expanded) {
box.set_state_flags(Gtk::StateFlags::STATE_FLAG_PRELIGHT);
}
revealer.get_style_context()->add_class("drawer"); revealer.get_style_context()->add_class("drawer");
@@ -108,14 +120,14 @@ void Group::hide_group() {
} }
bool Group::handleMouseEnter(GdkEventCrossing* const& e) { bool Group::handleMouseEnter(GdkEventCrossing* const& e) {
if (!click_to_reveal) { if (!click_to_reveal && !toggle_signal) {
show_group(); show_group();
} }
return false; return false;
} }
bool Group::handleMouseLeave(GdkEventCrossing* const& e) { bool Group::handleMouseLeave(GdkEventCrossing* const& e) {
if (!click_to_reveal && e->detail != GDK_NOTIFY_INFERIOR) { if (!click_to_reveal && e->detail != GDK_NOTIFY_INFERIOR && !toggle_signal) {
hide_group(); hide_group();
} }
return false; return false;
@@ -139,6 +151,11 @@ void Group::refresh(int sig) {
} }
} }
bool Group::handleScroll(GdkEventScroll* e) {
// no scroll.
return true;
}
Gtk::Box& Group::getBox() { return is_drawer ? (is_first_widget ? box : revealer_box) : box; } Gtk::Box& Group::getBox() { return is_drawer ? (is_first_widget ? box : revealer_box) : box; }
void Group::addWidget(Gtk::Widget& widget) { void Group::addWidget(Gtk::Widget& widget) {

View File

@@ -33,7 +33,10 @@ static void writeSignalToPipe(int signum) {
// to `signal_handler`. // to `signal_handler`.
static void catchSignals(waybar::SafeSignal<int>& signal_handler) { static void catchSignals(waybar::SafeSignal<int>& signal_handler) {
int fd[2]; int fd[2];
pipe(fd); if (pipe(fd) != 0) {
spdlog::error("Failed to create signal pipe: {}", strerror(errno));
return;
}
int signal_pipe_read_fd = fd[0]; int signal_pipe_read_fd = fd[0];
signal_pipe_write_fd = fd[1]; signal_pipe_write_fd = fd[1];
@@ -138,15 +141,16 @@ static void handleSignalMainThread(int signum, bool& reload) {
break; break;
case SIGCHLD: case SIGCHLD:
spdlog::debug("Received SIGCHLD in signalThread"); spdlog::debug("Received SIGCHLD in signalThread");
if (!reap.empty()) { {
reap_mtx.lock(); std::lock_guard<std::mutex> lock(reap_mtx);
for (auto it = reap.begin(); it != reap.end(); ++it) { for (auto it = reap.begin(); it != reap.end();) {
if (waitpid(*it, nullptr, WNOHANG) == *it) { if (waitpid(*it, nullptr, WNOHANG) == *it) {
spdlog::debug("Reaped child with PID: {}", *it); spdlog::debug("Reaped child with PID: {}", *it);
it = reap.erase(it); it = reap.erase(it);
} else {
++it;
} }
} }
reap_mtx.unlock();
} }
break; break;
default: default:

View File

@@ -58,11 +58,11 @@ auto waybar::modules::Backlight::update() -> void {
tooltip_format = config_["tooltip-format"].asString(); tooltip_format = config_["tooltip-format"].asString();
} }
if (!tooltip_format.empty()) { if (!tooltip_format.empty()) {
label_.set_tooltip_text(fmt::format(fmt::runtime(tooltip_format), label_.set_tooltip_markup(fmt::format(fmt::runtime(tooltip_format),
fmt::arg("percent", percent), fmt::arg("percent", percent),
fmt::arg("icon", getIcon(percent)))); fmt::arg("icon", getIcon(percent))));
} else { } else {
label_.set_tooltip_text(desc); label_.set_tooltip_markup(desc);
} }
} }
} else { } else {

View File

@@ -139,7 +139,9 @@ void waybar::modules::Battery::refreshBatteries() {
auto event_path = (node.path() / "uevent"); auto event_path = (node.path() / "uevent");
auto wd = inotify_add_watch(battery_watch_fd_, event_path.c_str(), IN_ACCESS); auto wd = inotify_add_watch(battery_watch_fd_, event_path.c_str(), IN_ACCESS);
if (wd < 0) { if (wd < 0) {
throw std::runtime_error("Could not watch events for " + node.path().string()); spdlog::warn("Could not watch events for {} (device may have been removed)",
node.path().string());
continue;
} }
batteries_[node.path()] = wd; batteries_[node.path()] = wd;
} }
@@ -686,7 +688,7 @@ auto waybar::modules::Battery::update() -> void {
status = getAdapterStatus(capacity); status = getAdapterStatus(capacity);
} }
auto status_pretty = status; auto status_pretty = status;
puts(status.c_str());
// Transform to lowercase and replace space with dash // Transform to lowercase and replace space with dash
std::ranges::transform(status.begin(), status.end(), status.begin(), std::ranges::transform(status.begin(), status.end(), status.begin(),
[](char ch) { return ch == ' ' ? '-' : std::tolower(ch); }); [](char ch) { return ch == ' ' ? '-' : std::tolower(ch); });
@@ -790,16 +792,19 @@ void waybar::modules::Battery::processEvents(std::string& state, std::string& st
if (!events.isObject() || events.empty()) { if (!events.isObject() || events.empty()) {
return; return;
} }
std::string event_name = fmt::format("on-{}-{}", status == "discharging" ? status : "charging", auto exec = [](Json::Value const& event) {
state.empty() ? std::to_string(capacity) : state); if (!event.isString()) return;
if (auto command = event.asString(); !command.empty()) {
util::command::exec(command, "");
}
};
std::string status_name = status == "discharging" ? "on-discharging" : "on-charging";
std::string event_name = status_name + '-' + (state.empty() ? std::to_string(capacity) : state);
if (last_event_ != event_name) { if (last_event_ != event_name) {
spdlog::debug("battery: triggering event {}", event_name); spdlog::debug("battery: triggering event {}", event_name);
if (events[event_name].isString()) { exec(events[event_name]);
std::string exec = events[event_name].asString(); if (!last_event_.empty() && last_event_[3] != event_name[3]) {
// Execute the command if it is not empty exec(events[status_name]);
if (!exec.empty()) {
util::command::exec(exec, "");
}
} }
last_event_ = event_name; last_event_ = event_name;
} }

View File

@@ -111,13 +111,16 @@ waybar::modules::Bluetooth::Bluetooth(const std::string& id, const Json::Value&
findConnectedDevices(cur_controller_->path, connected_devices_); findConnectedDevices(cur_controller_->path, connected_devices_);
} }
if (manager_) {
g_signal_connect(manager_.get(), "object-added", G_CALLBACK(onObjectAdded), this); g_signal_connect(manager_.get(), "object-added", G_CALLBACK(onObjectAdded), this);
g_signal_connect(manager_.get(), "object-removed", G_CALLBACK(onObjectRemoved), this); g_signal_connect(manager_.get(), "object-removed", G_CALLBACK(onObjectRemoved), this);
g_signal_connect(manager_.get(), "interface-proxy-properties-changed", g_signal_connect(manager_.get(), "interface-proxy-properties-changed",
G_CALLBACK(onInterfaceProxyPropertiesChanged), this); G_CALLBACK(onInterfaceProxyPropertiesChanged), this);
g_signal_connect(manager_.get(), "interface-added", G_CALLBACK(onInterfaceAddedOrRemoved), this); g_signal_connect(manager_.get(), "interface-added", G_CALLBACK(onInterfaceAddedOrRemoved),
this);
g_signal_connect(manager_.get(), "interface-removed", G_CALLBACK(onInterfaceAddedOrRemoved), g_signal_connect(manager_.get(), "interface-removed", G_CALLBACK(onInterfaceAddedOrRemoved),
this); this);
}
#ifdef WANT_RFKILL #ifdef WANT_RFKILL
rfkill_.on_update.connect(sigc::hide(sigc::mem_fun(*this, &Bluetooth::update))); rfkill_.on_update.connect(sigc::hide(sigc::mem_fun(*this, &Bluetooth::update)));
@@ -446,6 +449,10 @@ auto waybar::modules::Bluetooth::getControllerProperties(GDBusObject* object,
auto waybar::modules::Bluetooth::findCurController() -> std::optional<ControllerInfo> { auto waybar::modules::Bluetooth::findCurController() -> std::optional<ControllerInfo> {
std::optional<ControllerInfo> controller_info; std::optional<ControllerInfo> controller_info;
if (!manager_) {
return controller_info;
}
GList* objects = g_dbus_object_manager_get_objects(manager_.get()); GList* objects = g_dbus_object_manager_get_objects(manager_.get());
for (GList* l = objects; l != NULL; l = l->next) { for (GList* l = objects; l != NULL; l = l->next) {
GDBusObject* object = G_DBUS_OBJECT(l->data); GDBusObject* object = G_DBUS_OBJECT(l->data);
@@ -465,6 +472,9 @@ auto waybar::modules::Bluetooth::findCurController() -> std::optional<Controller
auto waybar::modules::Bluetooth::findConnectedDevices(const std::string& cur_controller_path, auto waybar::modules::Bluetooth::findConnectedDevices(const std::string& cur_controller_path,
std::vector<DeviceInfo>& connected_devices) std::vector<DeviceInfo>& connected_devices)
-> void { -> void {
if (!manager_) {
return;
}
GList* objects = g_dbus_object_manager_get_objects(manager_.get()); GList* objects = g_dbus_object_manager_get_objects(manager_.get());
for (GList* l = objects; l != NULL; l = l->next) { for (GList* l = objects; l != NULL; l = l->next) {
GDBusObject* object = G_DBUS_OBJECT(l->data); GDBusObject* object = G_DBUS_OBJECT(l->data);

View File

@@ -26,6 +26,7 @@ void waybar::modules::cava::Cava::pause_resume() { backend_->doPauseResume(); }
auto waybar::modules::cava::Cava::onUpdate(const std::string& input) -> void { auto waybar::modules::cava::Cava::onUpdate(const std::string& input) -> void {
Glib::signal_idle().connect_once([this, input]() { Glib::signal_idle().connect_once([this, input]() {
if (silence_) { if (silence_) {
silence_ = false;
label_.get_style_context()->remove_class("silent"); label_.get_style_context()->remove_class("silent");
if (!label_.get_style_context()->has_class("updated")) if (!label_.get_style_context()->has_class("updated"))
label_.get_style_context()->add_class("updated"); label_.get_style_context()->add_class("updated");
@@ -38,7 +39,6 @@ auto waybar::modules::cava::Cava::onUpdate(const std::string& input) -> void {
label_.show(); label_.show();
ALabel::update(); ALabel::update();
}); });
silence_ = false;
} }
auto waybar::modules::cava::Cava::onSilence() -> void { auto waybar::modules::cava::Cava::onSilence() -> void {

View File

@@ -218,7 +218,7 @@ void waybar::modules::cava::CavaBackend::loadConfig() {
prm_.input = ::cava::input_method_by_name(config_["method"].asString().c_str()); prm_.input = ::cava::input_method_by_name(config_["method"].asString().c_str());
if (config_["source"].isString()) { if (config_["source"].isString()) {
if (prm_.audio_source) free(prm_.audio_source); if (prm_.audio_source) free(prm_.audio_source);
prm_.audio_source = config_["source"].asString().data(); prm_.audio_source = strdup(config_["source"].asString().c_str());
} }
if (config_["sample_rate"].isNumeric()) prm_.samplerate = config_["sample_rate"].asLargestInt(); if (config_["sample_rate"].isNumeric()) prm_.samplerate = config_["sample_rate"].asLargestInt();
if (config_["sample_bits"].isInt()) prm_.samplebits = config_["sample_bits"].asInt(); if (config_["sample_bits"].isInt()) prm_.samplebits = config_["sample_bits"].asInt();

View File

@@ -35,7 +35,7 @@ CFFI::CFFI(const std::string& name, const std::string& id, const Json::Value& co
throw std::runtime_error{std::string{"Missing wbcffi_init function: "} + dlerror()}; throw std::runtime_error{std::string{"Missing wbcffi_init function: "} + dlerror()};
} }
hooks_.deinit = reinterpret_cast<DenitFn*>(dlsym(handle, "wbcffi_deinit")); hooks_.deinit = reinterpret_cast<DenitFn*>(dlsym(handle, "wbcffi_deinit"));
if (!hooks_.init) { if (!hooks_.deinit) {
throw std::runtime_error{std::string{"Missing wbcffi_deinit function: "} + dlerror()}; throw std::runtime_error{std::string{"Missing wbcffi_deinit function: "} + dlerror()};
} }
// Optional functions // Optional functions
@@ -71,6 +71,7 @@ CFFI::CFFI(const std::string& name, const std::string& id, const Json::Value& co
// Prepare config_entries array // Prepare config_entries array
std::vector<ffi::wbcffi_config_entry> config_entries; std::vector<ffi::wbcffi_config_entry> config_entries;
config_entries.reserve(keys.size());
for (size_t i = 0; i < keys.size(); i++) { for (size_t i = 0; i < keys.size(); i++) {
config_entries.push_back({keys[i].c_str(), config_entries_stringstor[i].c_str()}); config_entries.push_back({keys[i].c_str(), config_entries_stringstor[i].c_str()});
} }

View File

@@ -26,9 +26,7 @@ auto waybar::modules::Cpu::update() -> void {
auto [load1, load5, load15] = Load::getLoad(); auto [load1, load5, load15] = Load::getLoad();
auto [cpu_usage, tooltip] = CpuUsage::getCpuUsage(prev_times_); auto [cpu_usage, tooltip] = CpuUsage::getCpuUsage(prev_times_);
auto [max_frequency, min_frequency, avg_frequency] = CpuFrequency::getCpuFrequency(); auto [max_frequency, min_frequency, avg_frequency] = CpuFrequency::getCpuFrequency();
if (tooltipEnabled()) {
label_.set_tooltip_text(tooltip);
}
auto format = format_; auto format = format_;
auto total_usage = cpu_usage.empty() ? 0 : cpu_usage[0]; auto total_usage = cpu_usage.empty() ? 0 : cpu_usage[0];
auto state = getState(total_usage); auto state = getState(total_usage);
@@ -48,14 +46,25 @@ auto waybar::modules::Cpu::update() -> void {
store.push_back(fmt::arg("max_frequency", max_frequency)); store.push_back(fmt::arg("max_frequency", max_frequency));
store.push_back(fmt::arg("min_frequency", min_frequency)); store.push_back(fmt::arg("min_frequency", min_frequency));
store.push_back(fmt::arg("avg_frequency", avg_frequency)); store.push_back(fmt::arg("avg_frequency", avg_frequency));
std::vector<std::string> arg_names;
arg_names.reserve(cpu_usage.size() * 2);
for (size_t i = 1; i < cpu_usage.size(); ++i) { for (size_t i = 1; i < cpu_usage.size(); ++i) {
auto core_i = i - 1; auto core_i = i - 1;
auto core_format = fmt::format("usage{}", core_i); arg_names.push_back(fmt::format("usage{}", core_i));
store.push_back(fmt::arg(core_format.c_str(), cpu_usage[i])); store.push_back(fmt::arg(arg_names.back().c_str(), cpu_usage[i]));
auto icon_format = fmt::format("icon{}", core_i); arg_names.push_back(fmt::format("icon{}", core_i));
store.push_back(fmt::arg(icon_format.c_str(), getIcon(cpu_usage[i], icons))); store.push_back(fmt::arg(arg_names.back().c_str(), getIcon(cpu_usage[i], icons)));
} }
label_.set_markup(fmt::vformat(format, store)); label_.set_markup(fmt::vformat(format, store));
if (tooltipEnabled()) {
if (config_["tooltip-format"].isString()) {
tooltip = config_["tooltip-format"].asString();
label_.set_tooltip_markup(fmt::vformat(tooltip, store));
} else {
label_.set_tooltip_markup(tooltip);
}
}
} }
// Call parent update // Call parent update

View File

@@ -20,12 +20,7 @@ waybar::modules::CpuFrequency::CpuFrequency(const std::string& id, const Json::V
auto waybar::modules::CpuFrequency::update() -> void { auto waybar::modules::CpuFrequency::update() -> void {
// TODO: as creating dynamic fmt::arg arrays is buggy we have to calc both // TODO: as creating dynamic fmt::arg arrays is buggy we have to calc both
auto [max_frequency, min_frequency, avg_frequency] = CpuFrequency::getCpuFrequency(); auto [max_frequency, min_frequency, avg_frequency] = CpuFrequency::getCpuFrequency();
if (tooltipEnabled()) {
auto tooltip =
fmt::format("Minimum frequency: {}\nAverage frequency: {}\nMaximum frequency: {}\n",
min_frequency, avg_frequency, max_frequency);
label_.set_tooltip_text(tooltip);
}
auto format = format_; auto format = format_;
auto state = getState(avg_frequency); auto state = getState(avg_frequency);
if (!state.empty() && config_["format-" + state].isString()) { if (!state.empty() && config_["format-" + state].isString()) {
@@ -43,6 +38,18 @@ auto waybar::modules::CpuFrequency::update() -> void {
store.push_back(fmt::arg("min_frequency", min_frequency)); store.push_back(fmt::arg("min_frequency", min_frequency));
store.push_back(fmt::arg("avg_frequency", avg_frequency)); store.push_back(fmt::arg("avg_frequency", avg_frequency));
label_.set_markup(fmt::vformat(format, store)); label_.set_markup(fmt::vformat(format, store));
if (tooltipEnabled()) {
std::string tooltip;
if (config_["tooltip-format"].isString()) {
tooltip = config_["tooltip-format"].asString();
label_.set_tooltip_markup(fmt::vformat(tooltip, store));
} else {
tooltip = "Minimum frequency: {}\nAverage frequency: {}\nMaximum frequency: {}\n";
label_.set_tooltip_markup(
fmt::format(fmt::runtime(tooltip), min_frequency, avg_frequency, max_frequency));
}
}
} }
// Call parent update // Call parent update

View File

@@ -26,7 +26,7 @@ std::vector<float> waybar::modules::CpuFrequency::parseCpuFrequencies() {
if (std::filesystem::exists(cpufreq_dir)) { if (std::filesystem::exists(cpufreq_dir)) {
std::vector<std::string> frequency_files = {"/cpuinfo_min_freq", "/cpuinfo_max_freq"}; std::vector<std::string> frequency_files = {"/cpuinfo_min_freq", "/cpuinfo_max_freq"};
for (auto& p : std::filesystem::directory_iterator(cpufreq_dir)) { for (auto& p : std::filesystem::directory_iterator(cpufreq_dir)) {
for (auto freq_file : frequency_files) { for (const auto& freq_file : frequency_files) {
std::string freq_file_path = p.path().string() + freq_file; std::string freq_file_path = p.path().string() + freq_file;
if (std::filesystem::exists(freq_file_path)) { if (std::filesystem::exists(freq_file_path)) {
std::string freq_value; std::string freq_value;

View File

@@ -20,9 +20,7 @@ waybar::modules::CpuUsage::CpuUsage(const std::string& id, const Json::Value& co
auto waybar::modules::CpuUsage::update() -> void { auto waybar::modules::CpuUsage::update() -> void {
// TODO: as creating dynamic fmt::arg arrays is buggy we have to calc both // TODO: as creating dynamic fmt::arg arrays is buggy we have to calc both
auto [cpu_usage, tooltip] = CpuUsage::getCpuUsage(prev_times_); auto [cpu_usage, tooltip] = CpuUsage::getCpuUsage(prev_times_);
if (tooltipEnabled()) {
label_.set_tooltip_text(tooltip);
}
auto format = format_; auto format = format_;
auto total_usage = cpu_usage.empty() ? 0 : cpu_usage[0]; auto total_usage = cpu_usage.empty() ? 0 : cpu_usage[0];
auto state = getState(total_usage); auto state = getState(total_usage);
@@ -38,14 +36,25 @@ auto waybar::modules::CpuUsage::update() -> void {
fmt::dynamic_format_arg_store<fmt::format_context> store; fmt::dynamic_format_arg_store<fmt::format_context> store;
store.push_back(fmt::arg("usage", total_usage)); store.push_back(fmt::arg("usage", total_usage));
store.push_back(fmt::arg("icon", getIcon(total_usage, icons))); store.push_back(fmt::arg("icon", getIcon(total_usage, icons)));
std::vector<std::string> arg_names;
arg_names.reserve(cpu_usage.size() * 2);
for (size_t i = 1; i < cpu_usage.size(); ++i) { for (size_t i = 1; i < cpu_usage.size(); ++i) {
auto core_i = i - 1; auto core_i = i - 1;
auto core_format = fmt::format("usage{}", core_i); arg_names.push_back(fmt::format("usage{}", core_i));
store.push_back(fmt::arg(core_format.c_str(), cpu_usage[i])); store.push_back(fmt::arg(arg_names.back().c_str(), cpu_usage[i]));
auto icon_format = fmt::format("icon{}", core_i); arg_names.push_back(fmt::format("icon{}", core_i));
store.push_back(fmt::arg(icon_format.c_str(), getIcon(cpu_usage[i], icons))); store.push_back(fmt::arg(arg_names.back().c_str(), getIcon(cpu_usage[i], icons)));
} }
label_.set_markup(fmt::vformat(format, store)); label_.set_markup(fmt::vformat(format, store));
if (tooltipEnabled()) {
if (config_["tooltip-format"].isString()) {
tooltip = config_["tooltip-format"].asString();
label_.set_tooltip_markup(fmt::vformat(tooltip, store));
} else {
label_.set_tooltip_markup(tooltip);
}
}
} }
// Call parent update // Call parent update
@@ -71,7 +80,8 @@ std::tuple<std::vector<uint16_t>, std::string> waybar::modules::CpuUsage::getCpu
auto [prev_idle, prev_total] = prev_times[0]; auto [prev_idle, prev_total] = prev_times[0];
const float delta_idle = curr_idle - prev_idle; const float delta_idle = curr_idle - prev_idle;
const float delta_total = curr_total - prev_total; const float delta_total = curr_total - prev_total;
uint16_t tmp = 100 * (1 - delta_idle / delta_total); uint16_t tmp =
(delta_total > 0) ? static_cast<uint16_t>(100 * (1 - delta_idle / delta_total)) : 0;
tooltip = fmt::format("Total: {}%\nCores: (pending)", tmp); tooltip = fmt::format("Total: {}%\nCores: (pending)", tmp);
usage.push_back(tmp); usage.push_back(tmp);
} else { } else {
@@ -93,7 +103,8 @@ std::tuple<std::vector<uint16_t>, std::string> waybar::modules::CpuUsage::getCpu
} }
const float delta_idle = curr_idle - prev_idle; const float delta_idle = curr_idle - prev_idle;
const float delta_total = curr_total - prev_total; const float delta_total = curr_total - prev_total;
uint16_t tmp = 100 * (1 - delta_idle / delta_total); uint16_t tmp =
(delta_total > 0) ? static_cast<uint16_t>(100 * (1 - delta_idle / delta_total)) : 0;
if (i == 0) { if (i == 0) {
tooltip = fmt::format("Total: {}%", tmp); tooltip = fmt::format("Total: {}%", tmp);
} else { } else {

View File

@@ -2,6 +2,8 @@
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
#include <utility>
#include "util/scope_guard.hpp" #include "util/scope_guard.hpp"
waybar::modules::Custom::Custom(const std::string& name, const std::string& id, waybar::modules::Custom::Custom(const std::string& name, const std::string& id,
@@ -180,16 +182,22 @@ auto waybar::modules::Custom::update() -> void {
} else { } else {
label_.set_markup(str); label_.set_markup(str);
if (tooltipEnabled()) { if (tooltipEnabled()) {
std::string tooltip_markup;
if (tooltip_format_enabled_) { if (tooltip_format_enabled_) {
auto tooltip = config_["tooltip-format"].asString(); auto tooltip = config_["tooltip-format"].asString();
tooltip = fmt::format( tooltip_markup = fmt::format(fmt::runtime(tooltip), fmt::arg("text", text_),
fmt::runtime(tooltip), fmt::arg("text", text_), fmt::arg("alt", alt_), fmt::arg("tooltip", tooltip_), fmt::arg("alt", alt_),
fmt::arg("icon", getIcon(percentage_, alt_)), fmt::arg("percentage", percentage_)); fmt::arg("icon", getIcon(percentage_, alt_)),
label_.set_tooltip_markup(tooltip); fmt::arg("percentage", percentage_));
} else if (text_ == tooltip_) { } else if (text_ == tooltip_) {
label_.set_tooltip_markup(str); tooltip_markup = str;
} else { } else {
label_.set_tooltip_markup(tooltip_); tooltip_markup = tooltip_;
}
if (last_tooltip_markup_ != tooltip_markup) {
label_.set_tooltip_markup(tooltip_markup);
last_tooltip_markup_ = std::move(tooltip_markup);
} }
} }
auto style = label_.get_style_context(); auto style = label_.get_style_context();

View File

@@ -41,7 +41,7 @@ auto waybar::modules::Disk::update() -> void {
fs_used - File system used space fs_used - File system used space
*/ */
if (err != 0) { if (err != 0 || stats.f_blocks == 0) {
event_box_.hide(); event_box_.hide();
return; return;
} }
@@ -81,7 +81,7 @@ auto waybar::modules::Disk::update() -> void {
if (config_["tooltip-format"].isString()) { if (config_["tooltip-format"].isString()) {
tooltip_format = config_["tooltip-format"].asString(); tooltip_format = config_["tooltip-format"].asString();
} }
label_.set_tooltip_text(fmt::format( label_.set_tooltip_markup(fmt::format(
fmt::runtime(tooltip_format), stats.f_bavail * 100 / stats.f_blocks, fmt::arg("free", free), fmt::runtime(tooltip_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_free", stats.f_bavail * 100 / stats.f_blocks), fmt::arg("used", used),
fmt::arg("percentage_used", percentage_used), fmt::arg("total", total), fmt::arg("percentage_used", percentage_used), fmt::arg("total", total),
@@ -92,7 +92,7 @@ auto waybar::modules::Disk::update() -> void {
ALabel::update(); ALabel::update();
} }
float waybar::modules::Disk::calc_specific_divisor(std::string divisor) { float waybar::modules::Disk::calc_specific_divisor(const std::string& divisor) {
if (divisor == "kB") { if (divisor == "kB") {
return 1000.0; return 1000.0;
} else if (divisor == "kiB") { } else if (divisor == "kiB") {

View File

@@ -70,17 +70,33 @@ static const zdwl_ipc_output_v2_listener output_status_listener_impl{
static void handle_global(void* data, struct wl_registry* registry, uint32_t name, static void handle_global(void* data, struct wl_registry* registry, uint32_t name,
const char* interface, uint32_t version) { const char* interface, uint32_t version) {
if (std::strcmp(interface, zdwl_ipc_manager_v2_interface.name) == 0) {
static_cast<Tags*>(data)->status_manager_ = static_cast<struct zdwl_ipc_manager_v2*>( if (std::strcmp(interface, zdwl_ipc_manager_v2_interface.name) == 0) {
(zdwl_ipc_manager_v2*)wl_registry_bind(registry, name, &zdwl_ipc_manager_v2_interface, 1)); auto* self = static_cast<Tags*>(data);
}
if (std::strcmp(interface, wl_seat_interface.name) == 0) { if (self->status_manager_) {
version = std::min<uint32_t>(version, 1); zdwl_ipc_manager_v2_destroy(self->status_manager_);
static_cast<Tags*>(data)->seat_ = self->status_manager_ = nullptr;
static_cast<struct wl_seat*>(wl_registry_bind(registry, name, &wl_seat_interface, version));
}
} }
self->status_manager_ = static_cast<struct zdwl_ipc_manager_v2*>(
wl_registry_bind(registry, name, &zdwl_ipc_manager_v2_interface, 1));
}
if (std::strcmp(interface, wl_seat_interface.name) == 0) {
auto* self = static_cast<Tags*>(data);
if (self->seat_) {
wl_seat_destroy(self->seat_);
self->seat_ = nullptr;
}
version = std::min<uint32_t>(version, 1);
self->seat_ = static_cast<struct wl_seat*>(
wl_registry_bind(registry, name, &wl_seat_interface, version));
}
}
static void handle_global_remove(void* data, struct wl_registry* registry, uint32_t name) { static void handle_global_remove(void* data, struct wl_registry* registry, uint32_t name) {
/* Ignore event */ /* Ignore event */
} }
@@ -179,6 +195,7 @@ bool Tags::handle_button_press(GdkEventButton* event_button, uint32_t tag) {
} }
void Tags::handle_view_tags(uint32_t tag, uint32_t state, uint32_t clients, uint32_t focused) { void Tags::handle_view_tags(uint32_t tag, uint32_t state, uint32_t clients, uint32_t focused) {
if (tag >= buttons_.size()) return;
// First clear all occupied state // First clear all occupied state
auto& button = buttons_[tag]; auto& button = buttons_[tag];
if (clients) { if (clients) {

View File

@@ -116,7 +116,7 @@ void Window::handle_frame() {
updateAppIconName(appid_, ""); updateAppIconName(appid_, "");
updateAppIcon(); updateAppIcon();
if (tooltipEnabled()) { if (tooltipEnabled()) {
label_.set_tooltip_text(title_); label_.set_tooltip_markup(title_);
} }
} }

View File

@@ -143,7 +143,9 @@ void WorkspaceManager::handle_finished() {
ext_manager_ = nullptr; ext_manager_ = nullptr;
} }
void WorkspaceManager::commit() const { ext_workspace_manager_v1_commit(ext_manager_); } void WorkspaceManager::commit() const {
if (ext_manager_) ext_workspace_manager_v1_commit(ext_manager_);
}
void WorkspaceManager::update() { void WorkspaceManager::update() {
spdlog::debug("[ext/workspaces]: Updating state"); spdlog::debug("[ext/workspaces]: Updating state");

View File

@@ -128,9 +128,9 @@ void Gamemode::getData() {
Glib::VariantContainerBase data = gamemode_proxy->call_sync("Get", parameters); Glib::VariantContainerBase data = gamemode_proxy->call_sync("Get", parameters);
if (data && data.is_of_type(Glib::VariantType("(v)"))) { if (data && data.is_of_type(Glib::VariantType("(v)"))) {
Glib::VariantBase variant; Glib::VariantBase variant;
g_variant_get(data.gobj_copy(), "(v)", &variant); g_variant_get(const_cast<GVariant*>(data.gobj()), "(v)", &variant);
if (variant && variant.is_of_type(Glib::VARIANT_TYPE_INT32)) { if (variant && variant.is_of_type(Glib::VARIANT_TYPE_INT32)) {
g_variant_get(variant.gobj_copy(), "i", &gameCount); g_variant_get(const_cast<GVariant*>(variant.gobj()), "i", &gameCount);
return; return;
} }
} }
@@ -158,7 +158,7 @@ void Gamemode::prepareForSleep_cb(const Glib::RefPtr<Gio::DBus::Connection>& con
const Glib::VariantContainerBase& parameters) { const Glib::VariantContainerBase& parameters) {
if (parameters.is_of_type(Glib::VariantType("(b)"))) { if (parameters.is_of_type(Glib::VariantType("(b)"))) {
gboolean sleeping; gboolean sleeping;
g_variant_get(parameters.gobj_copy(), "(b)", &sleeping); g_variant_get(const_cast<GVariant*>(parameters.gobj()), "(b)", &sleeping);
if (!sleeping) { if (!sleeping) {
getData(); getData();
dp.emit(); dp.emit();
@@ -212,7 +212,7 @@ auto Gamemode::update() -> void {
// Tooltip // Tooltip
if (tooltip) { if (tooltip) {
std::string text = fmt::format(fmt::runtime(tooltip_format), fmt::arg("count", gameCount)); std::string text = fmt::format(fmt::runtime(tooltip_format), fmt::arg("count", gameCount));
box_.set_tooltip_text(text); box_.set_tooltip_markup(text);
} }
// Label format // Label format

View File

@@ -9,9 +9,14 @@
#include <sys/un.h> #include <sys/un.h>
#include <unistd.h> #include <unistd.h>
#include <array>
#include <cerrno>
#include <cstring>
#include <filesystem> #include <filesystem>
#include <string> #include <string>
#include "util/scoped_fd.hpp"
namespace waybar::modules::hyprland { namespace waybar::modules::hyprland {
std::filesystem::path IPC::socketFolder_; std::filesystem::path IPC::socketFolder_;
@@ -20,11 +25,7 @@ std::filesystem::path IPC::getSocketFolder(const char* instanceSig) {
static std::mutex folderMutex; static std::mutex folderMutex;
std::unique_lock lock(folderMutex); std::unique_lock lock(folderMutex);
// socket path, specified by EventManager of Hyprland if (socketFolder_.empty()) {
if (!socketFolder_.empty()) {
return socketFolder_;
}
const char* xdgRuntimeDirEnv = std::getenv("XDG_RUNTIME_DIR"); const char* xdgRuntimeDirEnv = std::getenv("XDG_RUNTIME_DIR");
std::filesystem::path xdgRuntimeDir; std::filesystem::path xdgRuntimeDir;
// Only set path if env variable is set // Only set path if env variable is set
@@ -38,15 +39,15 @@ std::filesystem::path IPC::getSocketFolder(const char* instanceSig) {
spdlog::warn("$XDG_RUNTIME_DIR/hypr does not exist, falling back to /tmp/hypr"); spdlog::warn("$XDG_RUNTIME_DIR/hypr does not exist, falling back to /tmp/hypr");
socketFolder_ = std::filesystem::path("/tmp") / "hypr"; socketFolder_ = std::filesystem::path("/tmp") / "hypr";
} }
}
socketFolder_ = socketFolder_ / instanceSig; return socketFolder_ / instanceSig;
return socketFolder_;
} }
IPC::IPC() { IPC::IPC() {
// will start IPC and relay events to parseIPC // will start IPC and relay events to parseIPC
ipcThread_ = std::thread([this]() { socketListener(); });
socketOwnerPid_ = getpid(); socketOwnerPid_ = getpid();
ipcThread_ = std::thread([this]() { socketListener(); });
} }
IPC::~IPC() { IPC::~IPC() {
@@ -54,19 +55,20 @@ IPC::~IPC() {
// failed exec()) exits. // failed exec()) exits.
if (getpid() != socketOwnerPid_) return; if (getpid() != socketOwnerPid_) return;
running_ = false; running_.store(false, std::memory_order_relaxed);
spdlog::info("Hyprland IPC stopping..."); spdlog::info("Hyprland IPC stopping...");
{
std::lock_guard<std::mutex> lock(socketMutex_);
if (socketfd_ != -1) { if (socketfd_ != -1) {
spdlog::trace("Shutting down socket"); spdlog::trace("Shutting down socket");
if (shutdown(socketfd_, SHUT_RDWR) == -1) { if (shutdown(socketfd_, SHUT_RDWR) == -1 && errno != ENOTCONN) {
spdlog::error("Hyprland IPC: Couldn't shutdown socket"); spdlog::error("Hyprland IPC: Couldn't shutdown socket");
} }
spdlog::trace("Closing socket");
if (close(socketfd_) == -1) {
spdlog::error("Hyprland IPC: Couldn't close socket");
} }
} }
if (ipcThread_.joinable()) {
ipcThread_.join(); ipcThread_.join();
}
} }
IPC& IPC::inst() { IPC& IPC::inst() {
@@ -85,10 +87,10 @@ void IPC::socketListener() {
spdlog::info("Hyprland IPC starting"); spdlog::info("Hyprland IPC starting");
struct sockaddr_un addr; struct sockaddr_un addr = {};
socketfd_ = socket(AF_UNIX, SOCK_STREAM, 0); const int socketfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (socketfd_ == -1) { if (socketfd == -1) {
spdlog::error("Hyprland IPC: socketfd failed"); spdlog::error("Hyprland IPC: socketfd failed");
return; return;
} }
@@ -96,33 +98,57 @@ void IPC::socketListener() {
addr.sun_family = AF_UNIX; addr.sun_family = AF_UNIX;
auto socketPath = IPC::getSocketFolder(his) / ".socket2.sock"; auto socketPath = IPC::getSocketFolder(his) / ".socket2.sock";
if (socketPath.native().size() >= sizeof(addr.sun_path)) {
spdlog::error("Hyprland IPC: Socket path is too long: {}", socketPath.string());
close(socketfd);
return;
}
strncpy(addr.sun_path, socketPath.c_str(), sizeof(addr.sun_path) - 1); strncpy(addr.sun_path, socketPath.c_str(), sizeof(addr.sun_path) - 1);
addr.sun_path[sizeof(addr.sun_path) - 1] = 0;
int l = sizeof(struct sockaddr_un); int l = sizeof(struct sockaddr_un);
if (connect(socketfd_, (struct sockaddr*)&addr, l) == -1) { if (connect(socketfd, (struct sockaddr*)&addr, l) == -1) {
spdlog::error("Hyprland IPC: Unable to connect?"); spdlog::error("Hyprland IPC: Unable to connect? {}", std::strerror(errno));
close(socketfd);
return; return;
} }
auto* file = fdopen(socketfd_, "r");
if (file == nullptr) { {
spdlog::error("Hyprland IPC: Couldn't open file descriptor"); std::lock_guard<std::mutex> lock(socketMutex_);
return; socketfd_ = socketfd;
} }
while (running_) {
std::string pending;
while (running_.load(std::memory_order_relaxed)) {
std::array<char, 1024> buffer; // Hyprland socket2 events are max 1024 bytes std::array<char, 1024> buffer; // Hyprland socket2 events are max 1024 bytes
const ssize_t bytes_read = read(socketfd, buffer.data(), buffer.size());
auto* receivedCharPtr = fgets(buffer.data(), buffer.size(), file); if (bytes_read == 0) {
if (running_.load(std::memory_order_relaxed)) {
spdlog::warn("Hyprland IPC: Socket closed by peer");
}
break;
}
if (receivedCharPtr == nullptr) { if (bytes_read < 0) {
std::this_thread::sleep_for(std::chrono::milliseconds(1)); if (errno == EINTR) {
continue; continue;
} }
if (!running_.load(std::memory_order_relaxed)) {
break;
}
spdlog::error("Hyprland IPC: read failed: {}", std::strerror(errno));
break;
}
std::string messageReceived(buffer.data()); pending.append(buffer.data(), static_cast<std::size_t>(bytes_read));
messageReceived = messageReceived.substr(0, messageReceived.find_first_of('\n')); for (auto newline_pos = pending.find('\n'); newline_pos != std::string::npos;
newline_pos = pending.find('\n')) {
std::string messageReceived = pending.substr(0, newline_pos);
pending.erase(0, newline_pos + 1);
if (messageReceived.empty()) {
continue;
}
spdlog::debug("hyprland IPC received {}", messageReceived); spdlog::debug("hyprland IPC received {}", messageReceived);
try { try {
@@ -132,8 +158,16 @@ void IPC::socketListener() {
} catch (...) { } catch (...) {
throw; throw;
} }
}
std::this_thread::sleep_for(std::chrono::milliseconds(1)); }
{
std::lock_guard<std::mutex> lock(socketMutex_);
if (socketfd_ != -1) {
if (close(socketfd_) == -1) {
spdlog::error("Hyprland IPC: Couldn't close socket");
}
socketfd_ = -1;
}
} }
spdlog::debug("Hyprland IPC stopped"); spdlog::debug("Hyprland IPC stopped");
} }
@@ -178,7 +212,7 @@ void IPC::unregisterForIPC(EventHandler* ev_handler) {
std::string IPC::getSocket1Reply(const std::string& rq) { std::string IPC::getSocket1Reply(const std::string& rq) {
// basically hyprctl // basically hyprctl
const auto serverSocket = socket(AF_UNIX, SOCK_STREAM, 0); util::ScopedFd serverSocket(socket(AF_UNIX, SOCK_STREAM, 0));
if (serverSocket < 0) { if (serverSocket < 0) {
throw std::runtime_error("Hyprland IPC: Couldn't open a socket (1)"); throw std::runtime_error("Hyprland IPC: Couldn't open a socket (1)");
@@ -198,8 +232,10 @@ std::string IPC::getSocket1Reply(const std::string& rq) {
std::string socketPath = IPC::getSocketFolder(instanceSig) / ".socket.sock"; std::string socketPath = IPC::getSocketFolder(instanceSig) / ".socket.sock";
// Use snprintf to copy the socketPath string into serverAddress.sun_path // Use snprintf to copy the socketPath string into serverAddress.sun_path
if (snprintf(serverAddress.sun_path, sizeof(serverAddress.sun_path), "%s", socketPath.c_str()) < const auto socketPathLength =
0) { snprintf(serverAddress.sun_path, sizeof(serverAddress.sun_path), "%s", socketPath.c_str());
if (socketPathLength < 0 ||
socketPathLength >= static_cast<int>(sizeof(serverAddress.sun_path))) {
throw std::runtime_error("Hyprland IPC: Couldn't copy socket path (6)"); throw std::runtime_error("Hyprland IPC: Couldn't copy socket path (6)");
} }
@@ -208,28 +244,39 @@ std::string IPC::getSocket1Reply(const std::string& rq) {
throw std::runtime_error("Hyprland IPC: Couldn't connect to " + socketPath + ". (3)"); throw std::runtime_error("Hyprland IPC: Couldn't connect to " + socketPath + ". (3)");
} }
auto sizeWritten = write(serverSocket, rq.c_str(), rq.length()); std::size_t totalWritten = 0;
while (totalWritten < rq.length()) {
const auto sizeWritten =
write(serverSocket, rq.c_str() + totalWritten, rq.length() - totalWritten);
if (sizeWritten < 0) { if (sizeWritten < 0) {
if (errno == EINTR) {
continue;
}
spdlog::error("Hyprland IPC: Couldn't write (4)"); spdlog::error("Hyprland IPC: Couldn't write (4)");
return ""; return "";
} }
if (sizeWritten == 0) {
spdlog::error("Hyprland IPC: Socket write made no progress");
return "";
}
totalWritten += static_cast<std::size_t>(sizeWritten);
}
std::array<char, 8192> buffer = {0}; std::array<char, 8192> buffer = {0};
std::string response; std::string response;
ssize_t sizeWritten = 0;
do { do {
sizeWritten = read(serverSocket, buffer.data(), 8192); sizeWritten = read(serverSocket, buffer.data(), 8192);
if (sizeWritten < 0) { if (sizeWritten < 0) {
spdlog::error("Hyprland IPC: Couldn't read (5)"); spdlog::error("Hyprland IPC: Couldn't read (5)");
close(serverSocket);
return ""; return "";
} }
response.append(buffer.data(), sizeWritten); response.append(buffer.data(), sizeWritten);
} while (sizeWritten > 0); } while (sizeWritten > 0);
close(serverSocket);
return response; return response;
} }

View File

@@ -63,19 +63,35 @@ auto Language::update() -> void {
void Language::onEvent(const std::string& ev) { void Language::onEvent(const std::string& ev) {
std::lock_guard<std::mutex> lg(mutex_); std::lock_guard<std::mutex> lg(mutex_);
std::string kbName(begin(ev) + ev.find_last_of('>') + 1, begin(ev) + ev.find_first_of(',')); const auto payloadStart = ev.find(">>");
if (payloadStart == std::string::npos) {
spdlog::warn("hyprland language received malformed event: {}", ev);
return;
}
const auto payload = ev.substr(payloadStart + 2);
const auto kbSeparator = payload.find(',');
if (kbSeparator == std::string::npos) {
spdlog::warn("hyprland language received malformed event payload: {}", ev);
return;
}
std::string kbName = payload.substr(0, kbSeparator);
// Last comma before variants parenthesis, eg: // Last comma before variants parenthesis, eg:
// activelayout>>micro-star-int'l-co.,-ltd.-msi-gk50-elite-gaming-keyboard,English (US, intl., // activelayout>>micro-star-int'l-co.,-ltd.-msi-gk50-elite-gaming-keyboard,English (US, intl.,
// with dead keys) // with dead keys)
std::string beforeParenthesis; std::string beforeParenthesis;
auto parenthesisPos = ev.find_last_of('('); auto parenthesisPos = payload.find_last_of('(');
if (parenthesisPos == std::string::npos) { if (parenthesisPos == std::string::npos) {
beforeParenthesis = ev; beforeParenthesis = payload;
} else { } else {
beforeParenthesis = std::string(begin(ev), begin(ev) + parenthesisPos); beforeParenthesis = payload.substr(0, parenthesisPos);
} }
auto layoutName = ev.substr(beforeParenthesis.find_last_of(',') + 1); const auto layoutSeparator = beforeParenthesis.find_last_of(',');
if (layoutSeparator == std::string::npos) {
spdlog::warn("hyprland language received malformed layout payload: {}", ev);
return;
}
auto layoutName = payload.substr(layoutSeparator + 1);
if (config_.isMember("keyboard-name") && kbName != config_["keyboard-name"].asString()) if (config_.isMember("keyboard-name") && kbName != config_["keyboard-name"].asString())
return; // ignore return; // ignore

View File

@@ -60,7 +60,7 @@ auto Submap::update() -> void {
} else { } else {
label_.set_markup(fmt::format(fmt::runtime(format_), submap_)); label_.set_markup(fmt::format(fmt::runtime(format_), submap_));
if (tooltipEnabled()) { if (tooltipEnabled()) {
label_.set_tooltip_text(submap_); label_.set_tooltip_markup(submap_);
} }
event_box_.show(); event_box_.show();
} }
@@ -75,7 +75,12 @@ void Submap::onEvent(const std::string& ev) {
return; return;
} }
auto submapName = ev.substr(ev.find_first_of('>') + 2); const auto separator = ev.find(">>");
if (separator == std::string::npos) {
spdlog::warn("hyprland submap received malformed event: {}", ev);
return;
}
auto submapName = ev.substr(separator + 2);
submap_ = submapName; submap_ = submapName;

View File

@@ -19,21 +19,19 @@ std::shared_mutex windowIpcSmtx;
Window::Window(const std::string& id, const Bar& bar, const Json::Value& config) Window::Window(const std::string& id, const Bar& bar, const Json::Value& config)
: AAppIconLabel(config, "window", id, "{title}", 0, true), bar_(bar), m_ipc(IPC::inst()) { : AAppIconLabel(config, "window", id, "{title}", 0, true), bar_(bar), m_ipc(IPC::inst()) {
std::unique_lock<std::shared_mutex> windowIpcUniqueLock(windowIpcSmtx);
separateOutputs_ = config["separate-outputs"].asBool(); separateOutputs_ = config["separate-outputs"].asBool();
update();
// register for hyprland ipc // register for hyprland ipc
std::unique_lock<std::shared_mutex> windowIpcUniqueLock(windowIpcSmtx);
m_ipc.registerForIPC("activewindow", this); m_ipc.registerForIPC("activewindow", this);
m_ipc.registerForIPC("closewindow", this); m_ipc.registerForIPC("closewindow", this);
m_ipc.registerForIPC("movewindow", this); m_ipc.registerForIPC("movewindow", this);
m_ipc.registerForIPC("changefloatingmode", this); m_ipc.registerForIPC("changefloatingmode", this);
m_ipc.registerForIPC("fullscreen", this); m_ipc.registerForIPC("fullscreen", this);
windowIpcUniqueLock.unlock(); windowIpcUniqueLock.unlock();
queryActiveWorkspace();
update();
dp.emit(); dp.emit();
} }
@@ -72,13 +70,13 @@ auto Window::update() -> void {
tooltip_format = config_["tooltip-format"].asString(); tooltip_format = config_["tooltip-format"].asString();
} }
if (!tooltip_format.empty()) { if (!tooltip_format.empty()) {
label_.set_tooltip_text( label_.set_tooltip_markup(
fmt::format(fmt::runtime(tooltip_format), fmt::arg("title", windowName), fmt::format(fmt::runtime(tooltip_format), fmt::arg("title", windowName),
fmt::arg("initialTitle", windowData_.initial_title), fmt::arg("initialTitle", windowData_.initial_title),
fmt::arg("class", windowData_.class_name), fmt::arg("class", windowData_.class_name),
fmt::arg("initialClass", windowData_.initial_class_name))); fmt::arg("initialClass", windowData_.initial_class_name)));
} else if (!label_text.empty()) { } else if (!label_text.empty()) {
label_.set_tooltip_text(label_text); label_.set_tooltip_markup(label_text);
} }
} }
@@ -124,7 +122,7 @@ auto Window::getActiveWorkspace(const std::string& monitorName) -> Workspace {
const auto monitors = IPC::inst().getSocket1JsonReply("monitors"); const auto monitors = IPC::inst().getSocket1JsonReply("monitors");
if (monitors.isArray()) { if (monitors.isArray()) {
auto monitor = std::ranges::find_if( auto monitor = std::ranges::find_if(
monitors, [&](Json::Value monitor) { return monitor["name"] == monitorName; }); monitors, [&](const Json::Value& monitor) { return monitor["name"] == monitorName; });
if (monitor == std::end(monitors)) { if (monitor == std::end(monitors)) {
spdlog::warn("Monitor not found: {}", monitorName); spdlog::warn("Monitor not found: {}", monitorName);
return Workspace{ return Workspace{
@@ -139,7 +137,7 @@ auto Window::getActiveWorkspace(const std::string& monitorName) -> Workspace {
const auto workspaces = IPC::inst().getSocket1JsonReply("workspaces"); const auto workspaces = IPC::inst().getSocket1JsonReply("workspaces");
if (workspaces.isArray()) { if (workspaces.isArray()) {
auto workspace = std::ranges::find_if( auto workspace = std::ranges::find_if(
workspaces, [&](Json::Value workspace) { return workspace["id"] == id; }); workspaces, [&](const Json::Value& workspace) { return workspace["id"] == id; });
if (workspace == std::end(workspaces)) { if (workspace == std::end(workspaces)) {
spdlog::warn("No workspace with id {}", id); spdlog::warn("No workspace with id {}", id);
return Workspace{ return Workspace{
@@ -177,42 +175,56 @@ auto Window::WindowData::parse(const Json::Value& value) -> Window::WindowData {
} }
void Window::queryActiveWorkspace() { void Window::queryActiveWorkspace() {
std::shared_lock<std::shared_mutex> windowIpcShareLock(windowIpcSmtx);
if (separateOutputs_) { if (separateOutputs_) {
workspace_ = getActiveWorkspace(this->bar_.output->name); workspace_ = getActiveWorkspace(this->bar_.output->name);
} else { } else {
workspace_ = getActiveWorkspace(); workspace_ = getActiveWorkspace();
} }
focused_ = true;
if (workspace_.windows > 0) {
const auto clients = m_ipc.getSocket1JsonReply("clients");
if (clients.isArray()) {
auto activeWindow = std::ranges::find_if(
clients, [&](Json::Value window) { return window["address"] == workspace_.last_window; });
if (activeWindow == std::end(clients)) {
focused_ = false; focused_ = false;
windowData_ = WindowData{};
allFloating_ = false;
swallowing_ = false;
fullscreen_ = false;
solo_ = false;
soloClass_.clear();
if (workspace_.windows <= 0) {
return; return;
} }
const auto clients = m_ipc.getSocket1JsonReply("clients");
if (!clients.isArray()) {
return;
}
auto activeWindow = std::ranges::find_if(clients, [&](const Json::Value& window) {
return window["address"] == workspace_.last_window;
});
if (activeWindow == std::end(clients)) {
return;
}
focused_ = true;
windowData_ = WindowData::parse(*activeWindow); windowData_ = WindowData::parse(*activeWindow);
updateAppIconName(windowData_.class_name, windowData_.initial_class_name); updateAppIconName(windowData_.class_name, windowData_.initial_class_name);
std::vector<Json::Value> workspaceWindows; std::vector<Json::Value> workspaceWindows;
std::ranges::copy_if(clients, std::back_inserter(workspaceWindows), [&](Json::Value window) { std::ranges::copy_if(
clients, std::back_inserter(workspaceWindows), [&](const Json::Value& window) {
return window["workspace"]["id"] == workspace_.id && window["mapped"].asBool(); return window["workspace"]["id"] == workspace_.id && window["mapped"].asBool();
}); });
swallowing_ = std::ranges::any_of(workspaceWindows, [&](Json::Value window) { swallowing_ = std::ranges::any_of(workspaceWindows, [&](const Json::Value& window) {
return !window["swallowing"].isNull() && window["swallowing"].asString() != "0x0"; return !window["swallowing"].isNull() && window["swallowing"].asString() != "0x0";
}); });
std::vector<Json::Value> visibleWindows; std::vector<Json::Value> visibleWindows;
std::ranges::copy_if(workspaceWindows, std::back_inserter(visibleWindows), std::ranges::copy_if(workspaceWindows, std::back_inserter(visibleWindows),
[&](Json::Value window) { return !window["hidden"].asBool(); }); [&](const Json::Value& window) { return !window["hidden"].asBool(); });
solo_ = 1 == std::count_if(visibleWindows.begin(), visibleWindows.end(), solo_ = 1 == std::count_if(
[&](Json::Value window) { return !window["floating"].asBool(); }); visibleWindows.begin(), visibleWindows.end(),
[&](const Json::Value& window) { return !window["floating"].asBool(); });
allFloating_ = std::ranges::all_of( allFloating_ = std::ranges::all_of(
visibleWindows, [&](Json::Value window) { return window["floating"].asBool(); }); visibleWindows, [&](const Json::Value& window) { return window["floating"].asBool(); });
fullscreen_ = windowData_.fullscreen; fullscreen_ = windowData_.fullscreen;
// Fullscreen windows look like they are solo // Fullscreen windows look like they are solo
@@ -222,24 +234,10 @@ void Window::queryActiveWorkspace() {
if (solo_) { if (solo_) {
soloClass_ = windowData_.class_name; soloClass_ = windowData_.class_name;
} else {
soloClass_ = "";
}
}
} else {
focused_ = false;
windowData_ = WindowData{};
allFloating_ = false;
swallowing_ = false;
fullscreen_ = false;
solo_ = false;
soloClass_ = "";
} }
} }
void Window::onEvent(const std::string& ev) { void Window::onEvent(const std::string& ev) { dp.emit(); }
dp.emit();
}
void Window::setClass(const std::string& classname, bool enable) { void Window::setClass(const std::string& classname, bool enable) {
if (enable) { if (enable) {

View File

@@ -58,7 +58,7 @@ auto WindowCount::update() -> void {
} else if (!format.empty()) { } else if (!format.empty()) {
label_.set_markup(fmt::format(fmt::runtime(format), workspace_.windows)); label_.set_markup(fmt::format(fmt::runtime(format), workspace_.windows));
} else { } else {
label_.set_text(fmt::format("{}", workspace_.windows)); label_.set_markup(fmt::format("{}", workspace_.windows));
} }
label_.show(); label_.show();
@@ -79,7 +79,7 @@ auto WindowCount::getActiveWorkspace(const std::string& monitorName) -> Workspac
const auto monitors = m_ipc.getSocket1JsonReply("monitors"); const auto monitors = m_ipc.getSocket1JsonReply("monitors");
if (monitors.isArray()) { if (monitors.isArray()) {
auto monitor = std::ranges::find_if( auto monitor = std::ranges::find_if(
monitors, [&](Json::Value monitor) { return monitor["name"] == monitorName; }); monitors, [&](const Json::Value& monitor) { return monitor["name"] == monitorName; });
if (monitor == std::end(monitors)) { if (monitor == std::end(monitors)) {
spdlog::warn("Monitor not found: {}", monitorName); spdlog::warn("Monitor not found: {}", monitorName);
return Workspace{ return Workspace{
@@ -93,7 +93,7 @@ auto WindowCount::getActiveWorkspace(const std::string& monitorName) -> Workspac
const auto workspaces = m_ipc.getSocket1JsonReply("workspaces"); const auto workspaces = m_ipc.getSocket1JsonReply("workspaces");
if (workspaces.isArray()) { if (workspaces.isArray()) {
auto workspace = std::ranges::find_if( auto workspace = std::ranges::find_if(
workspaces, [&](Json::Value workspace) { return workspace["id"] == id; }); workspaces, [&](const Json::Value& workspace) { return workspace["id"] == id; });
if (workspace == std::end(workspaces)) { if (workspace == std::end(workspaces)) {
spdlog::warn("No workspace with id {}", id); spdlog::warn("No workspace with id {}", id);
return Workspace{ return Workspace{
@@ -125,9 +125,7 @@ void WindowCount::queryActiveWorkspace() {
} }
} }
void WindowCount::onEvent(const std::string& ev) { void WindowCount::onEvent(const std::string& ev) { dp.emit(); }
dp.emit();
}
void WindowCount::setClass(const std::string& classname, bool enable) { void WindowCount::setClass(const std::string& classname, bool enable) {
if (enable) { if (enable) {

View File

@@ -19,7 +19,7 @@ WindowCreationPayload::WindowCreationPayload(Json::Value const& client_data)
clearWorkspaceName(); clearWorkspaceName();
} }
WindowCreationPayload::WindowCreationPayload(std::string workspace_name, WindowCreationPayload::WindowCreationPayload(const std::string& workspace_name,
WindowAddress window_address, WindowRepr window_repr) WindowAddress window_address, WindowRepr window_repr)
: m_window(std::move(window_repr)), : m_window(std::move(window_repr)),
m_windowAddress(std::move(window_address)), m_windowAddress(std::move(window_address)),
@@ -28,9 +28,10 @@ WindowCreationPayload::WindowCreationPayload(std::string workspace_name,
clearWorkspaceName(); clearWorkspaceName();
} }
WindowCreationPayload::WindowCreationPayload(std::string workspace_name, WindowCreationPayload::WindowCreationPayload(const std::string& workspace_name,
WindowAddress window_address, std::string window_class, WindowAddress window_address,
std::string window_title, bool is_active) const std::string& window_class,
const std::string& window_title, bool is_active)
: m_window(std::make_pair(std::move(window_class), std::move(window_title))), : m_window(std::make_pair(std::move(window_class), std::move(window_title))),
m_windowAddress(std::move(window_address)), m_windowAddress(std::move(window_address)),
m_workspaceName(std::move(workspace_name)), m_workspaceName(std::move(workspace_name)),

View File

@@ -96,7 +96,7 @@ bool Workspace::handleClicked(GdkEventButton* bt) const {
void Workspace::initializeWindowMap(const Json::Value& clients_data) { void Workspace::initializeWindowMap(const Json::Value& clients_data) {
m_windowMap.clear(); m_windowMap.clear();
for (auto client : clients_data) { for (const auto& client : clients_data) {
if (client["workspace"]["id"].asInt() == id()) { if (client["workspace"]["id"].asInt() == id()) {
insertWindow({client}); insertWindow({client});
} }
@@ -300,7 +300,7 @@ void Workspace::updateTaskbar(const std::string& workspace_icon) {
} }
auto window_box = Gtk::make_managed<Gtk::Box>(Gtk::ORIENTATION_HORIZONTAL); auto window_box = Gtk::make_managed<Gtk::Box>(Gtk::ORIENTATION_HORIZONTAL);
window_box->set_tooltip_text(window_repr.window_title); window_box->set_tooltip_markup(window_repr.window_title);
window_box->get_style_context()->add_class("taskbar-window"); window_box->get_style_context()->add_class("taskbar-window");
if (window_repr.isActive) { if (window_repr.isActive) {
window_box->get_style_context()->add_class("active"); window_box->get_style_context()->add_class("active");

View File

@@ -34,6 +34,9 @@ Workspaces::Workspaces(const std::string& id, const Bar& bar, const Json::Value&
} }
Workspaces::~Workspaces() { Workspaces::~Workspaces() {
if (m_scrollEventConnection_.connected()) {
m_scrollEventConnection_.disconnect();
}
m_ipc.unregisterForIPC(this); m_ipc.unregisterForIPC(this);
// wait for possible event handler to finish // wait for possible event handler to finish
std::lock_guard<std::mutex> lg(m_mutex); std::lock_guard<std::mutex> lg(m_mutex);
@@ -43,6 +46,17 @@ void Workspaces::init() {
m_activeWorkspaceId = m_ipc.getSocket1JsonReply("activeworkspace")["id"].asInt(); m_activeWorkspaceId = m_ipc.getSocket1JsonReply("activeworkspace")["id"].asInt();
initializeWorkspaces(); initializeWorkspaces();
if (m_scrollEventConnection_.connected()) {
m_scrollEventConnection_.disconnect();
}
if (barScroll()) {
auto& window = const_cast<Bar&>(m_bar).window;
window.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);
m_scrollEventConnection_ =
window.signal_scroll_event().connect(sigc::mem_fun(*this, &Workspaces::handleScroll));
}
dp.emit(); dp.emit();
} }
@@ -148,7 +162,8 @@ void Workspaces::extendOrphans(int workspaceId, Json::Value const& clientsJson)
} }
} }
std::string Workspaces::getRewrite(std::string window_class, std::string window_title) { std::string Workspaces::getRewrite(const std::string& window_class,
const std::string& window_title) {
std::string windowReprKey; std::string windowReprKey;
if (windowRewriteConfigUsesTitle()) { if (windowRewriteConfigUsesTitle()) {
windowReprKey = fmt::format("class<{}> title<{}>", window_class, window_title); windowReprKey = fmt::format("class<{}> title<{}>", window_class, window_title);
@@ -189,7 +204,7 @@ void Workspaces::initializeWorkspaces() {
auto const workspacesJson = m_ipc.getSocket1JsonReply("workspaces"); auto const workspacesJson = m_ipc.getSocket1JsonReply("workspaces");
auto const clientsJson = m_ipc.getSocket1JsonReply("clients"); auto const clientsJson = m_ipc.getSocket1JsonReply("clients");
for (Json::Value workspaceJson : workspacesJson) { for (const auto& workspaceJson : workspacesJson) {
std::string workspaceName = workspaceJson["name"].asString(); std::string workspaceName = workspaceJson["name"].asString();
if ((allOutputs() || m_bar.output->name == workspaceJson["monitor"].asString()) && if ((allOutputs() || m_bar.output->name == workspaceJson["monitor"].asString()) &&
(!workspaceName.starts_with("special") || showSpecial()) && (!workspaceName.starts_with("special") || showSpecial()) &&
@@ -263,7 +278,7 @@ void Workspaces::loadPersistentWorkspacesFromConfig(Json::Value const& clientsJs
// key is the workspace and value is array of monitors to create on // key is the workspace and value is array of monitors to create on
for (const Json::Value& monitor : value) { for (const Json::Value& monitor : value) {
if (monitor.isString() && monitor.asString() == currentMonitor) { if (monitor.isString() && monitor.asString() == currentMonitor) {
persistentWorkspacesToCreate.emplace_back(currentMonitor); persistentWorkspacesToCreate.emplace_back(key);
break; break;
} }
} }
@@ -324,8 +339,13 @@ void Workspaces::loadPersistentWorkspacesFromWorkspaceRules(const Json::Value& c
void Workspaces::onEvent(const std::string& ev) { void Workspaces::onEvent(const std::string& ev) {
std::lock_guard<std::mutex> lock(m_mutex); std::lock_guard<std::mutex> lock(m_mutex);
std::string eventName(begin(ev), begin(ev) + ev.find_first_of('>')); const auto separator = ev.find(">>");
std::string payload = ev.substr(eventName.size() + 2); if (separator == std::string::npos) {
spdlog::warn("Malformed Hyprland workspace event: {}", ev);
return;
}
std::string eventName = ev.substr(0, separator);
std::string payload = ev.substr(separator + 2);
if (eventName == "workspacev2") { if (eventName == "workspacev2") {
onWorkspaceActivated(payload); onWorkspaceActivated(payload);
@@ -393,7 +413,7 @@ void Workspaces::onWorkspaceCreated(std::string const& payload, Json::Value cons
auto const workspaceRules = m_ipc.getSocket1JsonReply("workspacerules"); auto const workspaceRules = m_ipc.getSocket1JsonReply("workspacerules");
auto const workspacesJson = m_ipc.getSocket1JsonReply("workspaces"); auto const workspacesJson = m_ipc.getSocket1JsonReply("workspaces");
for (Json::Value workspaceJson : workspacesJson) { for (auto workspaceJson : workspacesJson) {
const auto currentId = workspaceJson["id"].asInt(); const auto currentId = workspaceJson["id"].asInt();
if (currentId == *workspaceId) { if (currentId == *workspaceId) {
std::string workspaceName = workspaceJson["name"].asString(); std::string workspaceName = workspaceJson["name"].asString();
@@ -488,19 +508,21 @@ void Workspaces::onMonitorFocused(std::string const& payload) {
void Workspaces::onWindowOpened(std::string const& payload) { void Workspaces::onWindowOpened(std::string const& payload) {
spdlog::trace("Window opened: {}", payload); spdlog::trace("Window opened: {}", payload);
updateWindowCount(); updateWindowCount();
size_t lastCommaIdx = 0; const auto firstComma = payload.find(',');
size_t nextCommaIdx = payload.find(','); const auto secondComma =
std::string windowAddress = payload.substr(lastCommaIdx, nextCommaIdx - lastCommaIdx); firstComma == std::string::npos ? std::string::npos : payload.find(',', firstComma + 1);
const auto thirdComma =
secondComma == std::string::npos ? std::string::npos : payload.find(',', secondComma + 1);
if (firstComma == std::string::npos || secondComma == std::string::npos ||
thirdComma == std::string::npos) {
spdlog::warn("Malformed Hyprland openwindow payload: {}", payload);
return;
}
lastCommaIdx = nextCommaIdx; std::string windowAddress = payload.substr(0, firstComma);
nextCommaIdx = payload.find(',', nextCommaIdx + 1); std::string workspaceName = payload.substr(firstComma + 1, secondComma - firstComma - 1);
std::string workspaceName = payload.substr(lastCommaIdx + 1, nextCommaIdx - lastCommaIdx - 1); std::string windowClass = payload.substr(secondComma + 1, thirdComma - secondComma - 1);
std::string windowTitle = payload.substr(thirdComma + 1);
lastCommaIdx = nextCommaIdx;
nextCommaIdx = payload.find(',', nextCommaIdx + 1);
std::string windowClass = payload.substr(lastCommaIdx + 1, nextCommaIdx - lastCommaIdx - 1);
std::string windowTitle = payload.substr(nextCommaIdx + 1, payload.length() - nextCommaIdx);
bool isActive = m_currentActiveWindowAddress == windowAddress; bool isActive = m_currentActiveWindowAddress == windowAddress;
m_windowsToCreate.emplace_back(workspaceName, windowAddress, windowClass, windowTitle, isActive); m_windowsToCreate.emplace_back(workspaceName, windowAddress, windowClass, windowTitle, isActive);
@@ -636,6 +658,7 @@ auto Workspaces::parseConfig(const Json::Value& config) -> void {
populateBoolConfig(config, "persistent-only", m_persistentOnly); populateBoolConfig(config, "persistent-only", m_persistentOnly);
populateBoolConfig(config, "active-only", m_activeOnly); populateBoolConfig(config, "active-only", m_activeOnly);
populateBoolConfig(config, "move-to-monitor", m_moveToMonitor); populateBoolConfig(config, "move-to-monitor", m_moveToMonitor);
populateBoolConfig(config, "enable-bar-scroll", m_barScroll);
m_persistentWorkspaceConfig = config.get("persistent-workspaces", Json::Value()); m_persistentWorkspaceConfig = config.get("persistent-workspaces", Json::Value());
populateSortByConfig(config); populateSortByConfig(config);
@@ -993,10 +1016,12 @@ void Workspaces::sortWorkspaces() {
void Workspaces::setUrgentWorkspace(std::string const& windowaddress) { void Workspaces::setUrgentWorkspace(std::string const& windowaddress) {
const Json::Value clientsJson = m_ipc.getSocket1JsonReply("clients"); const Json::Value clientsJson = m_ipc.getSocket1JsonReply("clients");
const std::string normalizedAddress =
windowaddress.starts_with("0x") ? windowaddress : fmt::format("0x{}", windowaddress);
int workspaceId = -1; int workspaceId = -1;
for (Json::Value clientJson : clientsJson) { for (const auto& clientJson : clientsJson) {
if (clientJson["address"].asString().ends_with(windowaddress)) { if (clientJson["address"].asString() == normalizedAddress) {
workspaceId = clientJson["workspace"]["id"].asInt(); workspaceId = clientJson["workspace"]["id"].asInt();
break; break;
} }
@@ -1125,7 +1150,11 @@ std::string Workspaces::makePayload(Args const&... args) {
} }
std::pair<std::string, std::string> Workspaces::splitDoublePayload(std::string const& payload) { std::pair<std::string, std::string> Workspaces::splitDoublePayload(std::string const& payload) {
const std::string part1 = payload.substr(0, payload.find(',')); const auto separator = payload.find(',');
if (separator == std::string::npos) {
throw std::invalid_argument("Expected a two-part Hyprland payload");
}
const std::string part1 = payload.substr(0, separator);
const std::string part2 = payload.substr(part1.size() + 1); const std::string part2 = payload.substr(part1.size() + 1);
return {part1, part2}; return {part1, part2};
} }
@@ -1134,6 +1163,9 @@ std::tuple<std::string, std::string, std::string> Workspaces::splitTriplePayload
std::string const& payload) { std::string const& payload) {
const size_t firstComma = payload.find(','); const size_t firstComma = payload.find(',');
const size_t secondComma = payload.find(',', firstComma + 1); const size_t secondComma = payload.find(',', firstComma + 1);
if (firstComma == std::string::npos || secondComma == std::string::npos) {
throw std::invalid_argument("Expected a three-part Hyprland payload");
}
const std::string part1 = payload.substr(0, firstComma); const std::string part1 = payload.substr(0, firstComma);
const std::string part2 = payload.substr(firstComma + 1, secondComma - (firstComma + 1)); const std::string part2 = payload.substr(firstComma + 1, secondComma - (firstComma + 1));
@@ -1151,4 +1183,31 @@ std::optional<int> Workspaces::parseWorkspaceId(std::string const& workspaceIdSt
} }
} }
bool Workspaces::handleScroll(GdkEventScroll* e) {
// Ignore emulated scroll events on window
if (gdk_event_get_pointer_emulated((GdkEvent*)e)) {
return false;
}
auto dir = AModule::getScrollDir(e);
if (dir == SCROLL_DIR::NONE) {
return true;
}
if (dir == SCROLL_DIR::DOWN || dir == SCROLL_DIR::RIGHT) {
if (allOutputs()) {
m_ipc.getSocket1Reply("dispatch workspace e+1");
} else {
m_ipc.getSocket1Reply("dispatch workspace m+1");
}
} else if (dir == SCROLL_DIR::UP || dir == SCROLL_DIR::LEFT) {
if (allOutputs()) {
m_ipc.getSocket1Reply("dispatch workspace e-1");
} else {
m_ipc.getSocket1Reply("dispatch workspace m-1");
}
}
return true;
}
} // namespace waybar::modules::hyprland } // namespace waybar::modules::hyprland

View File

@@ -14,22 +14,27 @@ waybar::modules::Image::Image(const std::string& id, const Json::Value& config)
size_ = config["size"].asInt(); size_ = config["size"].asInt();
interval_ = config_["interval"] == "once" const auto once = std::chrono::milliseconds::max();
? std::chrono::milliseconds::max() if (!config_.isMember("interval") || config_["interval"].isNull() ||
: std::chrono::milliseconds(std::max( config_["interval"] == "once") {
1L, // Minimum 1ms due to millisecond precision interval_ = once;
static_cast<long>( } else if (config_["interval"].isNumeric()) {
(config_["interval"].isNumeric() ? config_["interval"].asDouble() : 0) * const auto interval_seconds = config_["interval"].asDouble();
1000))); if (interval_seconds <= 0) {
interval_ = once;
} else {
interval_ =
std::chrono::milliseconds(std::max(1L, // Minimum 1ms due to millisecond precision
static_cast<long>(interval_seconds * 1000)));
}
} else {
interval_ = once;
}
if (size_ == 0) { if (size_ == 0) {
size_ = 16; size_ = 16;
} }
if (interval_.count() == 0) {
interval_ = std::chrono::milliseconds::max();
}
delayWorker(); delayWorker();
} }

View File

@@ -85,7 +85,8 @@ auto getInhibitors(const Json::Value& config) -> std::string {
if (config["what"].isArray()) { if (config["what"].isArray()) {
inhibitors = checkInhibitor(config["what"][0].asString()); inhibitors = checkInhibitor(config["what"][0].asString());
for (decltype(config["what"].size()) i = 1; i < config["what"].size(); ++i) { for (decltype(config["what"].size()) i = 1; i < config["what"].size(); ++i) {
inhibitors += ":" + checkInhibitor(config["what"][i].asString()); inhibitors.append(":");
inhibitors.append(checkInhibitor(config["what"][i].asString()));
} }
return inhibitors; return inhibitors;
} }
@@ -123,7 +124,7 @@ auto Inhibitor::update() -> void {
label_.get_style_context()->add_class(status_text); label_.get_style_context()->add_class(status_text);
if (tooltipEnabled()) { if (tooltipEnabled()) {
label_.set_tooltip_text(status_text); label_.set_tooltip_markup(status_text);
} }
return ALabel::update(); return ALabel::update();

View File

@@ -53,7 +53,7 @@ std::string JACK::JACKState() {
auto JACK::update() -> void { auto JACK::update() -> void {
std::string format; std::string format;
std::string state = JACKState(); std::string state = JACKState();
float latency = 1000 * (float)bufsize_ / (float)samplerate_; float latency = samplerate_ > 0 ? 1000.0f * (float)bufsize_ / (float)samplerate_ : 0.0f;
if (label_.get_style_context()->has_class("xrun")) { if (label_.get_style_context()->has_class("xrun")) {
label_.get_style_context()->remove_class("xrun"); label_.get_style_context()->remove_class("xrun");
@@ -80,7 +80,7 @@ auto JACK::update() -> void {
if (tooltipEnabled()) { if (tooltipEnabled()) {
std::string tooltip_format = "{bufsize}/{samplerate} {latency}ms"; std::string tooltip_format = "{bufsize}/{samplerate} {latency}ms";
if (config_["tooltip-format"].isString()) tooltip_format = config_["tooltip-format"].asString(); if (config_["tooltip-format"].isString()) tooltip_format = config_["tooltip-format"].asString();
label_.set_tooltip_text(fmt::format( label_.set_tooltip_markup(fmt::format(
fmt::runtime(tooltip_format), fmt::arg("load", std::round(load_)), fmt::runtime(tooltip_format), fmt::arg("load", std::round(load_)),
fmt::arg("bufsize", bufsize_), fmt::arg("samplerate", samplerate_), fmt::arg("bufsize", bufsize_), fmt::arg("samplerate", samplerate_),
fmt::arg("latency", fmt::format("{:.2f}", latency)), fmt::arg("xruns", xruns_))); fmt::arg("latency", fmt::format("{:.2f}", latency)), fmt::arg("xruns", xruns_)));
@@ -91,16 +91,19 @@ auto JACK::update() -> void {
} }
int JACK::bufSize(jack_nframes_t size) { int JACK::bufSize(jack_nframes_t size) {
std::lock_guard<std::mutex> lock(mutex_);
bufsize_ = size; bufsize_ = size;
return 0; return 0;
} }
int JACK::sampleRate(jack_nframes_t rate) { int JACK::sampleRate(jack_nframes_t rate) {
std::lock_guard<std::mutex> lock(mutex_);
samplerate_ = rate; samplerate_ = rate;
return 0; return 0;
} }
int JACK::xrun() { int JACK::xrun() {
std::lock_guard<std::mutex> lock(mutex_);
xruns_ += 1; xruns_ += 1;
state_ = "xrun"; state_ = "xrun";
return 0; return 0;

View File

@@ -232,9 +232,12 @@ waybar::modules::KeyboardState::KeyboardState(const std::string& id, const Bar&
} }
tryAddDevice(dev_path); tryAddDevice(dev_path);
} else if (event->mask & IN_DELETE) { } else if (event->mask & IN_DELETE) {
std::lock_guard<std::mutex> lock(devices_mutex_);
auto it = libinput_devices_.find(dev_path); auto it = libinput_devices_.find(dev_path);
if (it != libinput_devices_.end()) { if (it != libinput_devices_.end()) {
spdlog::info("Keyboard {} has been removed.", dev_path); spdlog::info("Keyboard {} has been removed.", dev_path);
libinput_path_remove_device(it->second);
libinput_device_unref(it->second);
libinput_devices_.erase(it); libinput_devices_.erase(it);
} }
} }
@@ -245,6 +248,7 @@ waybar::modules::KeyboardState::KeyboardState(const std::string& id, const Bar&
} }
waybar::modules::KeyboardState::~KeyboardState() { waybar::modules::KeyboardState::~KeyboardState() {
std::lock_guard<std::mutex> lock(devices_mutex_);
for (const auto& [_, dev_ptr] : libinput_devices_) { for (const auto& [_, dev_ptr] : libinput_devices_) {
libinput_path_remove_device(dev_ptr); libinput_path_remove_device(dev_ptr);
} }
@@ -256,12 +260,18 @@ auto waybar::modules::KeyboardState::update() -> void {
try { try {
std::string dev_path; std::string dev_path;
{
std::lock_guard<std::mutex> lock(devices_mutex_);
if (libinput_devices_.empty()) {
return;
}
if (config_["device-path"].isString() && if (config_["device-path"].isString() &&
libinput_devices_.find(config_["device-path"].asString()) != libinput_devices_.end()) { libinput_devices_.find(config_["device-path"].asString()) != libinput_devices_.end()) {
dev_path = config_["device-path"].asString(); dev_path = config_["device-path"].asString();
} else { } else {
dev_path = libinput_devices_.begin()->first; dev_path = libinput_devices_.begin()->first;
} }
}
int fd = openFile(dev_path, O_NONBLOCK | O_CLOEXEC | O_RDONLY); int fd = openFile(dev_path, O_NONBLOCK | O_CLOEXEC | O_RDONLY);
auto dev = openDevice(fd); auto dev = openDevice(fd);
numl = libevdev_get_event_value(dev, EV_LED, LED_NUML); numl = libevdev_get_event_value(dev, EV_LED, LED_NUML);
@@ -308,10 +318,15 @@ auto waybar::modules ::KeyboardState::tryAddDevice(const std::string& dev_path)
auto dev = openDevice(fd); auto dev = openDevice(fd);
if (supportsLockStates(dev)) { if (supportsLockStates(dev)) {
spdlog::info("Found device {} at '{}'", libevdev_get_name(dev), dev_path); spdlog::info("Found device {} at '{}'", libevdev_get_name(dev), dev_path);
std::lock_guard<std::mutex> lock(devices_mutex_);
if (libinput_devices_.find(dev_path) == libinput_devices_.end()) { if (libinput_devices_.find(dev_path) == libinput_devices_.end()) {
auto device = libinput_path_add_device(libinput_, dev_path.c_str()); auto device = libinput_path_add_device(libinput_, dev_path.c_str());
if (device) {
libinput_device_ref(device); libinput_device_ref(device);
libinput_devices_[dev_path] = device; libinput_devices_[dev_path] = device;
} else {
spdlog::warn("keyboard-state: Failed to add device to libinput: {}", dev_path);
}
} }
} }
libevdev_free(dev); libevdev_free(dev);

View File

@@ -22,7 +22,7 @@ auto waybar::modules::Load::update() -> void {
auto [load1, load5, load15] = Load::getLoad(); auto [load1, load5, load15] = Load::getLoad();
if (tooltipEnabled()) { if (tooltipEnabled()) {
auto tooltip = fmt::format("Load 1: {}\nLoad 5: {}\nLoad 15: {}", load1, load5, load15); auto tooltip = fmt::format("Load 1: {}\nLoad 5: {}\nLoad 15: {}", load1, load5, load15);
label_.set_tooltip_text(tooltip); label_.set_tooltip_markup(tooltip);
} }
auto format = format_; auto format = format_;
auto state = getState(load1); auto state = getState(load1);

View File

@@ -69,7 +69,7 @@ auto waybar::modules::Memory::update() -> void {
if (tooltipEnabled()) { if (tooltipEnabled()) {
if (config_["tooltip-format"].isString()) { if (config_["tooltip-format"].isString()) {
auto tooltip_format = config_["tooltip-format"].asString(); auto tooltip_format = config_["tooltip-format"].asString();
label_.set_tooltip_text(fmt::format( label_.set_tooltip_markup(fmt::format(
fmt::runtime(tooltip_format), used_ram_percentage, fmt::runtime(tooltip_format), used_ram_percentage,
fmt::arg("total", total_ram_gigabytes), fmt::arg("swapTotal", total_swap_gigabytes), fmt::arg("total", total_ram_gigabytes), fmt::arg("swapTotal", total_swap_gigabytes),
fmt::arg("percentage", used_ram_percentage), fmt::arg("percentage", used_ram_percentage),
@@ -78,7 +78,7 @@ auto waybar::modules::Memory::update() -> void {
fmt::arg("swapUsed", used_swap_gigabytes), fmt::arg("avail", available_ram_gigabytes), fmt::arg("swapUsed", used_swap_gigabytes), fmt::arg("avail", available_ram_gigabytes),
fmt::arg("swapAvail", available_swap_gigabytes))); fmt::arg("swapAvail", available_swap_gigabytes)));
} else { } else {
label_.set_tooltip_text(fmt::format("{:.{}f}GiB used", used_ram_gigabytes, 1)); label_.set_tooltip_markup(fmt::format("{:.{}f}GiB used", used_ram_gigabytes, 1));
} }
} }
} else { } else {

View File

@@ -110,7 +110,7 @@ void waybar::modules::MPD::setLabel() {
? config_["tooltip-format-disconnected"].asString() ? config_["tooltip-format-disconnected"].asString()
: "MPD (disconnected)"; : "MPD (disconnected)";
// Nothing to format // Nothing to format
label_.set_tooltip_text(tooltip_format); label_.set_tooltip_markup(tooltip_format);
} }
return; return;
} }
@@ -210,7 +210,7 @@ void waybar::modules::MPD::setLabel() {
fmt::arg("stateIcon", stateIcon), fmt::arg("consumeIcon", consumeIcon), fmt::arg("stateIcon", stateIcon), fmt::arg("consumeIcon", consumeIcon),
fmt::arg("randomIcon", randomIcon), fmt::arg("repeatIcon", repeatIcon), fmt::arg("randomIcon", randomIcon), fmt::arg("repeatIcon", repeatIcon),
fmt::arg("singleIcon", singleIcon), fmt::arg("filename", filename), fmt::arg("uri", uri)); fmt::arg("singleIcon", singleIcon), fmt::arg("filename", filename), fmt::arg("uri", uri));
label_.set_tooltip_text(tooltip_text); label_.set_tooltip_markup(tooltip_text);
} catch (fmt::format_error const& e) { } catch (fmt::format_error const& e) {
spdlog::warn("mpd: format error (tooltip): {}", e.what()); spdlog::warn("mpd: format error (tooltip): {}", e.what());
} }
@@ -239,7 +239,8 @@ std::string waybar::modules::MPD::getStateIcon() const {
} }
} }
std::string waybar::modules::MPD::getOptionIcon(std::string optionName, bool activated) const { std::string waybar::modules::MPD::getOptionIcon(const std::string& optionName,
bool activated) const {
if (!config_[optionName + "-icons"].isObject()) { if (!config_[optionName + "-icons"].isObject()) {
return ""; return "";
} }
@@ -323,6 +324,7 @@ void waybar::modules::MPD::checkErrors(mpd_connection* conn) {
case MPD_ERROR_SYSTEM: case MPD_ERROR_SYSTEM:
if (auto ec = mpd_connection_get_system_error(conn); ec != 0) { if (auto ec = mpd_connection_get_system_error(conn); ec != 0) {
mpd_connection_clear_error(conn); mpd_connection_clear_error(conn);
connection_.reset();
throw std::system_error(ec, std::system_category()); throw std::system_error(ec, std::system_category());
} }
G_GNUC_FALLTHROUGH; G_GNUC_FALLTHROUGH;

View File

@@ -109,6 +109,7 @@ Mpris::Mpris(const std::string& id, const Json::Value& config)
player_ = config_["player"].asString(); player_ = config_["player"].asString();
} }
if (config_["ignored-players"].isArray()) { if (config_["ignored-players"].isArray()) {
ignored_players_.reserve(config_["ignored-players"].size());
for (const auto& item : config_["ignored-players"]) { for (const auto& item : config_["ignored-players"]) {
if (item.isString()) { if (item.isString()) {
ignored_players_.push_back(item.asString()); ignored_players_.push_back(item.asString());
@@ -117,7 +118,7 @@ Mpris::Mpris(const std::string& id, const Json::Value& config)
} }
GError* error = nullptr; GError* error = nullptr;
waybar::util::ScopeGuard error_deleter([error]() { waybar::util::ScopeGuard error_deleter([&error]() {
if (error) { if (error) {
g_error_free(error); g_error_free(error);
} }
@@ -161,8 +162,7 @@ Mpris::Mpris(const std::string& id, const Json::Value& config)
if (player) { if (player) {
g_object_connect(player, "signal::play", G_CALLBACK(onPlayerPlay), this, "signal::pause", g_object_connect(player, "signal::play", G_CALLBACK(onPlayerPlay), this, "signal::pause",
G_CALLBACK(onPlayerPause), this, "signal::stop", G_CALLBACK(onPlayerStop), G_CALLBACK(onPlayerPause), this, "signal::stop", G_CALLBACK(onPlayerStop),
this, "signal::stop", G_CALLBACK(onPlayerStop), this, "signal::metadata", this, "signal::metadata", G_CALLBACK(onPlayerMetadata), this, NULL);
G_CALLBACK(onPlayerMetadata), this, NULL);
} }
// allow setting an interval count that triggers periodic refreshes // allow setting an interval count that triggers periodic refreshes
@@ -178,8 +178,17 @@ Mpris::Mpris(const std::string& id, const Json::Value& config)
} }
Mpris::~Mpris() { Mpris::~Mpris() {
if (manager != nullptr) g_object_unref(manager); if (manager != nullptr) {
if (player != nullptr) g_object_unref(player); g_signal_handlers_disconnect_by_data(manager, this);
}
if (player != nullptr) {
g_signal_handlers_disconnect_by_data(player, this);
}
if (last_active_player_ != nullptr && last_active_player_ != player) {
g_object_unref(last_active_player_);
}
g_clear_object(&manager);
g_clear_object(&player);
} }
auto Mpris::getIconFromJson(const Json::Value& icons, const std::string& key) -> std::string { auto Mpris::getIconFromJson(const Json::Value& icons, const std::string& key) -> std::string {
@@ -410,11 +419,14 @@ auto Mpris::onPlayerNameAppeared(PlayerctlPlayerManager* manager, PlayerctlPlaye
return; return;
} }
if (mpris->player != nullptr) {
g_signal_handlers_disconnect_by_data(mpris->player, mpris);
g_clear_object(&mpris->player);
}
mpris->player = playerctl_player_new_from_name(player_name, nullptr); mpris->player = playerctl_player_new_from_name(player_name, nullptr);
g_object_connect(mpris->player, "signal::play", G_CALLBACK(onPlayerPlay), mpris, "signal::pause", g_object_connect(mpris->player, "signal::play", G_CALLBACK(onPlayerPlay), mpris, "signal::pause",
G_CALLBACK(onPlayerPause), mpris, "signal::stop", G_CALLBACK(onPlayerStop), G_CALLBACK(onPlayerPause), mpris, "signal::stop", G_CALLBACK(onPlayerStop),
mpris, "signal::stop", G_CALLBACK(onPlayerStop), mpris, "signal::metadata", mpris, "signal::metadata", G_CALLBACK(onPlayerMetadata), mpris, NULL);
G_CALLBACK(onPlayerMetadata), mpris, NULL);
mpris->dp.emit(); mpris->dp.emit();
} }
@@ -458,10 +470,7 @@ auto Mpris::onPlayerStop(PlayerctlPlayer* player, gpointer data) -> void {
if (!mpris) return; if (!mpris) return;
spdlog::debug("mpris: player-stop callback"); spdlog::debug("mpris: player-stop callback");
// update widget (update() handles visibility)
// hide widget
mpris->event_box_.set_visible(false);
// update widget
mpris->dp.emit(); mpris->dp.emit();
} }
@@ -480,7 +489,7 @@ auto Mpris::getPlayerInfo() -> std::optional<PlayerInfo> {
} }
GError* error = nullptr; GError* error = nullptr;
waybar::util::ScopeGuard error_deleter([error]() { waybar::util::ScopeGuard error_deleter([&error]() {
if (error) { if (error) {
g_error_free(error); g_error_free(error);
} }
@@ -488,7 +497,12 @@ auto Mpris::getPlayerInfo() -> std::optional<PlayerInfo> {
char* player_status = nullptr; char* player_status = nullptr;
auto player_playback_status = PLAYERCTL_PLAYBACK_STATUS_STOPPED; auto player_playback_status = PLAYERCTL_PLAYBACK_STATUS_STOPPED;
g_object_get(player, "status", &player_status, "playback-status", &player_playback_status, NULL);
// Clean up previous fallback player
if (last_active_player_ && last_active_player_ != player) {
g_object_unref(last_active_player_);
last_active_player_ = nullptr;
}
std::string player_name = player_; std::string player_name = player_;
if (player_name == "playerctld") { if (player_name == "playerctld") {
@@ -498,19 +512,52 @@ auto Mpris::getPlayerInfo() -> std::optional<PlayerInfo> {
} }
// > get the list of players [..] in order of activity // > get the list of players [..] in order of activity
// https://github.com/altdesktop/playerctl/blob/b19a71cb9dba635df68d271bd2b3f6a99336a223/playerctl/playerctl-common.c#L248-L249 // https://github.com/altdesktop/playerctl/blob/b19a71cb9dba635df68d271bd2b3f6a99336a223/playerctl/playerctl-common.c#L248-L249
players = g_list_first(players); PlayerctlPlayer* first_valid_player = nullptr;
if (players) std::string first_valid_name;
player_name = static_cast<PlayerctlPlayerName*>(players->data)->name; for (auto* p = g_list_first(players); p != nullptr; p = p->next) {
else auto* pn = static_cast<PlayerctlPlayerName*>(p->data);
return std::nullopt; // no players found, hide the widget std::string name = pn->name;
}
if (std::any_of(ignored_players_.begin(), ignored_players_.end(), if (std::any_of(ignored_players_.begin(), ignored_players_.end(),
[&](const std::string& ignored) { return name == ignored; })) {
spdlog::warn("mpris[{}]: ignoring player update", name);
continue;
}
auto* tmp = playerctl_player_new_from_name(pn, &error);
if (error || !tmp) continue;
if (!first_valid_player) {
first_valid_player = tmp;
first_valid_name = name;
}
PlayerctlPlaybackStatus status;
g_object_get(tmp, "playback-status", &status, NULL);
if (status == PLAYERCTL_PLAYBACK_STATUS_PLAYING) {
if (tmp != first_valid_player) g_object_unref(first_valid_player);
last_active_player_ = tmp;
player_name = name;
break;
}
if (tmp != first_valid_player) g_object_unref(tmp);
}
if (!last_active_player_) {
if (!first_valid_player) return std::nullopt;
last_active_player_ = first_valid_player;
player_name = first_valid_name;
}
} else if (std::any_of(ignored_players_.begin(), ignored_players_.end(),
[&](const std::string& pn) { return player_name == pn; })) { [&](const std::string& pn) { return player_name == pn; })) {
spdlog::warn("mpris[{}]: ignoring player update", player_name); spdlog::warn("mpris[{}]: ignoring player update", player_name);
return std::nullopt; return std::nullopt;
} else {
last_active_player_ = player;
} }
g_object_get(last_active_player_, "status", &player_status, "playback-status",
&player_playback_status, NULL);
if (!player_status) {
spdlog::error("mpris: failed to get player status");
return std::nullopt;
}
// make status lowercase // make status lowercase
player_status[0] = std::tolower(player_status[0]); player_status[0] = std::tolower(player_status[0]);
@@ -524,28 +571,29 @@ auto Mpris::getPlayerInfo() -> std::optional<PlayerInfo> {
.length = std::nullopt, .length = std::nullopt,
}; };
if (auto* artist_ = playerctl_player_get_artist(player, &error)) { if (auto* artist_ = playerctl_player_get_artist(last_active_player_, &error)) {
spdlog::debug("mpris[{}]: artist = {}", info.name, artist_); spdlog::debug("mpris[{}]: artist = {}", info.name, artist_);
info.artist = artist_; info.artist = artist_;
g_free(artist_); g_free(artist_);
} }
if (error) goto errorexit; if (error) goto errorexit;
if (auto* album_ = playerctl_player_get_album(player, &error)) { if (auto* album_ = playerctl_player_get_album(last_active_player_, &error)) {
spdlog::debug("mpris[{}]: album = {}", info.name, album_); spdlog::debug("mpris[{}]: album = {}", info.name, album_);
info.album = album_; info.album = album_;
g_free(album_); g_free(album_);
} }
if (error) goto errorexit; if (error) goto errorexit;
if (auto* title_ = playerctl_player_get_title(player, &error)) { if (auto* title_ = playerctl_player_get_title(last_active_player_, &error)) {
spdlog::debug("mpris[{}]: title = {}", info.name, title_); spdlog::debug("mpris[{}]: title = {}", info.name, title_);
info.title = title_; info.title = title_;
g_free(title_); g_free(title_);
} }
if (error) goto errorexit; if (error) goto errorexit;
if (auto* length_ = playerctl_player_print_metadata_prop(player, "mpris:length", &error)) { if (auto* length_ =
playerctl_player_print_metadata_prop(last_active_player_, "mpris:length", &error)) {
spdlog::debug("mpris[{}]: mpris:length = {}", info.name, length_); spdlog::debug("mpris[{}]: mpris:length = {}", info.name, length_);
auto len = std::chrono::microseconds(std::strtol(length_, nullptr, 10)); auto len = std::chrono::microseconds(std::strtol(length_, nullptr, 10));
auto len_h = std::chrono::duration_cast<std::chrono::hours>(len); auto len_h = std::chrono::duration_cast<std::chrono::hours>(len);
@@ -557,7 +605,7 @@ auto Mpris::getPlayerInfo() -> std::optional<PlayerInfo> {
if (error) goto errorexit; if (error) goto errorexit;
{ {
auto position_ = playerctl_player_get_position(player, &error); auto position_ = playerctl_player_get_position(last_active_player_, &error);
if (error) { if (error) {
// it's fine to have an error here because not all players report a position // it's fine to have an error here because not all players report a position
g_error_free(error); g_error_free(error);
@@ -609,12 +657,13 @@ bool Mpris::handleToggle(GdkEventButton* const& e) {
}); });
// Command pattern: encapsulate each button's action // Command pattern: encapsulate each button's action
auto* target = last_active_player_ ? last_active_player_ : player;
const ButtonAction actions[] = { const ButtonAction actions[] = {
{1, "on-click", [&]() { playerctl_player_play_pause(player, &error); }}, {1, "on-click", [&]() { playerctl_player_play_pause(target, &error); }},
{2, "on-click-middle", [&]() { playerctl_player_previous(player, &error); }}, {2, "on-click-middle", [&]() { playerctl_player_previous(target, &error); }},
{3, "on-click-right", [&]() { playerctl_player_next(player, &error); }}, {3, "on-click-right", [&]() { playerctl_player_next(target, &error); }},
{8, "on-click-backward", [&]() { playerctl_player_previous(player, &error); }}, {8, "on-click-backward", [&]() { playerctl_player_previous(target, &error); }},
{9, "on-click-forward", [&]() { playerctl_player_next(player, &error); }}, {9, "on-click-forward", [&]() { playerctl_player_next(target, &error); }},
}; };
for (const auto& action : actions) { for (const auto& action : actions) {
@@ -734,7 +783,7 @@ auto Mpris::update() -> void {
fmt::arg("player_icon", getIconFromJson(config_["player-icons"], info.name)), fmt::arg("player_icon", getIconFromJson(config_["player-icons"], info.name)),
fmt::arg("status_icon", getIconFromJson(config_["status-icons"], info.status_string))); fmt::arg("status_icon", getIconFromJson(config_["status-icons"], info.status_string)));
label_.set_tooltip_text(tooltip_text); label_.set_tooltip_markup(tooltip_text);
} catch (fmt::format_error const& e) { } catch (fmt::format_error const& e) {
spdlog::warn("mpris: format error (tooltip): {}", e.what()); spdlog::warn("mpris: format error (tooltip): {}", e.what());
} }

View File

@@ -46,6 +46,7 @@ waybar::modules::Network::readBandwidthUsage() {
std::string ifacename; std::string ifacename;
iss >> ifacename; // ifacename contains "eth0:" iss >> ifacename; // ifacename contains "eth0:"
if (ifacename.empty()) continue;
ifacename.pop_back(); // remove trailing ':' ifacename.pop_back(); // remove trailing ':'
if (ifacename != ifname_) { if (ifacename != ifname_) {
continue; continue;
@@ -104,6 +105,7 @@ waybar::modules::Network::Network(const std::string& id, const Json::Value& conf
bandwidth_down_total_ = 0; bandwidth_down_total_ = 0;
bandwidth_up_total_ = 0; bandwidth_up_total_ = 0;
} }
bandwidth_last_sample_time_ = std::chrono::steady_clock::now();
if (!config_["interface"].isString()) { if (!config_["interface"].isString()) {
// "interface" isn't configured, then try to guess the external // "interface" isn't configured, then try to guess the external
@@ -276,6 +278,12 @@ const std::string waybar::modules::Network::getNetworkState() const {
auto waybar::modules::Network::update() -> void { auto waybar::modules::Network::update() -> void {
std::lock_guard<std::mutex> lock(mutex_); std::lock_guard<std::mutex> lock(mutex_);
std::string tooltip_format; std::string tooltip_format;
auto now = std::chrono::steady_clock::now();
auto elapsed_seconds = std::chrono::duration<double>(now - bandwidth_last_sample_time_).count();
if (elapsed_seconds <= 0.0) {
elapsed_seconds = std::chrono::duration<double>(interval_).count();
}
bandwidth_last_sample_time_ = now;
auto bandwidth = readBandwidthUsage(); auto bandwidth = readBandwidthUsage();
auto bandwidth_down = 0ull; auto bandwidth_down = 0ull;
@@ -320,6 +328,7 @@ auto waybar::modules::Network::update() -> void {
} else if (addr_pref_ == ip_addr_pref::IPV6) { } else if (addr_pref_ == ip_addr_pref::IPV6) {
final_ipaddr_ = ipaddr6_; final_ipaddr_ = ipaddr6_;
} else if (addr_pref_ == ip_addr_pref::IPV4_6) { } else if (addr_pref_ == ip_addr_pref::IPV4_6) {
final_ipaddr_.reserve(ipaddr_.length() + ipaddr6_.length() + 1);
final_ipaddr_ = ipaddr_; final_ipaddr_ = ipaddr_;
final_ipaddr_ += '\n'; final_ipaddr_ += '\n';
final_ipaddr_ += ipaddr6_; final_ipaddr_ += ipaddr6_;
@@ -333,23 +342,18 @@ auto waybar::modules::Network::update() -> void {
fmt::arg("ipaddr", final_ipaddr_), fmt::arg("gwaddr", gwaddr_), fmt::arg("cidr", cidr_), fmt::arg("ipaddr", final_ipaddr_), fmt::arg("gwaddr", gwaddr_), fmt::arg("cidr", cidr_),
fmt::arg("cidr6", cidr6_), fmt::arg("frequency", fmt::format("{:.1f}", frequency_)), fmt::arg("cidr6", cidr6_), fmt::arg("frequency", fmt::format("{:.1f}", frequency_)),
fmt::arg("icon", getIcon(signal_strength_, state_)), fmt::arg("icon", getIcon(signal_strength_, state_)),
fmt::arg("bandwidthDownBits", fmt::arg("bandwidthDownBits", pow_format(bandwidth_down * 8ull / elapsed_seconds, "b/s")),
pow_format(bandwidth_down * 8ull / (interval_.count() / 1000.0), "b/s")), fmt::arg("bandwidthUpBits", pow_format(bandwidth_up * 8ull / elapsed_seconds, "b/s")),
fmt::arg("bandwidthUpBits", fmt::arg("bandwidthTotalBits",
pow_format(bandwidth_up * 8ull / (interval_.count() / 1000.0), "b/s")), pow_format((bandwidth_up + bandwidth_down) * 8ull / elapsed_seconds, "b/s")),
fmt::arg( fmt::arg("bandwidthDownOctets", pow_format(bandwidth_down / elapsed_seconds, "o/s")),
"bandwidthTotalBits", fmt::arg("bandwidthUpOctets", pow_format(bandwidth_up / elapsed_seconds, "o/s")),
pow_format((bandwidth_up + bandwidth_down) * 8ull / (interval_.count() / 1000.0), "b/s")),
fmt::arg("bandwidthDownOctets",
pow_format(bandwidth_down / (interval_.count() / 1000.0), "o/s")),
fmt::arg("bandwidthUpOctets", pow_format(bandwidth_up / (interval_.count() / 1000.0), "o/s")),
fmt::arg("bandwidthTotalOctets", fmt::arg("bandwidthTotalOctets",
pow_format((bandwidth_up + bandwidth_down) / (interval_.count() / 1000.0), "o/s")), pow_format((bandwidth_up + bandwidth_down) / elapsed_seconds, "o/s")),
fmt::arg("bandwidthDownBytes", fmt::arg("bandwidthDownBytes", pow_format(bandwidth_down / elapsed_seconds, "B/s")),
pow_format(bandwidth_down / (interval_.count() / 1000.0), "B/s")), fmt::arg("bandwidthUpBytes", pow_format(bandwidth_up / elapsed_seconds, "B/s")),
fmt::arg("bandwidthUpBytes", pow_format(bandwidth_up / (interval_.count() / 1000.0), "B/s")),
fmt::arg("bandwidthTotalBytes", fmt::arg("bandwidthTotalBytes",
pow_format((bandwidth_up + bandwidth_down) / (interval_.count() / 1000.0), "B/s"))); pow_format((bandwidth_up + bandwidth_down) / elapsed_seconds, "B/s")));
if (text.compare(label_.get_label()) != 0) { if (text.compare(label_.get_label()) != 0) {
label_.set_markup(text); label_.set_markup(text);
if (text.empty()) { if (text.empty()) {
@@ -371,19 +375,18 @@ auto waybar::modules::Network::update() -> void {
fmt::arg("ipaddr", final_ipaddr_), fmt::arg("gwaddr", gwaddr_), fmt::arg("cidr", cidr_), fmt::arg("ipaddr", final_ipaddr_), fmt::arg("gwaddr", gwaddr_), fmt::arg("cidr", cidr_),
fmt::arg("cidr6", cidr6_), fmt::arg("frequency", fmt::format("{:.1f}", frequency_)), fmt::arg("cidr6", cidr6_), fmt::arg("frequency", fmt::format("{:.1f}", frequency_)),
fmt::arg("icon", getIcon(signal_strength_, state_)), fmt::arg("icon", getIcon(signal_strength_, state_)),
fmt::arg("bandwidthDownBits", fmt::arg("bandwidthDownBits", pow_format(bandwidth_down * 8ull / elapsed_seconds, "b/s")),
pow_format(bandwidth_down * 8ull / interval_.count(), "b/s")), fmt::arg("bandwidthUpBits", pow_format(bandwidth_up * 8ull / elapsed_seconds, "b/s")),
fmt::arg("bandwidthUpBits", pow_format(bandwidth_up * 8ull / interval_.count(), "b/s")),
fmt::arg("bandwidthTotalBits", fmt::arg("bandwidthTotalBits",
pow_format((bandwidth_up + bandwidth_down) * 8ull / interval_.count(), "b/s")), pow_format((bandwidth_up + bandwidth_down) * 8ull / elapsed_seconds, "b/s")),
fmt::arg("bandwidthDownOctets", pow_format(bandwidth_down / interval_.count(), "o/s")), fmt::arg("bandwidthDownOctets", pow_format(bandwidth_down / elapsed_seconds, "o/s")),
fmt::arg("bandwidthUpOctets", pow_format(bandwidth_up / interval_.count(), "o/s")), fmt::arg("bandwidthUpOctets", pow_format(bandwidth_up / elapsed_seconds, "o/s")),
fmt::arg("bandwidthTotalOctets", fmt::arg("bandwidthTotalOctets",
pow_format((bandwidth_up + bandwidth_down) / interval_.count(), "o/s")), pow_format((bandwidth_up + bandwidth_down) / elapsed_seconds, "o/s")),
fmt::arg("bandwidthDownBytes", pow_format(bandwidth_down / interval_.count(), "B/s")), fmt::arg("bandwidthDownBytes", pow_format(bandwidth_down / elapsed_seconds, "B/s")),
fmt::arg("bandwidthUpBytes", pow_format(bandwidth_up / interval_.count(), "B/s")), fmt::arg("bandwidthUpBytes", pow_format(bandwidth_up / elapsed_seconds, "B/s")),
fmt::arg("bandwidthTotalBytes", fmt::arg("bandwidthTotalBytes",
pow_format((bandwidth_up + bandwidth_down) / interval_.count(), "B/s"))); pow_format((bandwidth_up + bandwidth_down) / elapsed_seconds, "B/s")));
if (label_.get_tooltip_text() != tooltip_text) { if (label_.get_tooltip_text() != tooltip_text) {
label_.set_tooltip_markup(tooltip_text); label_.set_tooltip_markup(tooltip_text);
} }
@@ -625,18 +628,31 @@ int waybar::modules::Network::handleEvents(struct nl_msg* msg, void* data) {
case IFA_LOCAL: case IFA_LOCAL:
char ipaddr[INET6_ADDRSTRLEN]; char ipaddr[INET6_ADDRSTRLEN];
if (!is_del_event) { if (!is_del_event) {
bool addr_changed = false;
std::string changed_ipaddr;
int changed_cidr = 0;
if ((net->addr_pref_ == ip_addr_pref::IPV4 || if ((net->addr_pref_ == ip_addr_pref::IPV4 ||
net->addr_pref_ == ip_addr_pref::IPV4_6) && net->addr_pref_ == ip_addr_pref::IPV4_6) &&
net->cidr_ == 0 && ifa->ifa_family == AF_INET) { net->cidr_ == 0 && ifa->ifa_family == AF_INET) {
net->ipaddr_ = if (inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta), ipaddr, sizeof(ipaddr)) !=
inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta), ipaddr, sizeof(ipaddr)); nullptr) {
net->ipaddr_ = ipaddr;
net->cidr_ = ifa->ifa_prefixlen; net->cidr_ = ifa->ifa_prefixlen;
addr_changed = true;
changed_ipaddr = net->ipaddr_;
changed_cidr = net->cidr_;
}
} else if ((net->addr_pref_ == ip_addr_pref::IPV6 || } else if ((net->addr_pref_ == ip_addr_pref::IPV6 ||
net->addr_pref_ == ip_addr_pref::IPV4_6) && net->addr_pref_ == ip_addr_pref::IPV4_6) &&
net->cidr6_ == 0 && ifa->ifa_family == AF_INET6) { net->cidr6_ == 0 && ifa->ifa_family == AF_INET6) {
net->ipaddr6_ = if (inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta), ipaddr, sizeof(ipaddr)) !=
inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta), ipaddr, sizeof(ipaddr)); nullptr) {
net->ipaddr6_ = ipaddr;
net->cidr6_ = ifa->ifa_prefixlen; net->cidr6_ = ifa->ifa_prefixlen;
addr_changed = true;
changed_ipaddr = net->ipaddr6_;
changed_cidr = net->cidr6_;
}
} }
switch (ifa->ifa_family) { switch (ifa->ifa_family) {
@@ -656,7 +672,10 @@ int waybar::modules::Network::handleEvents(struct nl_msg* msg, void* data) {
net->netmask6_ = inet_ntop(ifa->ifa_family, &netmask6, ipaddr, sizeof(ipaddr)); net->netmask6_ = inet_ntop(ifa->ifa_family, &netmask6, ipaddr, sizeof(ipaddr));
} }
} }
spdlog::debug("network: {}, new addr {}/{}", net->ifname_, net->ipaddr_, net->cidr_); if (addr_changed) {
spdlog::debug("network: {}, new addr {}/{}", net->ifname_, changed_ipaddr,
changed_cidr);
}
} else { } else {
net->ipaddr_.clear(); net->ipaddr_.clear();
net->ipaddr6_.clear(); net->ipaddr6_.clear();
@@ -718,16 +737,20 @@ int waybar::modules::Network::handleEvents(struct nl_msg* msg, void* data) {
/* The destination address. /* The destination address.
* Should be either missing, or maybe all 0s. Accept both. * Should be either missing, or maybe all 0s. Accept both.
*/ */
const uint32_t nr_zeroes = (family == AF_INET) ? 4 : 16; auto* dest = (const unsigned char*)RTA_DATA(attr);
unsigned char c = 0; size_t dest_size = RTA_PAYLOAD(attr);
size_t dstlen = RTA_PAYLOAD(attr); for (size_t i = 0; i < dest_size; ++i) {
if (dstlen != nr_zeroes) { if (dest[i] != 0) {
has_destination = true;
break; break;
} }
for (uint32_t i = 0; i < dstlen; i += 1) {
c |= *((unsigned char*)RTA_DATA(attr) + i);
} }
has_destination = (c == 0);
if (rtm->rtm_dst_len != 0) {
// We have found a destination like 0.0.0.0/24, this is not a
// default gateway route.
has_destination = true;
}
break; break;
} }
case RTA_OIF: case RTA_OIF:

View File

@@ -17,19 +17,21 @@
#include "giomm/dataoutputstream.h" #include "giomm/dataoutputstream.h"
#include "giomm/unixinputstream.h" #include "giomm/unixinputstream.h"
#include "giomm/unixoutputstream.h" #include "giomm/unixoutputstream.h"
#include "util/scoped_fd.hpp"
namespace waybar::modules::niri { namespace waybar::modules::niri {
IPC::IPC() { startIPC(); }
int IPC::connectToSocket() { int IPC::connectToSocket() {
const char* socket_path = getenv("NIRI_SOCKET"); const char* socket_path = getenv("NIRI_SOCKET");
if (socket_path == nullptr) { if (socket_path == nullptr) {
spdlog::warn("Niri is not running, niri IPC will not be available."); throw std::runtime_error("Niri IPC: NIRI_SOCKET was not set! (Is Niri running?)");
return -1;
} }
struct sockaddr_un addr; struct sockaddr_un addr;
int socketfd = socket(AF_UNIX, SOCK_STREAM, 0); util::ScopedFd socketfd(socket(AF_UNIX, SOCK_STREAM, 0));
if (socketfd == -1) { if (socketfd == -1) {
throw std::runtime_error("socketfd failed"); throw std::runtime_error("socketfd failed");
@@ -44,26 +46,18 @@ int IPC::connectToSocket() {
int l = sizeof(struct sockaddr_un); int l = sizeof(struct sockaddr_un);
if (connect(socketfd, (struct sockaddr*)&addr, l) == -1) { if (connect(socketfd, (struct sockaddr*)&addr, l) == -1) {
close(socketfd);
throw std::runtime_error("unable to connect"); throw std::runtime_error("unable to connect");
} }
return socketfd; return socketfd.release();
} }
void IPC::startIPC() { void IPC::startIPC() {
// will start IPC and relay events to parseIPC // will start IPC and relay events to parseIPC
std::thread([&]() { int socketfd = connectToSocket();
int socketfd;
try {
socketfd = connectToSocket();
} catch (std::exception& e) {
spdlog::error("Niri IPC: failed to start, reason: {}", e.what());
return;
}
if (socketfd == -1) return;
std::thread([this, socketfd]() {
spdlog::info("Niri IPC starting"); spdlog::info("Niri IPC starting");
auto unix_istream = Gio::UnixInputStream::create(socketfd, true); auto unix_istream = Gio::UnixInputStream::create(socketfd, true);
@@ -241,8 +235,7 @@ void IPC::unregisterForIPC(EventHandler* ev_handler) {
} }
Json::Value IPC::send(const Json::Value& request) { Json::Value IPC::send(const Json::Value& request) {
int socketfd = connectToSocket(); util::ScopedFd socketfd(connectToSocket());
if (socketfd == -1) throw std::runtime_error("Niri is not running");
auto unix_istream = Gio::UnixInputStream::create(socketfd, true); auto unix_istream = Gio::UnixInputStream::create(socketfd, true);
auto unix_ostream = Gio::UnixOutputStream::create(socketfd, false); auto unix_ostream = Gio::UnixOutputStream::create(socketfd, false);

View File

@@ -32,6 +32,7 @@ void Language::updateFromIPC() {
auto ipcLock = gIPC->lockData(); auto ipcLock = gIPC->lockData();
layouts_.clear(); layouts_.clear();
layouts_.reserve(gIPC->keyboardLayoutNames().size());
for (const auto& fullName : gIPC->keyboardLayoutNames()) layouts_.push_back(getLayout(fullName)); for (const auto& fullName : gIPC->keyboardLayoutNames()) layouts_.push_back(getLayout(fullName));
current_idx_ = gIPC->keyboardLayoutCurrent(); current_idx_ = gIPC->keyboardLayoutCurrent();

View File

@@ -67,7 +67,7 @@ void Window::doUpdate() {
updateAppIconName(appId, ""); updateAppIconName(appId, "");
if (tooltipEnabled()) label_.set_tooltip_text(title); if (tooltipEnabled()) label_.set_tooltip_markup(title);
const auto id = window["id"].asUInt64(); const auto id = window["id"].asUInt64();
const auto workspaceId = window["workspace_id"].asUInt64(); const auto workspaceId = window["workspace_id"].asUInt64();

View File

@@ -174,11 +174,11 @@ std::string Workspaces::getIcon(const std::string& value, const Json::Value& ws)
if (ws["is_urgent"].asBool() && icons["urgent"]) return icons["urgent"].asString(); if (ws["is_urgent"].asBool() && icons["urgent"]) return icons["urgent"].asString();
if (ws["active_window_id"].isNull() && icons["empty"]) return icons["empty"].asString(); if (ws["is_active"].asBool() && icons["active"]) return icons["active"].asString();
if (ws["is_focused"].asBool() && icons["focused"]) return icons["focused"].asString(); if (ws["is_focused"].asBool() && icons["focused"]) return icons["focused"].asString();
if (ws["is_active"].asBool() && icons["active"]) return icons["active"].asString(); if (ws["active_window_id"].isNull() && icons["empty"]) return icons["empty"].asString();
if (ws["name"]) { if (ws["name"]) {
const auto& name = ws["name"].asString(); const auto& name = ws["name"].asString();

View File

@@ -157,7 +157,7 @@ auto PowerProfilesDaemon::update() -> void {
store.push_back(fmt::arg("icon", getIcon(0, profile.name))); store.push_back(fmt::arg("icon", getIcon(0, profile.name)));
label_.set_markup(fmt::vformat(format_, store)); label_.set_markup(fmt::vformat(format_, store));
if (tooltipEnabled()) { if (tooltipEnabled()) {
label_.set_tooltip_text(fmt::vformat(tooltipFormat_, store)); label_.set_tooltip_markup(fmt::vformat(tooltipFormat_, store));
} }
// Set CSS class // Set CSS class
@@ -176,6 +176,10 @@ auto PowerProfilesDaemon::update() -> void {
bool PowerProfilesDaemon::handleToggle(GdkEventButton* const& e) { bool PowerProfilesDaemon::handleToggle(GdkEventButton* const& e) {
if (e->type == GdkEventType::GDK_BUTTON_PRESS && connected_) { if (e->type == GdkEventType::GDK_BUTTON_PRESS && connected_) {
if (availableProfiles_.empty()) return true;
if (activeProfile_ == availableProfiles_.end()) {
activeProfile_ = availableProfiles_.begin();
}
if (e->button == 1) /* left click */ { if (e->button == 1) /* left click */ {
activeProfile_++; activeProfile_++;
if (activeProfile_ == availableProfiles_.end()) { if (activeProfile_ == availableProfiles_.end()) {

View File

@@ -138,7 +138,7 @@ auto waybar::modules::Pulseaudio::update() -> void {
fmt::arg("source_volume", source_volume), fmt::arg("source_desc", source_desc), fmt::arg("source_volume", source_volume), fmt::arg("source_desc", source_desc),
fmt::arg("icon", getIcon(sink_volume, getPulseIcon())))); fmt::arg("icon", getIcon(sink_volume, getPulseIcon()))));
} else { } else {
label_.set_tooltip_text(sink_desc); label_.set_tooltip_markup(sink_desc);
} }
} }

View File

@@ -118,6 +118,7 @@ Layout::Layout(const std::string& id, const waybar::Bar& bar, const Json::Value&
if (!seat_) { if (!seat_) {
spdlog::error("wl_seat not advertised"); spdlog::error("wl_seat not advertised");
return;
} }
label_.hide(); label_.hide();

View File

@@ -77,6 +77,7 @@ Mode::Mode(const std::string& id, const waybar::Bar& bar, const Json::Value& con
if (!seat_) { if (!seat_) {
spdlog::error("wl_seat not advertised"); spdlog::error("wl_seat not advertised");
return;
} }
if (config_["hidden-modes"].isArray()) { if (config_["hidden-modes"].isArray()) {

View File

@@ -141,10 +141,12 @@ Tags::Tags(const std::string& id, const waybar::Bar& bar, const Json::Value& con
if (!control_) { if (!control_) {
spdlog::error("river_control_v1 not advertised"); spdlog::error("river_control_v1 not advertised");
return;
} }
if (!seat_) { if (!seat_) {
spdlog::error("wl_seat not advertised"); spdlog::error("wl_seat not advertised");
return;
} }
seat_status_ = zriver_status_manager_v1_get_river_seat_status(status_manager_, seat_); seat_status_ = zriver_status_manager_v1_get_river_seat_status(status_manager_, seat_);
@@ -217,6 +219,7 @@ Tags::~Tags() {
} }
void Tags::handle_show() { void Tags::handle_show() {
if (!status_manager_) return;
struct wl_output* output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()); struct wl_output* output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj());
output_status_ = zriver_status_manager_v1_get_river_output_status(status_manager_, output); output_status_ = zriver_status_manager_v1_get_river_output_status(status_manager_, output);
zriver_output_status_v1_add_listener(output_status_, &output_status_listener_impl, this); zriver_output_status_v1_add_listener(output_status_, &output_status_listener_impl, this);

View File

@@ -82,6 +82,7 @@ Window::Window(const std::string& id, const waybar::Bar& bar, const Json::Value&
if (!seat_) { if (!seat_) {
spdlog::error("wl_seat not advertised"); spdlog::error("wl_seat not advertised");
return;
} }
label_.hide(); // hide the label until populated label_.hide(); // hide the label until populated

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