Compare commits

...

219 Commits

Author SHA1 Message Date
Alex
0fcda9afa5 chore: 0.13.0 2025-06-23 09:10:48 +02:00
Alexis Rouillard
4730fc4d77 Merge pull request #4219 from yuannan/nix_gpsd
Added IPC fix and Nix GPS fix
2025-06-22 13:22:19 +01:00
yuannan
2b9601b9a4 added IPC fix and Nix GPS fix 2025-06-22 13:15:18 +01:00
Alexis Rouillard
6a1d89e80d Merge pull request #4217 from Alexays/revert-2886-feat/upower_bat_bluetooth
Revert "Bluetooth module: fetch battery percentage from upower if not found from bluez"
2025-06-22 11:24:41 +01:00
Alexis Rouillard
35d5203b4e Revert "Bluetooth module: fetch battery percentage from upower if not found from bluez" 2025-06-22 12:24:22 +02:00
Alexis Rouillard
13ed499999 Merge pull request #2886 from levnikmyskin/feat/upower_bat_bluetooth
Bluetooth module: fetch battery percentage from upower if not found from bluez
2025-06-22 09:06:42 +01:00
Alexis Rouillard
0c1f1f2df9 Merge pull request #3544 from nktnet1/hyprland/windowcount
add Hyprland/windowcount module
2025-06-22 09:03:21 +01:00
Alex
ee91d18ad9 fix: lint 2025-06-22 10:01:36 +02:00
Alexis Rouillard
f27df33280 Merge pull request #3628 from adryzz/gps-module
Add GPS module
2025-06-22 08:59:46 +01:00
Alexis Rouillard
ea1ee2c027 Merge pull request #4068 from Rimsoo/patch-1
Fixed one layout with multiple variant bug hyprland/language Fixes #3676
2025-06-22 08:59:05 +01:00
Alexis Rouillard
54f7991325 Merge pull request #3762 from cc-nogueira/add_rewrite_to_wlr_tooltip
add rewrite to wlr tooltip
2025-06-22 08:58:33 +01:00
Alexis Rouillard
9246297d63 Merge pull request #4113 from Duncaen/niri-workspace-urgency
niri: add support for urgency indicators to workspaces
2025-06-22 08:57:57 +01:00
Alexis Rouillard
630f85dcb1 Merge branch 'master' into niri-workspace-urgency 2025-06-22 08:57:49 +01:00
Alexis Rouillard
77b42105a6 Merge pull request #4136 from Roc25/special-centered
hyprland workspaces: Add sorting Special Centered
2025-06-22 08:57:12 +01:00
Alex
df138e12c4 fix: compat freebsd 2025-06-22 09:55:57 +02:00
Alex
373fd77f7a chore: update cross-platform-actions/action 2025-06-22 09:54:46 +02:00
Alexis Rouillard
f5e6e5e9fc Merge pull request #3863 from yamader/wayfire
add module wayfire/window, wayfire/workspaces
2025-06-22 08:53:23 +01:00
Alexis Rouillard
a88e5795a4 Merge branch 'master' into wayfire 2025-06-22 08:53:02 +01:00
Alexis Rouillard
e9d0f2254d Merge pull request #1784 from ruanformigoni/issue-1681
exec runs after on-* events
2025-06-22 08:50:48 +01:00
Alexis Rouillard
71c6c6032b Merge pull request #4133 from Roc25/persistent-only
Add persistent-only setting for hyprland/workspaces
2025-06-22 08:50:33 +01:00
Alexis Rouillard
780e96bd78 Merge pull request #4131 from belcaik/master
Add support for mouse side buttons (back/forward) to control track navigation in mpris module
2025-06-22 08:45:41 +01:00
Alexis Rouillard
a563a3fceb Merge pull request #4127 from iostapyshyn/master
niri/workspaces: Add empty icon
2025-06-22 08:45:13 +01:00
Alex
286cff2e3d fix: lint 2025-06-22 09:44:35 +02:00
Alexis Rouillard
cbdbc492bb Merge pull request #4097 from RobertMueller2/sway-window-marks
feat: sway/window: provide {marks} format replacement
2025-06-22 08:43:27 +01:00
Alexis Rouillard
d6b6158ae9 Update custom.cpp 2025-06-22 09:42:14 +02:00
Alexis Rouillard
f314150736 Merge branch 'master' into issue-1681 2025-06-22 08:41:15 +01:00
Alexis Rouillard
6206cebd75 Merge pull request #3887 from torstehu/fix-typo
Fix typos in function, variable names and in documentation
2025-06-22 08:39:23 +01:00
Alexis Rouillard
0cae53747e Merge pull request #3881 from TripleTrable/AModules/fix-expand-fill-no-center
Fixes: Add stretching of modules and modules-center toggling
2025-06-22 08:38:08 +01:00
Alexis Rouillard
249b452829 Merge pull request #4040 from coreydoughty/hypr-submap-patch
hyprland/submap: allow pango markup.
2025-06-22 08:33:53 +01:00
Alexis Rouillard
bfedb7c446 Merge pull request #4155 from Yutsuten/fix/wide-char-calendar
Fix calendar extra padding if there are wide characters
2025-06-22 08:32:50 +01:00
Alexis Rouillard
9ca04ee2fd Merge pull request #4048 from LiterallyVoid/literallyvoid/fix-4047-deadlock
Fix signal safety deadlock
2025-06-22 08:32:36 +01:00
Alexis Rouillard
eea9561525 Merge pull request #4092 from RobertMueller2/issue_3981
Issue 3981: try and fix memory leak in privacy module
2025-06-22 08:31:06 +01:00
Alexis Rouillard
bef539e4de Update privacy_item.cpp 2025-06-22 09:30:46 +02:00
Alexis Rouillard
456c3add4a Merge pull request #4102 from edwin0cheng/style-sym
Use load_symbolic for gtk icon to support styling in tray icon
2025-06-22 08:29:42 +01:00
Alexis Rouillard
a48116799a Merge pull request #4088 from Alexays/update_flake_lock_action
flake.lock: Update
2025-06-22 08:27:57 +01:00
Alexis Rouillard
74a6726f4f Merge pull request #4096 from aereaux/idle_docs
Add idle_inhibitor style docs.
2025-06-22 08:27:37 +01:00
Alexis Rouillard
3d00c4bed4 Merge pull request #4160 from davidemanini/icon-label
AIconLabel.cpp: honour "rotation" property and add "swap-icon-label" propery
2025-06-22 08:27:07 +01:00
Alexis Rouillard
d8218a301d Merge pull request #4095 from Fengerros/patch-1
Update mediaplayer.py - Fix artist name display in mediaplayer.py
2025-06-22 08:26:37 +01:00
Alexis Rouillard
44a8910022 Merge pull request #4173 from S0nter/master
privacy: ignore some streams
2025-06-22 08:24:22 +01:00
Alexis Rouillard
341e39fbcf Merge pull request #4210 from stkth/feat/display-systemd-system-state
Feat/display systemd system state
2025-06-22 08:24:04 +01:00
Alexis Rouillard
7b9db134dd Merge pull request #4207 from markx86/fix/mpris
fix: MPRIS widget not hiding when no player is active
2025-06-22 08:23:09 +01:00
Alexis Rouillard
7d5905b38c Merge pull request #4204 from stkth/feat/add-swap-state-to-memory-module
modules: memory: Add swapState format argument
2025-06-22 08:22:36 +01:00
Alexis Rouillard
5a013bdba0 Merge pull request #4209 from notpeelz/fix-formatting
Fix CI clang-format
2025-06-22 08:21:38 +01:00
Alexis Rouillard
ae777c65a6 Merge pull request #4212 from notpeelz/fix-namespace-pollution
Fix namespace pollution
2025-06-22 08:21:24 +01:00
Alexis Rouillard
103487f672 Merge pull request #4213 from notpeelz/fix-missing-cxx20-chrono-literals
fix: don't use c++20 chrono literals
2025-06-22 08:21:01 +01:00
Alexis Rouillard
3c197811e2 Merge pull request #4214 from notpeelz/bump-freebsd-14-3
ci: bump FreeBSD to 14.3
2025-06-22 08:20:48 +01:00
peelz
0bdea1e46f ci: bump FreeBSD to 14.3 2025-06-22 01:02:53 -04:00
peelz
8daaad1e13 fix: don't use c++20 chrono literals
Unfortunately we can't use these yet because the freebsd build (clang)
still uses HowardHinnant/date, which doesn't provide literal suffixes.
2025-06-22 00:56:56 -04:00
peelz
25f432b0ce refactor: avoid Gio namespace pollution 2025-06-22 00:50:25 -04:00
peelz
e4dd2ecc5a refactor: avoid namespace pollution in util/date.hpp 2025-06-22 00:50:25 -04:00
Alexis Rouillard
a40c53bd5d Merge pull request #4211 from notpeelz/fix-ci-gentoo 2025-06-22 00:43:11 +01:00
peelz
84bd0d452e ci: run the docker workflow monthly instead of daily
Rebuilding the gentoo docker image daily is most definitely going to
bust through the free CI tier limits.
2025-06-21 16:05:00 -04:00
peelz
d4f61ad271 ci: allow manual triggering of docker workflow 2025-06-21 16:05:00 -04:00
peelz
c6fceb03c8 ci: relax gentoo gtkmm dependency requirement 2025-06-21 16:05:00 -04:00
Steffen Kothe
cfb47790ad modules: systemd_failed_units: Introduce systemd state variables
Systemd provides the status of a given user and system session as a
human readable string.

Retrieve this information via RequestSystemState and guard the
retrieve of failed units depending on this request.

The functionality is extended but does not change, which means that
failed units in any granularity are displayed as before.

Update documentation in the meantime.

Signed-off-by: Steffen Kothe <steffen.kothe@skothe.net>
2025-06-21 16:53:12 +00:00
Steffen Kothe
d5e3a9f894 modules: systemd_failed_units: Enforce visibility of event box on every update
Instead if guarding visibility in if condition, enforce visibility
regardless of the state of the current update.

Signed-off-by: Steffen Kothe <steffen.kothe@skothe.net>
2025-06-21 16:51:13 +00:00
Steffen Kothe
0731117679 modules: systemd_failed_units: Introduce RequestFailedUnits member
Split-out request of failed units from systemd into a separate member
function.

This increases the readability and extendability, but preserves the
current functionality (non-functional change).

Signed-off-by: Steffen Kothe <steffen.kothe@skothe.net>
2025-06-21 16:51:13 +00:00
Steffen Kothe
4bb06b86bc modules: systemd_failed_units: Use explicit g_variant_get_uint32
Determining of failed units can be done by usage of explicit uint32
function with direct return due to auto lambda expression.

Signed-off-by: Steffen Kothe <steffen.kothe@skothe.net>
2025-06-21 16:47:27 +00:00
Steffen Kothe
74255d0c7e modules: systemd_failed_units: Move DBUS proxy check into lambda function
Checking for the availability of a given proxy member can be done in the
lambda function as well.

Signed-off-by: Steffen Kothe <steffen.kothe@skothe.net>
2025-06-21 16:47:25 +00:00
Steffen Kothe
5c2cf4c65c modules: systemd_failed_units: Fail early if state did not change
Prefer early exit if last status matches the current status.

Signed-off-by: Steffen Kothe <steffen.kothe@skothe.net>
2025-06-21 16:47:07 +00:00
Steffen Kothe
dcbbe3bb97 modules: systemd_failed_units: Move nr_failed calculation to updateData
Signed-off-by: Steffen Kothe <steffen.kothe@skothe.net>
2025-06-21 16:46:06 +00:00
Steffen Kothe
37a6106d3e modules: systemd_failed_units: Introduce nr_failed as member
Keeping nr_failed as member allows to split-out calculation of overall
failed units into updateData.

Signed-off-by: Steffen Kothe <steffen.kothe@skothe.net>
2025-06-21 15:17:59 +00:00
peelz
b8a985d606 style: fix formatting 2025-06-21 10:54:16 -04:00
Sonter
8f35dc17b8 Merge branch 'Alexays:master' into master 2025-06-20 18:30:56 +00:00
Alexis Rouillard
dfbf1ff2a0 Merge pull request #4154 from VannTen/fix/service_file_description
Fix Description= in systemd service file
2025-06-20 16:15:30 +01:00
Alexis Rouillard
93d85a0cea Merge pull request #4190 from notpeelz/fix-network-rfkill-update
fix: network module not displaying rfkill state
2025-06-20 16:14:38 +01:00
Alexis Rouillard
3abbdb849d Merge pull request #4191 from notpeelz/fix-length-error-output-description
fix: length_error thrown in handleOutputDescription
2025-06-20 16:14:22 +01:00
Alexis Rouillard
7da053f1bd Merge pull request #4205 from stkth/feat/use-debian-slim-for-container
Dockerfiles: Use debian-slim for container
2025-06-20 16:13:43 +01:00
Sonter
2f6f9620d2 Merge branch 'Alexays:master' into master 2025-06-20 14:32:05 +00:00
markx86
c266befe0a fix: MPRIS widget not hiding when no player is active 2025-06-20 14:02:01 +02:00
Steffen Kothe
76d0b44214 Dockerfiles: Use debian-slim for container
Slim images of Debian tend to be smaller even during development. Hence
replace the full-fledged variant with the slim one.

Signed-off-by: Steffen Kothe <steffen.kothe@skothe.net>
2025-06-19 18:59:15 +00:00
Steffen Kothe
bdcab011ee modules: memory: Add swapState format argument
Add an argument to the memory module which displays the state of the
swap configuration of the local system.

Usage of swap does not necessarily indicate if swap is on or off.

Signed-off-by: Steffen Kothe <steffen.kothe@skothe.net>
2025-06-19 18:44:31 +00:00
Alexis Rouillard
f8f795ac38 Merge pull request #4202 from markx86/master
fix: `cldMonShift_` not getting initialized in `Clock()` constructor
2025-06-19 07:35:28 +01:00
markx86
af9d61fa8f fix: cldMonShift_ not getting initialized in Clock() constructor
Initialize `cldMonShift_` member in the `clock` module constructor. This
fixes a bug where the calendar tooltip would break after a reload, when
in month mode.
2025-06-19 00:35:54 +02:00
peelz
bd28bb959f fix: use spdlog in zxdg_output_v1_listener callbacks 2025-06-13 22:57:31 -04:00
peelz
f4496c9648 fix: length_error thrown in handleOutputDescription 2025-06-13 22:51:03 -04:00
peelz
07468357f4 fix: network module not displaying rfkill state 2025-06-13 21:40:18 -04:00
Alexis Rouillard
2c482a2917 Merge pull request #4189 from notpeelz/fix-man-network-frequency 2025-06-13 08:32:27 +02:00
peelz
5e14698b4e fix: network frequency is reported in GHz 2025-06-12 17:01:10 -04:00
Alexis Rouillard
250f58eed6 Merge pull request #4183 from Talbrelliles/custom-module-memory-fix
Memory Fix for continuous scripts in custom module
2025-06-11 13:18:23 +02:00
Taimase
3ebf2d96e5 fix continuousWorker in the custom module by capturing the buffer by reference. 2025-06-11 01:12:08 -06:00
Sonter
4d9403601a privacy: format with clang-format 2025-06-08 12:53:46 +03:00
Sonter
f73d26722c privacy: add example configuration 2025-06-08 12:39:10 +03:00
Gregor Kleen
6cfaf4ff63 privacy: document ignore options 2025-06-08 12:23:50 +03:00
Gregor Kleen
831602a913 privacy: default to ignoring all stream.monitor pw nodes 2025-06-08 12:23:50 +03:00
Gregor Kleen
703be13b00 privacy: introduce ignore option 2025-06-08 12:23:50 +03:00
Дмитрий
fca159ad01 Update man page
Co-authored-by: Tuur Vanhoutte <4633209+zjeffer@users.noreply.github.com>
2025-06-07 12:19:13 +03:00
gred
35c6e9c21c Update man page 2025-06-07 11:23:19 +03:00
gred
b36a283f83 Update man 2025-06-07 11:06:06 +03:00
gred
715503ec3e Rename vector to hiddenWorkspaces 2025-06-07 10:50:29 +03:00
Davide Manini
c26978eca8 Update documentation 2025-06-03 14:54:41 +03:00
Davide Manini
34484919d6 AIconLabel: honour rotation' option; add swap-icon-label' option 2025-06-03 14:54:41 +03:00
Mateus Eto
05cfd73804 Fix calendar extra padding if there are wide characters 2025-06-01 21:43:43 +09:00
github-actions[bot]
c0e7aad60e flake.lock: Update
Flake lock file updates:

• Updated input 'flake-compat':
    'github:edolstra/flake-compat/ff81ac966bb2cae68946d5ed5fc4994f96d0ffec?narHash=sha256-NeCCThCEP3eCl2l/%2B27kNNK7QrwZB1IJCrXfrbv5oqU%3D' (2024-12-04)
  → 'github:edolstra/flake-compat/9100a0f413b0c601e0533d1d94ffd501ce2e7885?narHash=sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX%2BfjA8Xf8PUmqCY%3D' (2025-05-12)
• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/8a2f738d9d1f1d986b5a4cd2fd2061a7127237d7?narHash=sha256-sPwcCYuiEopaafePqlG826tBhctuJsLx/mhKKM5Fmjo%3D' (2025-04-23)
  → 'github:NixOS/nixpkgs/96ec055edbe5ee227f28cdbc3f1ddf1df5965102?narHash=sha256-7doLyJBzCllvqX4gszYtmZUToxKvMUrg45EUWaUYmBg%3D' (2025-05-28)
2025-06-01 00:14:43 +00:00
Max Gautier
d53135f834 Fix Description= in systemd service file
Description= should be a noun phrase, and not a full sentence, according
to man 5 systemd.unit.

In particular, using a dot at the end result in messages like this in
journalctl when running as a user service (not the superfluous dot at
the end):

May 31 16:03:38 framework systemd[1180]: Started Highly customizable Wayland bar for Sway and Wlroots based compositors..
May 31 16:20:39 framework systemd[1180]: Stopping Highly customizable Wayland bar for Sway and Wlroots based compositors....
May 31 16:20:39 framework systemd[1180]: Stopped Highly customizable Wayland bar for Sway and Wlroots based compositors..
2025-05-31 23:07:26 +02:00
gred
15f54cd6ef Fix hidden buttons 2025-05-21 01:38:03 +03:00
gred
24a30b7ffd Add sorting with centered special workspaces 2025-05-21 01:01:23 +03:00
gred
4f55d7da90 Add persistent-only setting for hyprland/workspaces 2025-05-20 23:00:09 +03:00
belcaik
7613069a40 Merge pull request #1 from belcaik/feature/mpris-fordward-backward-handle
Add mouse navigation support for side buttons in Mpris
2025-05-19 23:25:31 -04:00
belcaik
d41a664779 refactor: use command pattern for button actions 2025-05-19 23:12:42 -04:00
belcaik
cdf3ca910d feat: Add support for side buttons mouse navigation in handleToggle
mapped buttons config on-click-backward and on-click-forward on mpris module to previous/next handle
GTK codes are 9 and 8
2025-05-19 22:52:01 -04:00
Illia Ostapyshyn
19d2430516 niri/workspaces: Add empty icon 2025-05-19 23:01:19 +02:00
Duncan Overbruck
47e0f42523 niri: add support for urgency indicators to workspaces 2025-05-14 20:33:11 +02:00
Khiet Tam Nguyen
6319569ed0 Merge branch 'Alexays:master' into hyprland/windowcount 2025-05-09 20:46:00 +10:00
Edwin Cheng
0340760e12 Use load_symbolic for gtk icon instead of load_icon. 2025-05-07 16:57:54 +08:00
Rene D. Obermueller
f4b68f41e6 feat: sway/window: provide {marks} format replacement 2025-05-04 07:50:28 +02:00
Aidan Epstein
7b5206128c Add idle_inhibitor style docs. 2025-05-03 10:41:07 -07:00
Fengerros
9bf8c8277a Update mediaplayer.py - Fix artist name display in mediaplayer.py
Fixed an issue where artist names like Earth, Wind & Fire were not displayed correctly. The change ensures that artist names containing commas or special characters are now shown properly.
2025-05-03 15:52:05 +02:00
Rene D. Obermueller
ff4ed82693 memory leak: 2nd attempt 2025-05-03 11:29:53 +02:00
Rene D. Obermueller
72184b2205 Issue 3981: try and fix memory leak 2025-05-02 10:01:09 +02:00
Rimsoo
afeea62214 fixes #3676 including #3224 2025-04-24 23:32:47 +02:00
Alexis Rouillard
0332d2ebf8 Merge pull request #4046 from ebeem/sway-workspace-current-not-shown-fix
Sway Workspace: Fix workspace button not showing for tabbed/nested layouts
2025-04-24 11:32:56 +02:00
Alexis Rouillard
7cbdce5064 Merge pull request #4075 from Alexays/update_flake_lock_action
flake.lock: Update
2025-04-24 11:30:08 +02:00
github-actions[bot]
ba8ea3d952 flake.lock: Update
Flake lock file updates:

• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/52faf482a3889b7619003c0daec593a1912fddc1?narHash=sha256-6hl6L/tRnwubHcA4pfUUtk542wn2Om%2BD4UnDhlDW9BE%3D' (2025-03-30)
  → 'github:NixOS/nixpkgs/8a2f738d9d1f1d986b5a4cd2fd2061a7127237d7?narHash=sha256-sPwcCYuiEopaafePqlG826tBhctuJsLx/mhKKM5Fmjo%3D' (2025-04-23)
2025-04-24 09:29:40 +00:00
Alexis Rouillard
8b48982bf6 Merge pull request #4032 from RobertMueller2/issue_3974
wireplumber: fix potential nullpointer deref
2025-04-24 11:28:56 +02:00
Alexis Rouillard
5382a09db0 Merge pull request #4025 from khaneliman/format
flake.nix: add treefmt; treewide: clang and nix format
2025-04-24 11:28:11 +02:00
Rimsoo
20642f47df Fixed variant bug 2025-04-22 11:41:45 +02:00
Lena
d0c6e91094 gps: add rfkill support 2025-04-18 22:56:47 +02:00
Lena
956e39d3d3 make gpsd receiver not poll 2025-04-18 22:56:47 +02:00
Lena
22ec8e0770 fix factory copy&paste typo 2025-04-18 22:56:47 +02:00
Lena
d331331b76 add manpage 2025-04-18 22:56:47 +02:00
Lena
dfa40d6a00 add all arguments 2025-04-18 22:56:47 +02:00
Lena
45ebf45343 Start GPS module 2025-04-18 22:56:36 +02:00
Austin Horstman
55f52c3457 treewide: clang and nix format 2025-04-15 14:56:28 -05:00
Austin Horstman
5c48373cfe flake.nix: add treefmt formatter
Easier to format everything properly.
2025-04-15 14:56:05 -05:00
Alexis Rouillard
c8484ebb1d Merge pull request #4058 from khaneliman/cava 2025-04-15 20:28:12 +02:00
Austin Horstman
bf4f3ab064 nix: cava bump 2025-04-15 12:06:41 -05:00
Alexis Rouillard
913e0665e7 Merge pull request #4056 from LukashonakV/cavaBump 2025-04-15 17:04:04 +02:00
Viktar Lukashonak
e85025f805 libCava bump: 0.10.4 2025-04-15 16:33:07 +03:00
literallyvoid
517eb7651e Run clang-format on main.cpp 2025-04-14 12:31:23 -07:00
Alexis Rouillard
67272cc47f Merge pull request #4042 from clemenscodes/wlr-taskbar-icon-title-fallback
wlr/taskbar: find icon by title as fallback
2025-04-14 20:53:06 +02:00
Alexis Rouillard
1dfde583df Merge pull request #4045 from khaneliman/pulse
audio_backend: fix crash
2025-04-14 20:52:45 +02:00
Alexis Rouillard
3e16c3f74e Merge pull request #4052 from AOSC-Tracking/aosc/v0.12.0 2025-04-14 08:34:57 +02:00
Kaiyang Wu
252e4f78bf fix: support libcava 0.10.4
Signed-off-by: Kaiyang Wu <self@origincode.me>
2025-04-13 22:23:40 -07:00
literallyvoid
dbd3ffd732 Convert reload to a local 2025-04-12 17:54:10 -07:00
literallyvoid
97591c825a Remove signalThread and move reaping to catchSignals 2025-04-12 17:52:37 -07:00
literallyvoid
b03ecb3d74 Move signal handling to main thread 2025-04-12 17:52:37 -07:00
Almarhoon Ibraheem
7e845f506e sway workspace: fix workspace button not shown in nested layouts 2025-04-12 18:31:34 +03:00
Austin Horstman
afb1ee5422 audio_backend: fix crash
Getting crashes when called before we have proper information.
2025-04-11 14:53:47 -05:00
Corey Doughty
682492f7a9 This commit fixes #4023 2025-04-10 07:05:45 -04:00
Corey Doughty
633bf9e00f Hyprland submap allow pango markup. 2025-04-10 06:56:00 -04:00
Clemens Horn
addf44d945 test 2025-04-07 20:51:35 +02:00
Clemens Horn
e92b0a86b5 wlr/taskbar: find icon by title as fallback 2025-04-07 20:33:18 +02:00
Rene D. Obermueller
9ca52a48c8 wireplumber: fix potential nullpointer deref 2025-04-06 09:46:06 +02:00
Alexis Rouillard
056295cbc6 Merge pull request #4026 from khaneliman/workflows
.github/workflows/clang-format: bump github action (fix clang-format check)
2025-04-04 10:20:16 +02:00
Alexis Rouillard
80669f7134 Merge pull request #4024 from khaneliman/hyprland-crash 2025-04-04 08:34:13 +02:00
Austin Horstman
5ff6b0ad0f .github/workflows: tweak job names
They didn't seem to correspond to the workflow, properly. Making
triggering them locally weird.
2025-04-04 00:04:51 -05:00
Austin Horstman
84162ec604 .github/workflows/clang-format: bump github action 2025-04-04 00:04:51 -05:00
Austin Horstman
91ef6e51ed hyprland/workspaces: range find lint cleanup 2025-04-03 23:31:18 -05:00
Austin Horstman
c5bc3bc59a hyprland/workspaces: fix crash 2025-04-03 23:31:14 -05:00
Alexis Rouillard
569445f8b3 Merge pull request #4017 from RobertMueller2/issue_4015
fix manpage for backlight/slider
2025-04-01 13:38:25 +02:00
Alexis Rouillard
eb254db867 Merge pull request #4018 from Alexays/update_flake_lock_action
flake.lock: Update
2025-04-01 13:38:15 +02:00
github-actions[bot]
c0b8c4d468 flake.lock: Update
Flake lock file updates:

• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/5135c59491985879812717f4c9fea69604e7f26f?narHash=sha256-Vr3Qi346M%2B8CjedtbyUevIGDZW8LcA1fTG0ugPY/Hic%3D' (2025-02-26)
  → 'github:NixOS/nixpkgs/52faf482a3889b7619003c0daec593a1912fddc1?narHash=sha256-6hl6L/tRnwubHcA4pfUUtk542wn2Om%2BD4UnDhlDW9BE%3D' (2025-03-30)
2025-04-01 00:12:37 +00:00
Rene D. Obermueller
9d2b137594 fix manpage for backlight/slider 2025-03-31 18:36:12 +02:00
Alexis Rouillard
4a8d527dd5 Merge pull request #3921 from bpandreotti/vertical-privacy
Add support for vertical orientation in privacy module
2025-03-28 09:52:48 +01:00
Alexis Rouillard
b98e717433 Merge pull request #3941 from ChaosInfinited/features/systray-custom-icons
Features/systray custom icons
2025-03-28 09:46:22 +01:00
Alexis Rouillard
fda7abb33d Merge pull request #3878 from matt-fff/mw/hyprland-events-workspaces-v2
Migrate Hyprland workspace events to v2
2025-03-28 09:45:44 +01:00
Alexis Rouillard
9681cfd2e8 Merge pull request #4009 from teatwig/master
fix incorrect type for `weeks-pos` in waybar-clock man page
2025-03-28 09:45:19 +01:00
tea
567ae16a68 fix incorrect type for weeks-pos in waybar-clock man page 2025-03-28 09:43:47 +01:00
Alexis Rouillard
2fdfd1028a Merge pull request #3959 from voiceroy/ip-address-display
Provide an option to show ipv4, ipv6 or both
2025-03-28 09:25:44 +01:00
Alexis Rouillard
8476a7dc84 Merge pull request #3970 from khaneliman/nativeinstallcheck
nix/default: disable version check
2025-03-28 09:24:43 +01:00
Alexis Rouillard
e74844b8d4 Merge pull request #3961 from Alexays/update_flake_lock_action
flake.lock: Update
2025-03-28 09:24:28 +01:00
Alexis Rouillard
a622dfd072 Merge pull request #3969 from LawnGnome/cffi-config-value-json
cffi: always return config values as JSON
2025-03-28 09:24:14 +01:00
Alexis Rouillard
775067f2da Merge pull request #3995 from spectrum70/wip/login-proxy
add login-proxy option
2025-03-28 09:23:32 +01:00
Alexis Rouillard
508d3cd3dd Merge pull request #3638 from RowanLeeder/ISSUE-3092
Resolves #3092 Add source support to wireplumber module
2025-03-28 09:23:01 +01:00
Alexis Rouillard
eb2df58f5a Merge pull request #4002 from lbartoletti/freebsd_temp
fix(FreeBSD): Use dev.cpu temperature sysctl
2025-03-24 22:19:40 +01:00
Loïc Bartoletti
4ba1947a50 fix(FreeBSD): Use dev.cpu temperature sysctl 2025-03-24 15:30:57 +01:00
Angelo Dureghello
6fd859c0c4 add login-proxy option
There are cases where systemd-logind is not used/running. Result is
that bcklight module will not run.

Add an option that, when set to false, allows backlight module to
work without systemd-logind.
2025-03-22 18:40:47 +01:00
Lars Niesen
8a15cbad5c Fixes: Add stretching of modules and modules-center toggling
Thanks to tmccombs this commit fixes some inconsitencies in #3730.
These inconsitencies were:
- Fixed the oversight of missing the implementation of expand_center for
  center_ and right_
- Removes a last minut printf debugging statment I missed.
2025-03-09 16:15:39 +01:00
Austin Horstman
f631d5eaf9 nix/default: disable version check
Downstream added version check, causes this flake to fail building.
2025-03-05 22:44:55 -06:00
Adam Harvey
906170400e cffi: always return config values as JSON
Previously, string JSON values were special cased to be provided as
bare strings, which means that CFFI modules have to either know what
type each value is expected to be, or use a heuristic such as trying to
decode and then treating the value as a string on failure.

Instead, we can always return JSON, and let the downstream consumer
handle deserialising the value into whatever type is expected.

The new behaviour is gated on a new ABI version 2: modules built against
version 1 will continue to get the old behaviour.
2025-03-05 17:11:26 -08:00
Harishankar G
5e4dac1c0a Newline as a seperator when displaying IPv4 and 6 at the same time 2025-03-05 15:29:21 +05:30
Matthew White
f7b4451564 fix(hyprland): support additional v2 events 2025-03-04 10:23:19 -07:00
Harishankar G
4a6c417ef5 Add format replacements
For cidr6, netmask6
2025-03-04 19:09:21 +05:30
github-actions[bot]
9f71de5227 flake.lock: Update
Flake lock file updates:

• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/9d3ae807ebd2981d593cddd0080856873139aa40?narHash=sha256-NGqpVVxNAHwIicXpgaVqJEJWeyqzoQJ9oc8lnK9%2BWC4%3D' (2025-01-29)
  → 'github:NixOS/nixpkgs/5135c59491985879812717f4c9fea69604e7f26f?narHash=sha256-Vr3Qi346M%2B8CjedtbyUevIGDZW8LcA1fTG0ugPY/Hic%3D' (2025-02-26)
2025-03-01 00:11:29 +00:00
Matt White
17cee0d876 feat(hyprland): support workspacev2 2025-02-26 18:56:11 -07:00
Matt White
0c6ca8321c feat(hyprland): support destroyworkspacev2 2025-02-26 18:56:11 -07:00
Matt White
26a344b131 feat(hyprland): support createworkspacev2 2025-02-26 18:56:11 -07:00
Harishankar G
8bd0285c88 Remove redundant if condition 2025-02-26 16:06:58 +05:30
Harishankar G
212c676251 Provide an option to show ipv4 or ipv6 or both of them 2025-02-26 15:59:33 +05:30
Kaosu
ddf5b3e07b add tray icons docs 2025-02-16 14:30:08 +01:00
Kaosu
d1998de47a add setCustomIcon and try to apply such when ID is known 2025-02-16 14:22:10 +01:00
Kaosu
78d5c3ef3a init custom icons from config per tray 2025-02-16 14:21:34 +01:00
Kaosu
937b62ea9a add SNI custom icon manager 2025-02-16 14:21:08 +01:00
Bruno Andreotti
a26ed50d0f Add support for vertical bars in privacy module 2025-02-07 14:39:07 -03:00
Torstein Husebø
6004316f1a Fix typos in function, variable names and in documentation 2025-01-16 13:25:19 +01:00
Khiet Tam Nguyen
36a1c89e30 Merge branch 'Alexays:master' into hyprland/windowcount 2025-01-13 10:33:05 +11:00
YamaD
d7e4a7d91f add module wayfire/window, wayfire/workspaces 2025-01-03 15:22:46 +09:00
Khiet Tam Nguyen
565602114e Merge branch 'Alexays:master' into hyprland/windowcount 2025-01-03 12:18:38 +11:00
Khiet Tam Nguyen
d29a17d703 Merge branch 'Alexays:master' into hyprland/windowcount 2024-12-22 17:12:54 +11:00
Khiet Tam Nguyen
334fc6e0e4 Merge branch 'Alexays:master' into hyprland/windowcount 2024-11-30 19:37:29 +11:00
Khiet Tam Nguyen
17d7cea4fd Merge branch 'Alexays:master' into hyprland/windowcount 2024-11-17 08:37:04 +11:00
Ricardo Nogueira
ddb3016440 add rewrite to wlr tooltip 2024-11-12 17:10:05 -03:00
Khiet Tam Nguyen
278588b1a3 Merge branch 'Alexays:master' into hyprland/windowcount 2024-10-06 00:34:20 +10:00
Rowan Leeder
2dfef1c213 Issue-3092 Add node type to wireplumber logs
- The module only fetches nodes for "node-type". This causes the 'onMixerChanged' log to spam whenever two or more
  wireplumber modules were registered on different nodes. To reduce this the unknown node warning will now only print
  if the node is not the focus of any current module.
2024-09-25 05:06:15 +10:00
Rowan Leeder
71a53eb79d Issue-3092 Add source support to wireplumber module
- Adds microphone support etc to the wireplumber module.

  The existing module hardcodes the selected node type to "Audio/Sink". This feature allows the user to override this
  via `"node-type": "Audio/Source"`.

- Unlike the pulseaudio module, this change does not try to see the module manage both input and output. The same effect
  can be achieved by running two instances of the wireplumber module.

  This approach:
  - Works around some of the complexity overhead that seem to have caused similar PRs to stall.
  - Using separate module instances also allows both the microphone and speaker levels to be controlled with a scroll
    wheel. This is something a unified module like pulseaudio struggles with.
  - Similarly, separate instances allows the source volume level to be exposed as the state. Ie- the linear-gradient
    css patterns can be applied to both input and output.
2024-09-25 05:03:24 +10:00
Khiet Tam Nguyen
636ef21f6e Merge branch 'Alexays:master' into hyprland/windowcount 2024-09-15 18:11:41 +10:00
Khiet Tam Nguyen
4cb2bf06b7 Merge branch 'Alexays:master' into hyprland/windowcount 2024-09-14 13:42:04 +10:00
Khiet Tam Nguyen
fd67c6e915 docs: rewording of separate-outputs in man page 2024-08-25 00:05:41 +10:00
Khiet Tam Nguyen
b82bcdb515 docs: updated documentation for windowcount.5.scd 2024-08-24 15:21:33 +10:00
Khiet Tam Nguyen
13bc497abd style: clang-format 2024-08-24 15:21:04 +10:00
Khiet Tam Nguyen
8254bd72b7 style: applied clang-format on windowcount.cpp 2024-08-24 13:36:00 +10:00
Khiet Tam Nguyen
9254ef6f2f docs: updated scd man pages 2024-08-24 13:29:46 +10:00
Khiet Tam Nguyen
a5e322ee66 fix: remove rewrite 2024-08-24 12:59:57 +10:00
Khiet Tam Nguyen
6aa8aa3b22 fix: remove focused_ 2024-08-24 12:57:10 +10:00
Khiet Tam Nguyen
f7e1d34251 feat: added empty and fullscreen style classes 2024-08-24 01:33:15 +10:00
Khiet Tam Nguyen
38ffb24c52 feat: format-fullscreen and format-windowed override added 2024-08-24 01:25:50 +10:00
Khiet Tam Nguyen
1b282e67a7 fix: remove unused attributes 2024-08-24 01:06:00 +10:00
Khiet Tam Nguyen
1806edcb06 fix: remove unused variable 2024-08-24 01:04:58 +10:00
Khiet Tam Nguyen
e40bc27257 fix: default separate-outputs to true 2024-08-24 00:40:41 +10:00
Khiet Tam Nguyen
58e4f89a82 fix: allow custom format 2024-08-24 00:40:22 +10:00
Khiet Tam Nguyen
d64c80e234 temp: working implementation 2024-08-24 00:14:30 +10:00
Khiet Tam Nguyen
5c859bf520 temp: changed window -> windowcount 2024-08-24 00:06:24 +10:00
Alessio Molinari
ea4b95fdd2 Fix: move init gdbusproxy after proxy_device_bat nullcheck
Co-authored-by: Alexis Rouillard <alexisr245@gmail.com>
2024-03-04 16:34:45 +01:00
Alessio Molinari
d831a45622 fix: restore signature 2024-02-04 18:01:26 +01:00
Alessio Molinari
2de8a83d84 feat: fetch battery percentage from upower if not found from bluez 2024-02-04 17:20:45 +01:00
Alexis Rouillard
ee0958973b Merge branch 'master' into issue-1681 2023-07-04 22:49:35 +02:00
Ruan E. Formigoni
15132aeec3 Fix for leftover pids 2022-11-15 01:19:51 -03:00
Ruan E. Formigoni
38af4a6f16 exec runs after on-* events 2022-11-10 02:36:54 -03:00
105 changed files with 3174 additions and 653 deletions

View File

@@ -7,11 +7,13 @@ concurrency:
cancel-in-progress: true
jobs:
build:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: DoozyX/clang-format-lint-action@v0.16.2
# TODO: bump to clang 19 release
# - uses: DoozyX/clang-format-lint-action@v0.18.2
- uses: DoozyX/clang-format-lint-action@558090054b3f39e3d6af24f0cd73b319535da809
name: clang-format
with:
source: "."

View File

@@ -7,7 +7,7 @@ concurrency:
cancel-in-progress: true
jobs:
build:
lint:
runs-on: ubuntu-latest
container:
image: alexays/waybar:debian

View File

@@ -1,14 +1,15 @@
name: Build and Push Docker Image
on:
workflow_dispatch:
schedule:
# run every night at midnight
- cron: '0 0 * * *'
# run monthly
- cron: '0 0 1 * *'
jobs:
build-and-push:
runs-on: ubuntu-latest
if: github.repository == 'Alexays/Waybar'
if: github.event_name != 'schedule' || github.repository == 'Alexays/Waybar'
strategy:
fail-fast: false # don't fail the other jobs if one of the images fails to build
matrix:

View File

@@ -7,21 +7,21 @@ concurrency:
cancel-in-progress: true
jobs:
clang:
build:
# Run actions in a FreeBSD VM on the ubuntu runner
# https://github.com/actions/runner/issues/385 - for FreeBSD runner support
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Test in FreeBSD VM
uses: cross-platform-actions/action@v0.25.0
uses: cross-platform-actions/action@v0.28.0
timeout-minutes: 180
env:
CPPFLAGS: '-isystem/usr/local/include'
LDFLAGS: '-L/usr/local/lib'
with:
operating_system: freebsd
version: "14.1"
version: "14.2"
environment_variables: CPPFLAGS LDFLAGS
sync_files: runner-to-vm
run: |

View File

@@ -1,6 +1,6 @@
# vim: ft=Dockerfile
FROM debian:sid
FROM debian:sid-slim
RUN apt update && \
apt install --no-install-recommends --no-install-suggests -y \

View File

@@ -6,6 +6,6 @@ RUN export FEATURES="-ipc-sandbox -network-sandbox -pid-sandbox -sandbox -usersa
emerge --sync && \
eselect news read --quiet new 1>/dev/null 2>&1 && \
emerge --verbose --update --deep --with-bdeps=y --backtrack=30 --newuse @world && \
USE="wayland gtk3 gtk -doc X pulseaudio minimal" emerge dev-vcs/git dev-libs/wayland dev-libs/wayland-protocols =dev-cpp/gtkmm-3.24.6 x11-libs/libxkbcommon \
USE="wayland gtk3 gtk -doc X pulseaudio minimal" emerge dev-vcs/git dev-libs/wayland dev-libs/wayland-protocols dev-cpp/gtkmm:3.0 x11-libs/libxkbcommon \
x11-libs/gtk+:3 dev-libs/libdbusmenu dev-libs/libnl sys-power/upower media-libs/libpulse dev-libs/libevdev media-libs/libmpdclient \
media-sound/sndio gui-libs/gtk-layer-shell app-text/scdoc media-sound/playerctl dev-libs/iniparser sci-libs/fftw

View File

@@ -1,10 +1,9 @@
(import
(
let lock = builtins.fromJSON (builtins.readFile ./flake.lock); in
fetchTarball {
url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz";
sha256 = lock.nodes.flake-compat.locked.narHash;
}
)
{ src = ./.; }
).defaultNix
(import (
let
lock = builtins.fromJSON (builtins.readFile ./flake.lock);
in
fetchTarball {
url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz";
sha256 = lock.nodes.flake-compat.locked.narHash;
}
) { src = ./.; }).defaultNix

12
flake.lock generated
View File

@@ -3,11 +3,11 @@
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1733328505,
"narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=",
"lastModified": 1747046372,
"narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec",
"rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885",
"type": "github"
},
"original": {
@@ -18,11 +18,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1738142207,
"narHash": "sha256-NGqpVVxNAHwIicXpgaVqJEJWeyqzoQJ9oc8lnK9+WC4=",
"lastModified": 1748460289,
"narHash": "sha256-7doLyJBzCllvqX4gszYtmZUToxKvMUrg45EUWaUYmBg=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "9d3ae807ebd2981d593cddd0080856873139aa40",
"rev": "96ec055edbe5ee227f28cdbc3f1ddf1df5965102",
"type": "github"
},
"original": {

129
flake.nix
View File

@@ -9,47 +9,96 @@
};
};
outputs = { self, nixpkgs, ... }:
outputs =
{ self, nixpkgs, ... }:
let
inherit (nixpkgs) lib;
genSystems = func: lib.genAttrs [
"x86_64-linux"
"aarch64-linux"
]
(system: func (import nixpkgs {
inherit system;
overlays = with self.overlays; [
waybar
];
}));
genSystems =
func:
lib.genAttrs
[
"x86_64-linux"
"aarch64-linux"
]
(
system:
func (
import nixpkgs {
inherit system;
overlays = with self.overlays; [
waybar
];
}
)
);
mkDate = longDate: (lib.concatStringsSep "-" [
(builtins.substring 0 4 longDate)
(builtins.substring 4 2 longDate)
(builtins.substring 6 2 longDate)
]);
mkDate =
longDate:
(lib.concatStringsSep "-" [
(builtins.substring 0 4 longDate)
(builtins.substring 4 2 longDate)
(builtins.substring 6 2 longDate)
]);
in
{
devShells = genSystems
(pkgs:
{
default =
pkgs.mkShell
{
name = "waybar-shell";
devShells = genSystems (pkgs: {
default = pkgs.mkShell {
name = "waybar-shell";
# inherit attributes from upstream nixpkgs derivation
inherit (pkgs.waybar) buildInputs depsBuildBuild depsBuildBuildPropagated depsBuildTarget
depsBuildTargetPropagated depsHostHost depsHostHostPropagated depsTargetTarget
depsTargetTargetPropagated propagatedBuildInputs propagatedNativeBuildInputs strictDeps;
# inherit attributes from upstream nixpkgs derivation
inherit (pkgs.waybar)
buildInputs
depsBuildBuild
depsBuildBuildPropagated
depsBuildTarget
depsBuildTargetPropagated
depsHostHost
depsHostHostPropagated
depsTargetTarget
depsTargetTargetPropagated
propagatedBuildInputs
propagatedNativeBuildInputs
strictDeps
;
# overrides for local development
nativeBuildInputs = pkgs.waybar.nativeBuildInputs ++ (with pkgs; [
clang-tools
gdb
]);
# overrides for local development
nativeBuildInputs =
pkgs.waybar.nativeBuildInputs
++ (with pkgs; [
nixfmt-rfc-style
clang-tools
gdb
]);
};
});
formatter = genSystems (
pkgs:
pkgs.treefmt.withConfig {
settings = [
{
formatter = {
clang-format = {
options = [ "-i" ];
command = lib.getExe' pkgs.clang-tools "clang-format";
excludes = [ ];
includes = [
"*.c"
"*.cpp"
"*.h"
"*.hpp"
];
};
});
nixfmt = {
command = lib.getExe pkgs.nixfmt-rfc-style;
includes = [ "*.nix" ];
};
};
tree-root-file = ".git/index";
}
];
}
);
overlays = {
default = self.overlays.waybar;
@@ -58,11 +107,15 @@
waybar = prev.waybar;
# take the first "version: '...'" from meson.build
version =
(builtins.head (builtins.split "'"
(builtins.elemAt
(builtins.split " version: '" (builtins.readFile ./meson.build))
2)))
+ "+date=" + (mkDate (self.lastModifiedDate or "19700101")) + "_" + (self.shortRev or "dirty");
(builtins.head (
builtins.split "'" (
builtins.elemAt (builtins.split " version: '" (builtins.readFile ./meson.build)) 2
)
))
+ "+date="
+ (mkDate (self.lastModifiedDate or "19700101"))
+ "_"
+ (self.shortRev or "dirty");
};
};
};

View File

@@ -36,6 +36,7 @@ class AModule : public IModule {
SCROLL_DIR getScrollDir(GdkEventScroll *e);
bool tooltipEnabled() const;
std::vector<int> pid_children_;
const std::string name_;
const Json::Value &config_;
Gtk::EventBox event_box_;
@@ -54,7 +55,6 @@ class AModule : public IModule {
const bool isTooltip;
const bool isExpand;
bool hasUserEvents_;
std::vector<int> pid_;
gdouble distance_scrolled_y_;
gdouble distance_scrolled_x_;
std::map<std::string, std::string> eventActionMap_;

View File

@@ -5,7 +5,16 @@
namespace cava {
extern "C" {
// Need sdl_glsl output feature to be enabled on libcava
#ifndef SDL_GLSL
#define SDL_GLSL
#endif
#include <cava/common.h>
#ifdef SDL_GLSL
#undef SDL_GLSL
#endif
}
} // namespace cava

View File

@@ -38,39 +38,39 @@ class Clock final : public ALabel {
5 - tooltip-format
*/
std::map<int, std::string const> fmtMap_;
uint cldMonCols_{3}; // calendar count month columns
int cldWnLen_{3}; // calendar week number length
const int cldMonColLen_{20}; // calendar month column length
WS cldWPos_{WS::HIDDEN}; // calendar week side to print
months cldCurrShift_{0}; // calendar months shift
int cldShift_{1}; // calendar months shift factor
year_month_day cldYearShift_; // calendar Year mode. Cached ymd
std::string cldYearCached_; // calendar Year mode. Cached calendar
year_month cldMonShift_; // calendar Month mode. Cached ym
std::string cldMonCached_; // calendar Month mode. Cached calendar
day cldBaseDay_{0}; // calendar Cached day. Is used when today is changing(midnight)
std::string cldText_{""}; // calendar text to print
uint cldMonCols_{3}; // calendar count month columns
int cldWnLen_{3}; // calendar week number length
const int cldMonColLen_{20}; // calendar month column length
WS cldWPos_{WS::HIDDEN}; // calendar week side to print
date::months cldCurrShift_{0}; // calendar months shift
int cldShift_{1}; // calendar months shift factor
date::year_month_day cldYearShift_; // calendar Year mode. Cached ymd
std::string cldYearCached_; // calendar Year mode. Cached calendar
date::year_month cldMonShift_; // calendar Month mode. Cached ym
std::string cldMonCached_; // calendar Month mode. Cached calendar
date::day cldBaseDay_{0}; // calendar Cached day. Is used when today is changing(midnight)
std::string cldText_{""}; // calendar text to print
CldMode cldMode_{CldMode::MONTH};
auto get_calendar(const year_month_day& today, const year_month_day& ymd, const time_zone* tz)
-> const std::string;
auto get_calendar(const date::year_month_day& today, const date::year_month_day& ymd,
const date::time_zone* tz) -> const std::string;
// get local time zone
auto local_zone() -> const time_zone*;
auto local_zone() -> const date::time_zone*;
// time zoned time in tooltip
const bool tzInTooltip_; // if need to print time zones text
std::vector<const time_zone*> tzList_; // time zones list
int tzCurrIdx_; // current time zone index for tzList_
std::string tzText_{""}; // time zones text to print
const bool tzInTooltip_; // if need to print time zones text
std::vector<const date::time_zone*> tzList_; // time zones list
int tzCurrIdx_; // current time zone index for tzList_
std::string tzText_{""}; // time zones text to print
util::SleeperThread thread_;
// ordinal date in tooltip
const bool ordInTooltip_;
std::string ordText_{""};
auto get_ordinal_date(const year_month_day& today) -> std::string;
auto get_ordinal_date(const date::year_month_day& today) -> std::string;
auto getTZtext(sys_seconds now) -> std::string;
auto first_day_of_week() -> weekday;
auto getTZtext(date::sys_seconds now) -> std::string;
auto first_day_of_week() -> date::weekday;
// Module actions
void cldModeSwitch();
void cldShift_up();

40
include/modules/gps.hpp Normal file
View File

@@ -0,0 +1,40 @@
#pragma once
#include <fmt/format.h>
#include <sys/statvfs.h>
#ifdef WANT_RFKILL
#include "util/rfkill.hpp"
#endif
#include <gps.h>
#include "ALabel.hpp"
#include "util/sleeper_thread.hpp"
namespace waybar::modules {
class Gps : public ALabel {
public:
Gps(const std::string&, const Json::Value&);
virtual ~Gps();
auto update() -> void override;
private:
#ifdef WANT_RFKILL
util::Rfkill rfkill_;
#endif
const std::string getFixModeName() const;
const std::string getFixModeString() const;
const std::string getFixStatusString() const;
util::SleeperThread thread_, gps_thread_;
gps_data_t gps_data_;
std::string state_;
bool hideDisconnected = true;
bool hideNoFix = false;
};
} // namespace waybar::modules

View File

@@ -46,4 +46,5 @@ class IPC {
};
inline bool modulesReady = false;
inline std::unique_ptr<IPC> gIPC;
}; // namespace waybar::modules::hyprland

View File

@@ -0,0 +1,41 @@
#pragma once
#include <fmt/format.h>
#include <string>
#include "AAppIconLabel.hpp"
#include "bar.hpp"
#include "modules/hyprland/backend.hpp"
#include "util/json.hpp"
namespace waybar::modules::hyprland {
class WindowCount : public waybar::AAppIconLabel, public EventHandler {
public:
WindowCount(const std::string&, const waybar::Bar&, const Json::Value&);
~WindowCount() override;
auto update() -> void override;
private:
struct Workspace {
int id;
int windows;
bool hasfullscreen;
static auto parse(const Json::Value& value) -> Workspace;
};
static auto getActiveWorkspace(const std::string&) -> Workspace;
static auto getActiveWorkspace() -> Workspace;
void onEvent(const std::string& ev) override;
void queryActiveWorkspace();
void setClass(const std::string&, bool enable);
bool separateOutputs_;
std::mutex mutex_;
const Bar& bar_;
Workspace workspace_;
};
} // namespace waybar::modules::hyprland

View File

@@ -42,7 +42,7 @@ class WindowCreationPayload {
std::string getWorkspaceName() const { return m_workspaceName; }
WindowAddress getAddress() const { return m_windowAddress; }
void moveToWorksace(std::string& new_workspace_name);
void moveToWorkspace(std::string& new_workspace_name);
private:
void clearAddr();

View File

@@ -55,11 +55,11 @@ class Workspace {
void setName(std::string const& value) { m_name = value; };
void setOutput(std::string const& value) { m_output = value; };
bool containsWindow(WindowAddress const& addr) const { return m_windowMap.contains(addr); }
void insertWindow(WindowCreationPayload create_window_paylod);
void insertWindow(WindowCreationPayload create_window_payload);
std::string removeWindow(WindowAddress const& addr);
void initializeWindowMap(const Json::Value& clients_data);
bool onWindowOpened(WindowCreationPayload const& create_window_paylod);
bool onWindowOpened(WindowCreationPayload const& create_window_payload);
std::optional<std::string> closeWindow(WindowAddress const& addr);
void update(const std::string& format, const std::string& icon);

View File

@@ -7,6 +7,7 @@
#include <cstdint>
#include <map>
#include <memory>
#include <optional>
#include <regex>
#include <string>
#include <vector>
@@ -36,6 +37,7 @@ class Workspaces : public AModule, public EventHandler {
auto showSpecial() const -> bool { return m_showSpecial; }
auto activeOnly() const -> bool { return m_activeOnly; }
auto specialVisibleOnly() const -> bool { return m_specialVisibleOnly; }
auto persistentOnly() const -> bool { return m_persistentOnly; }
auto moveToMonitor() const -> bool { return m_moveToMonitor; }
auto getBarOutput() const -> std::string { return m_bar.output->name; }
@@ -49,13 +51,14 @@ class Workspaces : public AModule, public EventHandler {
private:
void onEvent(const std::string& e) override;
void updateWindowCount();
void sortSpecialCentered();
void sortWorkspaces();
void createWorkspace(Json::Value const& workspace_data,
Json::Value const& clients_data = Json::Value::nullRef);
static Json::Value createMonitorWorkspaceData(std::string const& name,
std::string const& monitor);
void removeWorkspace(std::string const& name);
void removeWorkspace(std::string const& workspaceString);
void setUrgentWorkspace(std::string const& windowaddress);
// Config
@@ -74,10 +77,11 @@ class Workspaces : public AModule, public EventHandler {
void onWorkspaceActivated(std::string const& payload);
void onSpecialWorkspaceActivated(std::string const& payload);
void onWorkspaceDestroyed(std::string const& payload);
void onWorkspaceCreated(std::string const& workspaceName,
void onWorkspaceCreated(std::string const& payload,
Json::Value const& clientsData = Json::Value::nullRef);
void onWorkspaceMoved(std::string const& payload);
void onWorkspaceRenamed(std::string const& payload);
static std::optional<int> parseWorkspaceId(std::string const& workspaceIdStr);
// monitor events
void onMonitorFocused(std::string const& payload);
@@ -93,11 +97,18 @@ class Workspaces : public AModule, public EventHandler {
int windowRewritePriorityFunction(std::string const& window_rule);
// event payload management
template <typename... Args>
static std::string makePayload(Args const&... args);
static std::pair<std::string, std::string> splitDoublePayload(std::string const& payload);
static std::tuple<std::string, std::string, std::string> splitTriplePayload(
std::string const& payload);
// Update methods
void doUpdate();
void removeWorkspacesToRemove();
void createWorkspacesToCreate();
static std::vector<std::string> getVisibleWorkspaces();
static std::vector<int> getVisibleWorkspaces();
void updateWorkspaceStates();
bool updateWindowsToCreate();
@@ -113,20 +124,22 @@ class Workspaces : public AModule, public EventHandler {
bool m_showSpecial = false;
bool m_activeOnly = false;
bool m_specialVisibleOnly = false;
bool m_persistentOnly = false;
bool m_moveToMonitor = false;
Json::Value m_persistentWorkspaceConfig;
// Map for windows stored in workspaces not present in the current bar.
// This happens when the user has multiple monitors (hence, multiple bars)
// and doesn't share windows accross bars (a.k.a `all-outputs` = false)
// and doesn't share windows across bars (a.k.a `all-outputs` = false)
std::map<WindowAddress, std::string> m_orphanWindowMap;
enum class SortMethod { ID, NAME, NUMBER, DEFAULT };
enum class SortMethod { ID, NAME, NUMBER, SPECIAL_CENTERED, DEFAULT };
util::EnumParser<SortMethod> m_enumParser;
SortMethod m_sortBy = SortMethod::DEFAULT;
std::map<std::string, SortMethod> m_sortMap = {{"ID", SortMethod::ID},
{"NAME", SortMethod::NAME},
{"NUMBER", SortMethod::NUMBER},
{"SPECIAL-CENTERED", SortMethod::SPECIAL_CENTERED},
{"DEFAULT", SortMethod::DEFAULT}};
std::string m_format;
@@ -138,7 +151,7 @@ class Workspaces : public AModule, public EventHandler {
bool m_withIcon;
uint64_t m_monitorId;
std::string m_activeWorkspaceName;
int m_activeWorkspaceId;
std::string m_activeSpecialWorkspaceName;
std::vector<std::unique_ptr<Workspace>> m_workspaces;
std::vector<std::pair<Json::Value, Json::Value>> m_workspacesToCreate;

View File

@@ -16,6 +16,8 @@
#include "util/rfkill.hpp"
#endif
enum ip_addr_pref : uint8_t { IPV4, IPV6, IPV4_6 };
namespace waybar::modules {
class Network : public ALabel {
@@ -50,6 +52,7 @@ class Network : public ALabel {
std::optional<std::pair<unsigned long long, unsigned long long>> readBandwidthUsage();
int ifid_;
ip_addr_pref addr_pref_;
struct sockaddr_nl nladdr_ = {0};
struct nl_sock* sock_ = nullptr;
struct nl_sock* ev_sock_ = nullptr;
@@ -73,9 +76,12 @@ class Network : public ALabel {
bool carrier_;
std::string ifname_;
std::string ipaddr_;
std::string ipaddr6_;
std::string gwaddr_;
std::string netmask_;
std::string netmask6_;
int cidr_;
int cidr6_;
int32_t signal_strength_dbm_;
uint8_t signal_strength_;
std::string signal_strength_app_;

View File

@@ -13,7 +13,7 @@ namespace waybar::modules::privacy {
class Privacy : public AModule {
public:
Privacy(const std::string &, const Json::Value &, const std::string &pos);
Privacy(const std::string &, const Json::Value &, Gtk::Orientation, const std::string &pos);
auto update() -> void override;
void onPrivacyNodesChanged();
@@ -31,6 +31,8 @@ class Privacy : public AModule {
uint iconSpacing = 4;
uint iconSize = 20;
uint transition_duration = 250;
std::set<std::pair<PrivacyNodeType, std::string>> ignore;
bool ignore_monitor = true;
std::shared_ptr<util::PipewireBackend::PipewireBackend> backend = nullptr;
};

View File

@@ -17,8 +17,8 @@ namespace waybar::modules::privacy {
class PrivacyItem : public Gtk::Revealer {
public:
PrivacyItem(const Json::Value &config_, enum PrivacyNodeType privacy_type_,
std::list<PrivacyNodeInfo *> *nodes, const std::string &pos, const uint icon_size,
const uint transition_duration);
std::list<PrivacyNodeInfo *> *nodes, Gtk::Orientation orientation,
const std::string &pos, const uint icon_size, const uint transition_duration);
enum PrivacyNodeType privacy_type;

View File

@@ -0,0 +1,43 @@
#pragma once
#include <json/json.h>
#include <spdlog/spdlog.h>
#include <string>
#include <unordered_map>
class IconManager {
public:
static IconManager& instance() {
static IconManager instance;
return instance;
}
void setIconsConfig(const Json::Value& icons_config) {
if (icons_config.isObject()) {
for (const auto& key : icons_config.getMemberNames()) {
std::string app_name = key;
const Json::Value& icon_value = icons_config[key];
if (icon_value.isString()) {
std::string icon_path = icon_value.asString();
icons_map_[app_name] = icon_path;
}
}
} else {
spdlog::warn("Invalid icon config format.");
}
}
std::string getIconForApp(const std::string& app_name) const {
auto it = icons_map_.find(app_name);
if (it != icons_map_.end()) {
return it->second;
}
return "";
}
private:
IconManager() = default;
std::unordered_map<std::string, std::string> icons_map_;
};

View File

@@ -62,6 +62,7 @@ class Item : public sigc::trackable {
void proxyReady(Glib::RefPtr<Gio::AsyncResult>& result);
void setProperty(const Glib::ustring& name, Glib::VariantBase& value);
void setStatus(const Glib::ustring& value);
void setCustomIcon(const std::string& id);
void getUpdatedProperties();
void processUpdatedProperties(Glib::RefPtr<Gio::AsyncResult>& result);
void onSignal(const Glib::ustring& sender_name, const Glib::ustring& signal_name,

View File

@@ -21,7 +21,7 @@ class Language : public ALabel, public sigc::trackable {
auto update() -> void override;
private:
enum class DispayedShortFlag { None = 0, ShortName = 1, ShortDescription = 1 << 1 };
enum class DisplayedShortFlag { None = 0, ShortName = 1, ShortDescription = 1 << 1 };
struct Layout {
std::string full_name;
@@ -58,7 +58,7 @@ class Language : public ALabel, public sigc::trackable {
std::map<std::string, Layout> layouts_map_;
bool hide_single_;
bool is_variant_displayed;
std::byte displayed_short_flag = static_cast<std::byte>(DispayedShortFlag::None);
std::byte displayed_short_flag = static_cast<std::byte>(DisplayedShortFlag::None);
util::JsonParser parser_;
std::mutex mutex_;

View File

@@ -19,10 +19,11 @@ class Window : public AAppIconLabel, public sigc::trackable {
auto update() -> void override;
private:
void setClass(std::string classname, bool enable);
void setClass(const std::string& classname, bool enable);
void onEvent(const struct Ipc::ipc_response&);
void onCmd(const struct Ipc::ipc_response&);
std::tuple<std::size_t, int, int, std::string, std::string, std::string, std::string, std::string>
std::tuple<std::size_t, int, int, std::string, std::string, std::string, std::string, std::string,
std::string>
getFocusedNode(const Json::Value& nodes, std::string& output);
void getTree();
@@ -35,6 +36,7 @@ class Window : public AAppIconLabel, public sigc::trackable {
std::string old_app_id_;
std::size_t app_nb_;
std::string shell_;
std::string marks_;
int floating_count_;
util::JsonParser parser_;
std::mutex mutex_;

View File

@@ -48,7 +48,7 @@ class Workspaces : public AModule, public sigc::trackable {
std::vector<std::string> high_priority_named_;
std::vector<std::string> workspaces_order_;
Gtk::Box box_;
std::string m_formatWindowSeperator;
std::string m_formatWindowSeparator;
util::RegexCollection m_windowRewriteRules;
util::JsonParser parser_;
std::unordered_map<std::string, Gtk::Button> buttons_;

View File

@@ -19,12 +19,15 @@ class SystemdFailedUnits : public ALabel {
std::string format_ok;
bool update_pending;
uint32_t nr_failed_system, nr_failed_user;
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_proxy, user_proxy;
void notify_cb(const Glib::ustring &sender_name, const Glib::ustring &signal_name,
const Glib::VariantContainerBase &arguments);
void RequestFailedUnits();
void RequestSystemState();
void updateData();
};

View File

@@ -0,0 +1,122 @@
#pragma once
#include <json/json.h>
#include <unistd.h>
#include <functional>
#include <list>
#include <memory>
#include <mutex>
#include <optional>
#include <string>
#include <unordered_map>
#include <utility>
namespace waybar::modules::wayfire {
using EventHandler = std::function<void(const std::string& event)>;
struct State {
/*
┌───────────┐ ┌───────────┐
│ output #1 │ │ output #2 │
└─────┬─────┘ └─────┬─────┘
└─┐ └─────┐─ ─ ─ ─ ─ ─ ─ ─ ┐
┌───────┴───────┐ ┌───────┴──────┐ ┌───────┴───────┐
│ wset #1 │ │ wset #2 │ │ wset #3 │
│┌────────────┐ │ │┌────────────┐│ │┌────────────┐ │
││ workspaces │ │ ││ workspaces ││ ││ workspaces │ │
│└─┬──────────┘ │ │└────────────┘│ │└─┬──────────┘ │
│ │ ┌─────────┐│ └──────────────┘ │ │ ┌─────────┐│
│ ├─┤ view #1 ││ │ └─┤ view #3 ││
│ │ └─────────┘│ │ └─────────┘│
│ │ ┌─────────┐│ └───────────────┘
│ └─┤ view #2 ││
│ └─────────┘│
└───────────────┘
*/
struct Output {
size_t id;
size_t w, h;
size_t wset_idx;
};
struct Workspace {
size_t num_views;
size_t num_sticky_views;
};
struct Wset {
std::optional<std::reference_wrapper<Output>> output;
std::vector<Workspace> wss;
size_t ws_w, ws_h, ws_x, ws_y;
size_t focused_view_id;
auto ws_idx() const { return ws_w * ws_y + ws_x; }
auto count_ws(const Json::Value& pos) -> Workspace&;
auto locate_ws(const Json::Value& geo) -> Workspace&;
auto locate_ws(const Json::Value& geo) const -> const Workspace&;
};
std::unordered_map<std::string, Output> outputs;
std::unordered_map<size_t, Wset> wsets;
std::unordered_map<size_t, Json::Value> views;
std::string focused_output_name;
size_t maybe_empty_focus_wset_idx = {};
size_t vswitch_sticky_view_id = {};
bool new_output_detected = {};
bool vswitching = {};
auto update_view(const Json::Value& view) -> void;
};
struct Sock {
int fd;
Sock(int fd) : fd{fd} {}
~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;
Json::CharReaderBuilder reader_builder;
Json::StreamWriterBuilder writer_builder;
std::list<std::pair<std::string, std::reference_wrapper<const EventHandler>>> handlers;
std::mutex handlers_mutex;
State state;
std::mutex state_mutex;
IPC() { start(); }
static auto connect() -> Sock;
auto receive(Sock& sock) -> Json::Value;
auto start() -> void;
auto root_event_handler(const std::string& event, const Json::Value& data) -> void;
auto update_state_handler(const std::string& event, const Json::Value& data) -> void;
public:
static auto get_instance() -> std::shared_ptr<IPC>;
auto send(const std::string& method, Json::Value&& data) -> Json::Value;
auto register_handler(const std::string& event, const EventHandler& handler) -> void;
auto unregister_handler(EventHandler& handler) -> void;
auto lock_state() -> std::lock_guard<std::mutex> { return std::lock_guard{state_mutex}; }
auto& get_outputs() const { return state.outputs; }
auto& get_wsets() const { return state.wsets; }
auto& get_views() const { return state.views; }
auto& get_focused_output_name() const { return state.focused_output_name; }
};
} // namespace waybar::modules::wayfire

View File

@@ -0,0 +1,24 @@
#pragma once
#include "AAppIconLabel.hpp"
#include "bar.hpp"
#include "modules/wayfire/backend.hpp"
namespace waybar::modules::wayfire {
class Window : public AAppIconLabel {
std::shared_ptr<IPC> ipc;
EventHandler handler;
const Bar& bar_;
std::string old_app_id_;
public:
Window(const std::string& id, const Bar& bar, const Json::Value& config);
~Window() override;
auto update() -> void override;
auto update_icon_label() -> void;
};
} // namespace waybar::modules::wayfire

View File

@@ -0,0 +1,32 @@
#pragma once
#include <gtkmm/button.h>
#include <json/json.h>
#include <memory>
#include <vector>
#include "AModule.hpp"
#include "bar.hpp"
#include "modules/wayfire/backend.hpp"
namespace waybar::modules::wayfire {
class Workspaces : public AModule {
std::shared_ptr<IPC> ipc;
EventHandler handler;
const Bar& bar_;
Gtk::Box box_;
std::vector<Gtk::Button> buttons_;
auto handleScroll(GdkEventScroll* e) -> bool override;
auto update() -> void override;
auto update_box() -> void;
public:
Workspaces(const std::string& id, const Bar& bar, const Json::Value& config);
~Workspaces() override;
};
} // namespace waybar::modules::wayfire

View File

@@ -18,7 +18,7 @@ class Wireplumber : public ALabel {
private:
void asyncLoadRequiredApiModules();
void prepare();
void prepare(waybar::modules::Wireplumber* self);
void activatePlugins();
static void updateVolume(waybar::modules::Wireplumber* self, uint32_t id);
static void updateNodeName(waybar::modules::Wireplumber* self, uint32_t id);
@@ -32,6 +32,8 @@ class Wireplumber : public ALabel {
bool handleScroll(GdkEventScroll* e) override;
static std::list<waybar::modules::Wireplumber*> modules;
WpCore* wp_core_;
GPtrArray* apis_;
WpObjectManager* om_;
@@ -44,6 +46,7 @@ class Wireplumber : public ALabel {
double min_step_;
uint32_t node_id_{0};
std::string node_name_;
gchar* type_;
};
} // namespace waybar::modules

View File

@@ -15,7 +15,7 @@
namespace date {
#if HAVE_CHRONO_TIMEZONES
using namespace std::chrono;
using namespace std;
using std::format;
#else
using system_clock = std::chrono::system_clock;
@@ -73,5 +73,3 @@ struct fmt::formatter<date::zoned_time<Duration, TimeZonePtr>> {
}
};
#endif
using namespace date;

View File

@@ -10,5 +10,7 @@ class DefaultGtkIconThemeWrapper {
public:
static bool has_icon(const std::string&);
static Glib::RefPtr<Gdk::Pixbuf> load_icon(const char*, int, Gtk::IconLookupFlags);
static Glib::RefPtr<Gdk::Pixbuf> load_icon(
const char*, int, Gtk::IconLookupFlags,
Glib::RefPtr<Gtk::StyleContext> style = Glib::RefPtr<Gtk::StyleContext>());
};

View File

@@ -25,6 +25,7 @@ class PrivacyNodeInfo {
std::string media_name;
std::string node_name;
std::string application_name;
bool is_monitor = false;
std::string pipewire_access_portal_app_id;
std::string application_icon_name;

View File

@@ -6,14 +6,12 @@
namespace waybar {
using namespace Gio;
enum class Appearance {
UNKNOWN = 0,
DARK = 1,
LIGHT = 2,
};
class Portal : private DBus::Proxy {
class Portal : private Gio::DBus::Proxy {
public:
Portal();
void refreshAppearance();

View File

@@ -4,6 +4,6 @@
namespace waybar::util {
// Get a signal emited with value true when entering sleep, and false when exiting
// Get a signal emitted with value true when entering sleep, and false when exiting
SafeSignal<bool>& prepare_for_sleep();
} // namespace waybar::util

View File

@@ -40,7 +40,7 @@ The brightness can be controlled by dragging the slider across the bar or clicki
```
"modules-right": [
"backlight-slider",
"backlight/slider",
],
"backlight/slider": {
"min": 0,

View File

@@ -117,7 +117,7 @@ View all valid format options in *strftime(3)* or have a look https://en.cpprefe
:[ 3
:[ Relevant for *mode=year*. Count of months per row
|[ *weeks-pos*
:[ integer
:[ string
:[
:[ The position where week numbers should be displayed. Disabled when is empty.
Possible values: left|right

111
man/waybar-gps.5.scd Normal file
View File

@@ -0,0 +1,111 @@
waybar-gps(5) "waybar-gps" "User Manual"
# NAME
waybar - gps module
# DESCRIPTION
*gps* module for gpsd.
# FILES
$XDG_CONFIG_HOME/waybar/config ++
Per user configuration file
# ADDITIONAL FILES
libgps lives in:
. /usr/lib/libgps.so or /usr/lib64/libgps.so
. /usr/lib/pkgconfig/libgps.pc or /usr/lib64/pkgconfig/libgps.pc
. /usr/include/gps
# CONFIGURATION
*format*: ++
typeof: string ++
default: {glyph} ++
The text format.
*tooltip*: ++
typeof: bool ++
default: true ++
Option to disable tooltip on hover.
*tooltip-format*: ++
typeof: string ++
default: Games running: {glyph} ++
The text format of the tooltip.
*interval*: ++
typeof: integer ++
default: 5 ++
The interval in which the GPS information gets polled (e.g. current speed).
Significant updates (e.g. the current fix mode) are updated immediately.
*hide-disconnected*: ++
typeof: bool ++
default: true ++
Defines if the module should be hidden if there is no GPS receiver.
*hide-no-fix*: ++
typeof: bool ++
default: false ++
Defines if the module should be hidden if there is no GPS fix.
# FORMAT REPLACEMENTS
*{mode}*: Fix mode
*{status}*: Technology used for GPS fix. Not all GPS receivers report this.
*{latitude}*: Latitude, decimal degrees. Can be NaN.
*{latitude_error}*: Latitude uncertainty, meters. Can be NaN.
*{longitude}*: Longitude, decimal degrees. Can be NaN.
*{longitude_error}*: Longitude uncertainty, meters. Can be NaN.
*{altitude_hae}*: Altitude, height above ellipsoid, meters. Can be NaN.
*{altitude_msl}*: Longitude, MSL, meters. Can be NaN.
*{altitude_error}*: Altitude uncertainty, meters. Can be NaN.
*{speed}*: Speed over ground, meters/sec. Can be NaN.
*{speed_error}*: Speed uncertainty, meters/sec. Can be NaN.
*{climb}*: Vertical speed, meters/sec. Can be NaN.
*{climb_error}*: Vertical speed uncertainty, meters/sec. Can be NaN.
*{satellites_visible}*: Number of satellites visible from the GPS receiver.
*{satellites_used}*: Number of satellites used for the GPS fix.
# EXAMPLES
```
"gps": {
"format": "{mode}",
"format-disabled": "", // an empty format will hide the module
"format-no-fix": "No fix",
"format-fix-3d": "{status}",
"tooltip-format": "{mode}",
"tooltip-format-no-fix": "{satellites_visible} satellites visible",
"tooltip-format-fix-2d": "{satellites_used}/{satellites_visible} satellites used",
"tooltip-format-fix-3d": "Altitude: {altitude_hae}m",
"hide-disconnected": false
}
```
# STYLE
- *#gps*
- *#gps.disabled* Applied when GPS is disabled.
- *#gps.fix-none* Applied when GPS is present, but there is no fix.
- *#gps.fix-2d* Applied when there is a 2D fix.
- *#gps.fix-3d* Applied when there is a 3D fix.

View File

@@ -0,0 +1,46 @@
waybar-hyprland-windowcount(5)
# NAME
waybar - hyprland window count module
# DESCRIPTION
The *windowcount* module displays the number of windows in the current Hyprland workspace.
# CONFIGURATION
Addressed by *hyprland/windowcount*
*format*: ++
typeof: string ++
default: {} ++
The format for how information should be displayed. On {} the current workspace window count is displayed.
*format-empty*: ++
typeof: string ++
Override the format when the workspace contains no windows window
*format-windowed*: ++
typeof: string ++
Override the format when the workspace contains no fullscreen windows
*format-fullscreen*: ++
typeof: string ++
Override the format when the workspace contains a fullscreen window
*separate-outputs*: ++
typeof: bool ++
default: true ++
Show the active workspace window count of the monitor the bar belongs to, instead of the focused workspace.
# STYLE
- *#windowcount*
The following classes are applied to the entire Waybar rather than just the
windowcount widget:
- *window#waybar.empty* When no windows are in the workspace
- *window#waybar.fullscreen* When there is a fullscreen window in the workspace;
useful with Hyprland's *fullscreen, 1* mode

View File

@@ -48,6 +48,11 @@ Addressed by *hyprland/workspaces*
default: false ++
If this and show-special are to true, special workspaces will be shown only if visible.
*persistent-only*: ++
typeof: bool ++
default: false ++
If set to true, only persistent workspaces will be shown on bar.
*all-outputs*: ++
typeof: bool ++
default: false ++
@@ -76,6 +81,7 @@ Addressed by *hyprland/workspaces*
If set to number, workspaces will sort by number.
If set to name, workspaces will sort by name.
If set to id, workspaces will sort by id.
If set to special-centered, workspaces will sort by default with special workspaces in the center.
If none of those, workspaces will sort with default behavior.
*expand*: ++

View File

@@ -125,3 +125,9 @@ screensaver, also known as "presentation mode".
"timeout": 30.5
}
```
# STYLE
- *#idle_inhibitor*
- *#idle_inhibitor.activated*
- *#idle_inhibitor.deactivated*

View File

@@ -120,6 +120,8 @@ Addressed by *memory*
*{swapAvail}*: Amount of available swap in GiB.
*{swapState}*: Signals if swap is activated or not
# EXAMPLES
```

View File

@@ -7,7 +7,7 @@ waybar - menu property
# OVERVIEW
Some modules support a 'menu', which allows to have a popup menu whan a defined
Some modules support a 'menu', which allows to have a popup menu when a defined
click is done over the module.
# PROPERTIES

View File

@@ -24,7 +24,7 @@ Addressed by *network*
*family*: ++
typeof: string ++
default: *ipv4* ++
The address family that is used for the format replacement {ipaddr} and to determine if a network connection is present.
The address family that is used for the format replacement {ipaddr} and to determine if a network connection is present. Set it to ipv4_6 to display both.
*format*: ++
typeof: string ++
@@ -155,9 +155,13 @@ Addressed by *network*
*{gwaddr}*: The default gateway for the interface
*{netmask}*: The subnetmask corresponding to the IP.
*{netmask}*: The subnetmask corresponding to the IP(V4).
*{cidr}*: The subnetmask corresponding to the IP in CIDR notation.
*{netmask6}*: The subnetmask corresponding to the IP(V6).
*{cidr}*: The subnetmask corresponding to the IP(V4) in CIDR notation.
*{cidr6}*: The subnetmask corresponding to the IP(V6) in CIDR notation.
*{essid}*: Name (SSID) of the wireless network.
@@ -167,7 +171,7 @@ Addressed by *network*
*{signaldBm}*: Signal strength of the wireless network in dBm.
*{frequency}*: Frequency of the wireless network in MHz.
*{frequency}*: Frequency of the wireless network in GHz.
*{bandwidthUpBits}*: Instant up speed in bits/seconds.

View File

@@ -70,6 +70,8 @@ Additional to workspace name matching, the following *format-icons* can be set.
- *default*: Will be shown, when no string matches are found.
- *focused*: Will be shown, when workspace is focused.
- *active*: Will be shown, when workspace is active on its output.
- *urgent*: Will be shown, when workspace has urgent windows.
- *empty*: Will be shown, when workspace is empty.
# EXAMPLES
@@ -95,6 +97,7 @@ Additional to workspace name matching, the following *format-icons* can be set.
- *#workspaces button*
- *#workspaces button.focused*: The single focused workspace.
- *#workspaces button.active*: The workspace is active (visible) on its output.
- *#workspaces button.urgent*: The workspace has one or more urgent windows.
- *#workspaces button.empty*: The workspace is empty.
- *#workspaces button.current_output*: The workspace is from the same output as
the bar that it is displayed on.

View File

@@ -37,6 +37,17 @@ the screen or playing audio.
default: false ++
Enables this module to consume all left over space dynamically.
*ignore-monitor* ++
typeof: bool ++
default: true ++
Ignore streams with *stream.monitor* property.
*ignore* ++
typeof: array of objects ++
default: [] ++
Additional streams to be ignored. See *IGNORE CONFIGURATION* for++
more information.
# MODULES CONFIGURATION
*type*: ++
@@ -54,6 +65,14 @@ the screen or playing audio.
default: 24 ++
The size of each icon in the tooltip.
# IGNORE CONFIGURATION
*type*: ++
typeof: string
*name*: ++
typeof: string
# EXAMPLES
```
@@ -77,6 +96,17 @@ the screen or playing audio.
"tooltip": true,
"tooltip-icon-size": 24
}
],
"ignore-monitor": true,
"ignore": [
{
"type": "audio-in",
"name": "cava"
},
{
"type": "screenshare",
"name": "obs"
}
]
},
```

View File

@@ -89,6 +89,11 @@ Addressed by *sway/window*
default: false ++
If the workspace itself is focused and the workspace contains nodes or floating_nodes, show the workspace name. If not set, text remains empty but styles according to nodes in the workspace are still applied.
*show-hidden-marks*: ++
typeof: bool ++
default: false ++
For the *{marks}* format replacement, include hidden marks that start with an underscore.
*rewrite*: ++
typeof: object ++
Rules to rewrite the module format output. See *rewrite rules*.
@@ -117,6 +122,8 @@ Addressed by *sway/window*
*{shell}*: The shell of the focused window. It's 'xwayland' when the window is
running through xwayland, otherwise, it's 'xdg-shell'.
*{marks}*: Marks of the window.
# REWRITE RULES
*rewrite* is an object where keys are regular expressions and values are

View File

@@ -62,6 +62,12 @@ Addressed by *systemd-failed-units*
*{nr_failed}*: Number of total failed units.
*{systemd_state}:* State of the systemd system session
*{user_state}:* State of the systemd user session
*{overall_state}:* Overall state of the systemd and user session. ("Ok" or "Degraded")
# EXAMPLES
```

View File

@@ -47,7 +47,11 @@ Addressed by *tray*
```
"tray": {
"icon-size": 21,
"spacing": 10
"spacing": 10,
"icons": {
"blueman": "bluetooth",
"TelegramDesktop": "$HOME/.local/share/icons/hicolor/16x16/apps/telegram.png"
}
}
```

View File

@@ -0,0 +1,82 @@
waybar-wayfire-window(5)
# NAME
waybar - wayfire window module
# DESCRIPTION
The *window* module displays the title of the currently focused window in wayfire.
# CONFIGURATION
Addressed by *wayfire/window*
*format*: ++
typeof: string ++
default: {title} ++
The format, how information should be displayed. On {} the current window title is displayed.
*rewrite*: ++
typeof: object ++
Rules to rewrite window title. See *rewrite rules*.
*icon*: ++
typeof: bool ++
default: false ++
Option to hide the application icon.
*icon-size*: ++
typeof: integer ++
default: 24 ++
Option to change the size of the application icon.
*expand*: ++
typeof: bool ++
default: false ++
Enables this module to consume all left over space dynamically.
# FORMAT REPLACEMENTS
See the output of "wayfire msg windows" for examples
*{title}*: The current title of the focused window.
*{app_id}*: The current app ID of the focused window.
# REWRITE RULES
*rewrite* is an object where keys are regular expressions and values are
rewrite rules if the expression matches. Rules may contain references to
captures of the expression.
Regular expression and replacement follow ECMA-script rules.
If no expression matches, the title is left unchanged.
Invalid expressions (e.g., mismatched parentheses) are skipped.
# EXAMPLES
```
"wayfire/window": {
"format": "{}",
"rewrite": {
"(.*) - Mozilla Firefox": "🌎 $1",
"(.*) - zsh": "> [$1]"
}
}
```
# STYLE
- *#window*
- *window#waybar.empty #window* When no windows are on the workspace
The following classes are applied to the entire Waybar rather than just the
window widget:
- *window#waybar.empty* When no windows are in the workspace
- *window#waybar.solo* When only one window is on the workspace
- *window#waybar.<app-id>* Where *app-id* is the app ID of the only window on
the workspace

View File

@@ -0,0 +1,86 @@
waybar-wayfire-workspaces(5)
# NAME
waybar - wayfire workspaces module
# DESCRIPTION
The *workspaces* module displays the currently used workspaces in wayfire.
# CONFIGURATION
Addressed by *wayfire/workspaces*
*format*: ++
typeof: string ++
default: {value} ++
The format, how information should be displayed.
*format-icons*: ++
typeof: array ++
Based on the workspace name, index and state, the corresponding icon gets selected. See *icons*.
*disable-click*: ++
typeof: bool ++
default: false ++
If set to false, you can click to change workspace. If set to true this behaviour is disabled.
*disable-markup*: ++
typeof: bool ++
default: false ++
If set to true, button label will escape pango markup.
*current-only*: ++
typeof: bool ++
default: false ++
If set to true, only the active or focused workspace will be shown.
*on-update*: ++
typeof: string ++
Command to execute when the module is updated.
*expand*: ++
typeof: bool ++
default: false ++
Enables this module to consume all left over space dynamically.
# FORMAT REPLACEMENTS
*{icon}*: Icon, as defined in *format-icons*.
*{index}*: Index of the workspace on its output.
*{output}*: Output where the workspace is located.
# ICONS
Additional to workspace name matching, the following *format-icons* can be set.
- *default*: Will be shown, when no string matches are found.
- *focused*: Will be shown, when workspace is focused.
# EXAMPLES
```
"wayfire/workspaces": {
"format": "{icon}",
"format-icons": {
"1": "",
"2": "",
"3": "",
"4": "",
"5": "",
"focused": "",
"default": ""
}
}
```
# Style
- *#workspaces button*
- *#workspaces button.focused*: The single focused workspace.
- *#workspaces button.empty*: The workspace is empty.
- *#workspaces button.current_output*: The workspace is from the same output as
the bar that it is displayed on.

View File

@@ -19,6 +19,11 @@ The *wireplumber* module displays the current volume reported by WirePlumber.
typeof: string ++
This format is used when the sound is muted.
*node-type*: ++
typeof: string ++
default: *Audio/Sink* ++
The WirePlumber node type to attach to. Use *Audio/Source* to manage microphones etc.
*tooltip*: ++
typeof: bool ++
default: *true* ++
@@ -108,6 +113,8 @@ The *wireplumber* module displays the current volume reported by WirePlumber.
# EXAMPLES
## Basic:
```
"wireplumber": {
"format": "{volume}%",
@@ -116,6 +123,26 @@ The *wireplumber* module displays the current volume reported by WirePlumber.
}
```
## Separate Sink and Source Widgets
```
"wireplumber#sink": {
"format": "{volume}% {icon}",
"format-muted": "",
"format-icons": ["", "", ""],
"on-click": "helvum",
"on-click-right": "wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle",
"scroll-step": 5
},
"wireplumber#source": {
"node-type": "Audio/Source",
"format": "{volume}% ",
"format-muted": "",
"on-click-right": "wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle",
"scroll-step": 5
}
```
# STYLE
- *#wireplumber*

View File

@@ -86,7 +86,7 @@ The visual display elements for waybar use a CSS stylesheet, see *waybar-styles(
*no-center* ++
typeof: bool ++
default: false ++
Option to disable the center modules fully usefull together with expand-\*.
Option to disable the center modules fully useful together with expand-\*.
*spacing* ++
typeof: integer ++
@@ -272,6 +272,17 @@ When positioning Waybar on the left or right side of the screen, sometimes it's
Valid options for the "rotate" property are: 0, 90, 180, and 270.
## Swapping icon and label
If a module displays both a label and an icon, it might be desirable to swap them (for instance, for panels on the left or right of the screen, or for user adopting a right-to-left script). This can be achieved with the "swap-icon-label" property, taking a boolean. Example:
```
{
"sway/window": {
"swap-icon-label": true
}
}
```
## Grouping modules
Module groups allow stacking modules in any direction. By default, when the bar is positioned on the top or bottom of the screen, modules in a group are stacked vertically. Likewise, when positioned on the left or right, modules in a group are stacked horizontally. This can be changed with the "orientation" property.

View File

@@ -1,6 +1,6 @@
project(
'waybar', 'cpp', 'c',
version: '0.12.0',
version: '0.13.0',
license: 'MIT',
meson_version: '>= 0.59.0',
default_options : [
@@ -93,6 +93,7 @@ libmpdclient = dependency('libmpdclient', required: get_option('mpd'))
xkbregistry = dependency('xkbregistry')
libjack = dependency('jack', required: get_option('jack'))
libwireplumber = dependency('wireplumber-0.5', required: get_option('wireplumber'))
libgps = dependency('libgps', required: get_option('gps'))
libsndio = compiler.find_library('sndio', required: get_option('sndio'))
if libsndio.found()
@@ -306,6 +307,7 @@ if true
'src/modules/hyprland/language.cpp',
'src/modules/hyprland/submap.cpp',
'src/modules/hyprland/window.cpp',
'src/modules/hyprland/windowcount.cpp',
'src/modules/hyprland/workspace.cpp',
'src/modules/hyprland/workspaces.cpp',
'src/modules/hyprland/windowcreationpayload.cpp',
@@ -333,6 +335,19 @@ if get_option('niri')
)
endif
if true
add_project_arguments('-DHAVE_WAYFIRE', language: 'cpp')
src_files += files(
'src/modules/wayfire/backend.cpp',
'src/modules/wayfire/window.cpp',
'src/modules/wayfire/workspaces.cpp',
)
endif
if get_option('login-proxy')
add_project_arguments('-DHAVE_LOGIN_PROXY', language: 'cpp')
endif
if libnl.found() and libnlgen.found()
add_project_arguments('-DHAVE_LIBNL', language: 'cpp')
src_files += files('src/modules/network.cpp')
@@ -482,7 +497,7 @@ if get_option('experimental')
endif
cava = dependency('cava',
version : '>=0.10.3',
version : '>=0.10.4',
required: get_option('cava'),
fallback : ['cava', 'cava_dep'],
not_found_message: 'cava is not found. Building waybar without cava')
@@ -493,6 +508,12 @@ if cava.found()
man_files += files('man/waybar-cava.5.scd')
endif
if libgps.found()
add_project_arguments('-DHAVE_LIBGPS', language: 'cpp')
src_files += files('src/modules/gps.cpp')
man_files += files('man/waybar-gps.5.scd')
endif
subdir('protocol')
app_resources = []
@@ -531,7 +552,8 @@ executable(
libsndio,
tz_dep,
xkbregistry,
cava
cava,
libgps
],
include_directories: inc_dirs,
install: true,

View File

@@ -20,3 +20,5 @@ option('jack', type: 'feature', value: 'auto', description: 'Enable support for
option('wireplumber', type: 'feature', value: 'auto', description: 'Enable support for WirePlumber')
option('cava', type: 'feature', value: 'auto', description: 'Enable support for Cava')
option('niri', type: 'boolean', description: 'Enable support for niri')
option('login-proxy', type: 'boolean', description: 'Enable interfacing with dbus login interface')
option('gps', type: 'feature', value: 'auto', description: 'Enable support for gps')

View File

@@ -1,42 +1,47 @@
{ lib
, pkgs
, waybar
, version
{
lib,
pkgs,
waybar,
version,
}:
let
libcava = rec {
version = "0.10.3";
version = "0.10.4";
src = pkgs.fetchFromGitHub {
owner = "LukashonakV";
repo = "cava";
rev = version;
hash = "sha256-ZDFbI69ECsUTjbhlw2kHRufZbQMu+FQSMmncCJ5pagg=";
tag = version;
hash = "sha256-9eTDqM+O1tA/3bEfd1apm8LbEcR9CVgELTIspSVPMKM=";
};
};
in
(waybar.overrideAttrs (
oldAttrs: {
inherit version;
(waybar.overrideAttrs (oldAttrs: {
inherit version;
src = lib.cleanSourceWith {
filter = name: type: type != "regular" || !lib.hasSuffix ".nix" name;
src = lib.cleanSource ../.;
};
src = lib.cleanSourceWith {
filter = name: type: type != "regular" || !lib.hasSuffix ".nix" name;
src = lib.cleanSource ../.;
};
mesonFlags = lib.remove "-Dgtk-layer-shell=enabled" oldAttrs.mesonFlags;
mesonFlags = lib.remove "-Dgtk-layer-shell=enabled" oldAttrs.mesonFlags;
# downstream patch should not affect upstream
patches = [];
# downstream patch should not affect upstream
patches = [ ];
# nixpkgs checks version, no need when building locally
nativeInstallCheckInputs = [ ];
buildInputs = (builtins.filter (p: p.pname != "wireplumber") oldAttrs.buildInputs) ++ [
pkgs.wireplumber
];
buildInputs = (builtins.filter (p:
p.pname != "wireplumber" &&
p.pname != "gps"
) oldAttrs.buildInputs) ++ [
pkgs.wireplumber
pkgs.gpsd
];
postUnpack = ''
pushd "$sourceRoot"
cp -R --no-preserve=mode,ownership ${libcava.src} subprojects/cava-${libcava.version}
patchShebangs .
popd
'';
}
))
postUnpack = ''
pushd "$sourceRoot"
cp -R --no-preserve=mode,ownership ${libcava.src} subprojects/cava-${libcava.version}
patchShebangs .
popd
'';
}))

View File

@@ -104,7 +104,11 @@
},
"tray": {
// "icon-size": 21,
"spacing": 10
"spacing": 10,
// "icons": {
// "blueman": "bluetooth",
// "TelegramDesktop": "$HOME/.local/share/icons/hicolor/16x16/apps/telegram.png"
// }
},
"clock": {
// "timezone": "America/New_York",

View File

@@ -17,7 +17,7 @@ void onclicked(GtkButton* button) {
}
// You must
const size_t wbcffi_version = 1;
const size_t wbcffi_version = 2;
void* wbcffi_init(const wbcffi_init_info* init_info, const wbcffi_config_entry* config_entries,
size_t config_entries_len) {
@@ -67,4 +67,4 @@ void wbcffi_refresh(void* instance, int signal) {
void wbcffi_doaction(void* instance, const char* name) {
printf("cffi_example inst=%p: doAction(%s)\n", instance, name);
}
}

View File

@@ -7,7 +7,7 @@
extern "C" {
#endif
/// Waybar ABI version. 1 is the latest version
/// Waybar ABI version. 2 is the latest version
extern const size_t wbcffi_version;
/// Private Waybar CFFI module
@@ -35,7 +35,13 @@ typedef struct {
typedef struct {
/// Entry key
const char* key;
/// Entry value as string. JSON object and arrays are serialized.
/// Entry value
///
/// In ABI version 1, this may be either a bare string if the value is a
/// string, or the JSON representation of any other JSON object as a string.
///
/// From ABI version 2 onwards, this is always the JSON representation of the
/// value as a string.
const char* value;
} wbcffi_config_entry;

View File

@@ -112,6 +112,7 @@ class PlayerManager:
logger.debug(f"Metadata changed for player {player.props.player_name}")
player_name = player.props.player_name
artist = player.get_artist()
artist = artist.replace("&", "&amp;")
title = player.get_title()
title = title.replace("&", "&amp;")

View File

@@ -1,5 +1,5 @@
[Unit]
Description=Highly customizable Wayland bar for Sway and Wlroots based compositors.
Description=Highly customizable Wayland bar for Sway and Wlroots based compositors
Documentation=https://github.com/Alexays/Waybar/wiki/
PartOf=graphical-session.target
After=graphical-session.target

View File

@@ -1,6 +1,7 @@
#include "AIconLabel.hpp"
#include <gdkmm/pixbuf.h>
#include <spdlog/spdlog.h>
namespace waybar {
@@ -17,14 +18,36 @@ AIconLabel::AIconLabel(const Json::Value &config, const std::string &name, const
box_.get_style_context()->add_class(id);
}
box_.set_orientation(Gtk::Orientation::ORIENTATION_HORIZONTAL);
int rot = 0;
if (config_["rotate"].isUInt()) {
rot = config["rotate"].asUInt() % 360;
if ((rot % 90) != 00) rot = 0;
rot /= 90;
}
if ((rot % 2) == 0)
box_.set_orientation(Gtk::Orientation::ORIENTATION_HORIZONTAL);
else
box_.set_orientation(Gtk::Orientation::ORIENTATION_VERTICAL);
box_.set_name(name);
int spacing = config_["icon-spacing"].isInt() ? config_["icon-spacing"].asInt() : 8;
box_.set_spacing(spacing);
box_.add(image_);
box_.add(label_);
bool swap_icon_label = false;
if (not config_["swap-icon-label"].isBool())
spdlog::warn("'swap-icon-label' must be a bool.");
else
swap_icon_label = config_["swap-icon-label"].asBool();
if ((rot == 0 || rot == 3) ^ swap_icon_label) {
box_.add(image_);
box_.add(label_);
} else {
box_.add(label_);
box_.add(image_);
}
event_box_.add(box_);
}

View File

@@ -83,7 +83,7 @@ AModule::AModule(const Json::Value& config, const std::string& name, const std::
}
AModule::~AModule() {
for (const auto& pid : pid_) {
for (const auto& pid : pid_children_) {
if (pid != -1) {
killpg(pid, SIGTERM);
}
@@ -93,15 +93,15 @@ AModule::~AModule() {
auto AModule::update() -> void {
// Run user-provided update handler if configured
if (config_["on-update"].isString()) {
pid_.push_back(util::command::forkExec(config_["on-update"].asString()));
pid_children_.push_back(util::command::forkExec(config_["on-update"].asString()));
}
}
// Get mapping between event name and module action name
// Then call overrided doAction in order to call appropriate module action
// Then call overridden doAction in order to call appropriate module action
auto AModule::doAction(const std::string& name) -> void {
if (!name.empty()) {
const std::map<std::string, std::string>::const_iterator& recA{eventActionMap_.find(name)};
// Call overrided action if derrived class has implemented it
// Call overridden action if derived class has implemented it
if (recA != eventActionMap_.cend() && name != recA->second) this->doAction(recA->second);
}
}
@@ -182,7 +182,7 @@ bool AModule::handleUserEvent(GdkEventButton* const& e) {
format.clear();
}
if (!format.empty()) {
pid_.push_back(util::command::forkExec(format));
pid_children_.push_back(util::command::forkExec(format));
}
dp.emit();
return true;
@@ -267,7 +267,7 @@ bool AModule::handleScroll(GdkEventScroll* e) {
this->AModule::doAction(eventName);
// Second call user scripts
if (config_[eventName].isString())
pid_.push_back(util::command::forkExec(config_[eventName].asString()));
pid_children_.push_back(util::command::forkExec(config_[eventName].asString()));
dp.emit();
return true;

View File

@@ -545,7 +545,6 @@ auto waybar::Bar::setupWidgets() -> void {
if (config["fixed-center"].isBool() ? config["fixed-center"].asBool() : true) {
box_.set_center_widget(center_);
} else {
spdlog::error("No fixed center_");
box_.pack_start(center_, true, expand_center);
}
}
@@ -569,13 +568,13 @@ auto waybar::Bar::setupWidgets() -> void {
if (!no_center) {
for (auto const& module : modules_center_) {
center_.pack_start(*module, false, false);
center_.pack_start(*module, module->expandEnabled(), module->expandEnabled());
}
}
std::reverse(modules_right_.begin(), modules_right_.end());
for (auto const& module : modules_right_) {
right_.pack_end(*module, false, false);
right_.pack_end(*module, module->expandEnabled(), module->expandEnabled());
}
}

View File

@@ -86,7 +86,7 @@ void waybar::Client::handleOutputDone(void *data, struct zxdg_output_v1 * /*xdg_
}
}
} catch (const std::exception &e) {
std::cerr << e.what() << '\n';
spdlog::warn("caught exception in zxdg_output_v1_listener::done: {}", e.what());
}
}
@@ -97,7 +97,7 @@ void waybar::Client::handleOutputName(void *data, struct zxdg_output_v1 * /*xdg_
auto &output = client->getOutput(data);
output.name = name;
} catch (const std::exception &e) {
std::cerr << e.what() << '\n';
spdlog::warn("caught exception in zxdg_output_v1_listener::name: {}", e.what());
}
}
@@ -106,13 +106,13 @@ void waybar::Client::handleOutputDescription(void *data, struct zxdg_output_v1 *
auto *client = waybar::Client::inst();
try {
auto &output = client->getOutput(data);
const char *open_paren = strrchr(description, '(');
// Description format: "identifier (name)"
size_t identifier_length = open_paren - description;
output.identifier = std::string(description, identifier_length - 1);
auto s = std::string(description);
auto pos = s.find(" (");
output.identifier = pos != std::string::npos ? s.substr(0, pos) : s;
} catch (const std::exception &e) {
std::cerr << e.what() << '\n';
spdlog::warn("caught exception in zxdg_output_v1_listener::description: {}", e.what());
}
}

View File

@@ -34,6 +34,7 @@
#include "modules/hyprland/language.hpp"
#include "modules/hyprland/submap.hpp"
#include "modules/hyprland/window.hpp"
#include "modules/hyprland/windowcount.hpp"
#include "modules/hyprland/workspaces.hpp"
#endif
#ifdef HAVE_NIRI
@@ -41,6 +42,10 @@
#include "modules/niri/window.hpp"
#include "modules/niri/workspaces.hpp"
#endif
#ifdef HAVE_WAYFIRE
#include "modules/wayfire/window.hpp"
#include "modules/wayfire/workspaces.hpp"
#endif
#if defined(__FreeBSD__) || defined(__linux__)
#include "modules/battery.hpp"
#endif
@@ -109,6 +114,9 @@
#ifdef HAVE_SYSTEMD_MONITOR
#include "modules/systemd_failed_units.hpp"
#endif
#ifdef HAVE_LIBGPS
#include "modules/gps.hpp"
#endif
#include "modules/cffi.hpp"
#include "modules/custom.hpp"
#include "modules/image.hpp"
@@ -140,7 +148,7 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name,
#endif
#ifdef HAVE_PIPEWIRE
if (ref == "privacy") {
return new waybar::modules::privacy::Privacy(id, config_[name], pos);
return new waybar::modules::privacy::Privacy(id, config_[name], bar_.orientation, pos);
}
#endif
#ifdef HAVE_MPRIS
@@ -201,6 +209,9 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name,
if (ref == "hyprland/window") {
return new waybar::modules::hyprland::Window(id, bar_, config_[name]);
}
if (ref == "hyprland/windowcount") {
return new waybar::modules::hyprland::WindowCount(id, bar_, config_[name]);
}
if (ref == "hyprland/language") {
return new waybar::modules::hyprland::Language(id, bar_, config_[name]);
}
@@ -221,6 +232,14 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name,
if (ref == "niri/workspaces") {
return new waybar::modules::niri::Workspaces(id, bar_, config_[name]);
}
#endif
#ifdef HAVE_WAYFIRE
if (ref == "wayfire/window") {
return new waybar::modules::wayfire::Window(id, bar_, config_[name]);
}
if (ref == "wayfire/workspaces") {
return new waybar::modules::wayfire::Workspaces(id, bar_, config_[name]);
}
#endif
if (ref == "idle_inhibitor") {
return new waybar::modules::IdleInhibitor(id, bar_, config_[name]);
@@ -331,6 +350,11 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name,
if (ref == "systemd-failed-units") {
return new waybar::modules::SystemdFailedUnits(id, config_[name]);
}
#endif
#ifdef HAVE_LIBGPS
if (ref == "gps") {
return new waybar::modules::Gps(id, config_[name]);
}
#endif
if (ref == "temperature") {
return new waybar::modules::Temperature(id, config_[name]);

View File

@@ -1,3 +1,4 @@
#include <fcntl.h>
#include <spdlog/spdlog.h>
#include <sys/types.h>
#include <sys/wait.h>
@@ -7,66 +8,115 @@
#include <mutex>
#include "client.hpp"
#include "util/SafeSignal.hpp"
std::mutex reap_mtx;
std::list<pid_t> reap;
volatile bool reload;
void* signalThread(void* args) {
int err;
int signum;
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGCHLD);
static int signal_pipe_write_fd;
// Write a single signal to `signal_pipe_write_fd`.
// This function is set as a signal handler, so it must be async-signal-safe.
static void writeSignalToPipe(int signum) {
ssize_t amt = write(signal_pipe_write_fd, &signum, sizeof(int));
// There's not much we can safely do inside of a signal handler.
// Let's just ignore any errors.
(void)amt;
}
// This initializes `signal_pipe_write_fd`, and sets up signal handlers.
//
// This function will run forever, emitting every `SIGUSR1`, `SIGUSR2`,
// `SIGINT`, `SIGCHLD`, and `SIGRTMIN + 1`...`SIGRTMAX` signal received
// to `signal_handler`.
static void catchSignals(waybar::SafeSignal<int>& signal_handler) {
int fd[2];
pipe(fd);
int signal_pipe_read_fd = fd[0];
signal_pipe_write_fd = fd[1];
// This pipe should be able to buffer ~thousands of signals. If it fills up,
// we'll drop signals instead of blocking.
// We can't allow the write end to block because we'll be writing to it in a
// signal handler, which could interrupt the loop that's reading from it and
// deadlock.
fcntl(signal_pipe_write_fd, F_SETFL, O_NONBLOCK);
std::signal(SIGUSR1, writeSignalToPipe);
std::signal(SIGUSR2, writeSignalToPipe);
std::signal(SIGINT, writeSignalToPipe);
std::signal(SIGCHLD, writeSignalToPipe);
for (int sig = SIGRTMIN + 1; sig <= SIGRTMAX; ++sig) {
std::signal(sig, writeSignalToPipe);
}
while (true) {
err = sigwait(&mask, &signum);
if (err != 0) {
spdlog::error("sigwait failed: {}", strerror(errno));
int signum;
ssize_t amt = read(signal_pipe_read_fd, &signum, sizeof(int));
if (amt < 0) {
spdlog::error("read from signal pipe failed with error {}, closing thread", strerror(errno));
break;
}
if (amt != sizeof(int)) {
continue;
}
switch (signum) {
case SIGCHLD:
spdlog::debug("Received SIGCHLD in signalThread");
if (!reap.empty()) {
reap_mtx.lock();
for (auto it = reap.begin(); it != reap.end(); ++it) {
if (waitpid(*it, nullptr, WNOHANG) == *it) {
spdlog::debug("Reaped child with PID: {}", *it);
it = reap.erase(it);
}
}
reap_mtx.unlock();
}
break;
default:
spdlog::debug("Received signal with number {}, but not handling", signum);
break;
}
signal_handler.emit(signum);
}
}
void startSignalThread() {
int err;
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGCHLD);
// Must be called on the main thread.
//
// If this signal should restart or close the bar, this function will write
// `true` or `false`, respectively, into `reload`.
static void handleSignalMainThread(int signum, bool& reload) {
if (signum >= SIGRTMIN + 1 && signum <= SIGRTMAX) {
for (auto& bar : waybar::Client::inst()->bars) {
bar->handleSignal(signum);
}
// Block SIGCHLD so it can be handled by the signal thread
// Any threads created by this one (the main thread) should not
// modify their signal mask to unblock SIGCHLD
err = pthread_sigmask(SIG_BLOCK, &mask, nullptr);
if (err != 0) {
spdlog::error("pthread_sigmask failed in startSignalThread: {}", strerror(err));
exit(1);
return;
}
pthread_t thread_id;
err = pthread_create(&thread_id, nullptr, signalThread, nullptr);
if (err != 0) {
spdlog::error("pthread_create failed in startSignalThread: {}", strerror(err));
exit(1);
switch (signum) {
case SIGUSR1:
spdlog::debug("Visibility toggled");
for (auto& bar : waybar::Client::inst()->bars) {
bar->toggle();
}
break;
case SIGUSR2:
spdlog::info("Reloading...");
reload = true;
waybar::Client::inst()->reset();
break;
case SIGINT:
spdlog::info("Quitting.");
reload = false;
waybar::Client::inst()->reset();
break;
case SIGCHLD:
spdlog::debug("Received SIGCHLD in signalThread");
if (!reap.empty()) {
reap_mtx.lock();
for (auto it = reap.begin(); it != reap.end(); ++it) {
if (waitpid(*it, nullptr, WNOHANG) == *it) {
spdlog::debug("Reaped child with PID: {}", *it);
it = reap.erase(it);
}
}
reap_mtx.unlock();
}
break;
default:
spdlog::debug("Received signal with number {}, but not handling", signum);
break;
}
}
@@ -74,32 +124,16 @@ int main(int argc, char* argv[]) {
try {
auto* client = waybar::Client::inst();
std::signal(SIGUSR1, [](int /*signal*/) {
for (auto& bar : waybar::Client::inst()->bars) {
bar->toggle();
}
});
bool reload;
std::signal(SIGUSR2, [](int /*signal*/) {
spdlog::info("Reloading...");
reload = true;
waybar::Client::inst()->reset();
});
waybar::SafeSignal<int> posix_signal_received;
posix_signal_received.connect([&](int signum) { handleSignalMainThread(signum, reload); });
std::signal(SIGINT, [](int /*signal*/) {
spdlog::info("Quitting.");
reload = false;
waybar::Client::inst()->reset();
});
std::thread signal_thread([&]() { catchSignals(posix_signal_received); });
for (int sig = SIGRTMIN + 1; sig <= SIGRTMAX; ++sig) {
std::signal(sig, [](int sig) {
for (auto& bar : waybar::Client::inst()->bars) {
bar->handleSignal(sig);
}
});
}
startSignalThread();
// Every `std::thread` must be joined or detached.
// This thread should run forever, so detach it.
signal_thread.detach();
auto ret = 0;
do {

View File

@@ -28,7 +28,7 @@ CFFI::CFFI(const std::string& name, const std::string& id, const Json::Value& co
}
// Fetch functions
if (*wbcffi_version == 1) {
if (*wbcffi_version == 1 || *wbcffi_version == 2) {
// Mandatory functions
hooks_.init = reinterpret_cast<InitFn*>(dlsym(handle, "wbcffi_init"));
if (!hooks_.init) {
@@ -58,10 +58,14 @@ CFFI::CFFI(const std::string& name, const std::string& id, const Json::Value& co
const auto& keys = config.getMemberNames();
for (size_t i = 0; i < keys.size(); i++) {
const auto& value = config[keys[i]];
if (value.isConvertibleTo(Json::ValueType::stringValue)) {
config_entries_stringstor.push_back(config[keys[i]].asString());
if (*wbcffi_version == 1) {
if (value.isConvertibleTo(Json::ValueType::stringValue)) {
config_entries_stringstor.push_back(value.asString());
} else {
config_entries_stringstor.push_back(value.toStyledString());
}
} else {
config_entries_stringstor.push_back(config[keys[i]].toStyledString());
config_entries_stringstor.push_back(value.toStyledString());
}
}

View File

@@ -1,5 +1,6 @@
#include "modules/clock.hpp"
#include <glib.h>
#include <gtkmm/tooltip.h>
#include <spdlog/spdlog.h>
@@ -16,6 +17,7 @@
#include <clocale>
#endif
using namespace date;
namespace fmt_lib = waybar::util::date::format;
waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
@@ -25,6 +27,7 @@ waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
m_tooltip_{new Gtk::Label()},
cldInTooltip_{m_tlpFmt_.find("{" + kCldPlaceholder + "}") != std::string::npos},
cldYearShift_{January / 1 / 1900},
cldMonShift_{year(1900) / January},
tzInTooltip_{m_tlpFmt_.find("{" + kTZPlaceholder + "}") != std::string::npos},
tzCurrIdx_{0},
ordInTooltip_{m_tlpFmt_.find("{" + kOrdPlaceholder + "}") != std::string::npos} {
@@ -348,9 +351,9 @@ auto waybar::modules::Clock::get_calendar(const year_month_day& today, const yea
m_locale_, fmtMap_[4],
fmt_lib::make_format_args(
(line == 2)
? static_cast<const date::zoned_seconds&&>(
? static_cast<const zoned_seconds&&>(
zoned_seconds{tz, local_days{ymTmp / 1}})
: static_cast<const date::zoned_seconds&&>(zoned_seconds{
: static_cast<const zoned_seconds&&>(zoned_seconds{
tz, local_days{cldGetWeekForLine(ymTmp, firstdow, line)}})))
<< ' ';
} else
@@ -358,10 +361,23 @@ auto waybar::modules::Clock::get_calendar(const year_month_day& today, const yea
}
}
os << Glib::ustring::format((cldWPos_ != WS::LEFT || line == 0) ? std::left : std::right,
std::setfill(L' '),
std::setw(cldMonColLen_ + ((line < 2) ? cldWnLen_ : 0)),
getCalendarLine(today, ymTmp, line, firstdow, &m_locale_));
// Count wide characters to avoid extra padding
size_t wideCharCount = 0;
std::string calendarLine = getCalendarLine(today, ymTmp, line, firstdow, &m_locale_);
if (line < 2) {
for (gchar *data = calendarLine.data(), *end = data + calendarLine.size();
data != nullptr;) {
gunichar c = g_utf8_get_char_validated(data, end - data);
if (g_unichar_iswide(c)) {
wideCharCount++;
}
data = g_utf8_find_next_char(data, end);
}
}
os << Glib::ustring::format(
(cldWPos_ != WS::LEFT || line == 0) ? std::left : std::right, std::setfill(L' '),
std::setw(cldMonColLen_ + ((line < 2) ? cldWnLen_ - wideCharCount : 0)),
calendarLine);
// Week numbers on the right
if (cldWPos_ == WS::RIGHT && line > 0) {
@@ -371,9 +387,9 @@ auto waybar::modules::Clock::get_calendar(const year_month_day& today, const yea
<< fmt_lib::vformat(
m_locale_, fmtMap_[4],
fmt_lib::make_format_args(
(line == 2) ? static_cast<const date::zoned_seconds&&>(
(line == 2) ? static_cast<const zoned_seconds&&>(
zoned_seconds{tz, local_days{ymTmp / 1}})
: static_cast<const date::zoned_seconds&&>(
: static_cast<const zoned_seconds&&>(
zoned_seconds{tz, local_days{cldGetWeekForLine(
ymTmp, firstdow, line)}})));
else

View File

@@ -35,6 +35,13 @@ waybar::modules::Custom::~Custom() {
void waybar::modules::Custom::delayWorker() {
thread_ = [this] {
for (int i : this->pid_children_) {
int status;
waitpid(i, &status, 0);
}
this->pid_children_.clear();
bool can_update = true;
if (config_["exec-if"].isString()) {
output_ = util::command::execNoRead(config_["exec-if"].asString());
@@ -62,7 +69,7 @@ void waybar::modules::Custom::continuousWorker() {
}
thread_ = [this, cmd] {
char* buff = nullptr;
waybar::util::ScopeGuard buff_deleter([buff]() {
waybar::util::ScopeGuard buff_deleter([&buff]() {
if (buff) {
free(buff);
}

216
src/modules/gps.cpp Normal file
View File

@@ -0,0 +1,216 @@
#include "modules/gps.hpp"
#include <gps.h>
#include <spdlog/spdlog.h>
#include <cmath>
#include <cstdio>
// In the 80000 version of fmt library authors decided to optimize imports
// and moved declarations required for fmt::dynamic_format_arg_store in new
// header fmt/args.h
#if (FMT_VERSION >= 80000)
#include <fmt/args.h>
#else
#include <fmt/core.h>
#endif
namespace {
using namespace waybar::util;
constexpr const char* DEFAULT_FORMAT = "{mode}";
} // namespace
waybar::modules::Gps::Gps(const std::string& id, const Json::Value& config)
: ALabel(config, "gps", id, "{}", 5)
#ifdef WANT_RFKILL
,
rfkill_{RFKILL_TYPE_GPS}
#endif
{
thread_ = [this] {
dp.emit();
thread_.sleep_for(interval_);
};
if (0 != gps_open("localhost", "2947", &gps_data_)) {
throw std::runtime_error("Can't open gpsd socket");
}
if (config_["hide-disconnected"].isBool()) {
hideDisconnected = config_["hide-disconnected"].asBool();
}
if (config_["hide-no-fix"].isBool()) {
hideNoFix = config_["hide-no-fix"].asBool();
}
gps_thread_ = [this] {
dp.emit();
gps_stream(&gps_data_, WATCH_ENABLE, NULL);
int last_gps_mode = 0;
while (gps_waiting(&gps_data_, 5000000)) {
if (gps_read(&gps_data_, NULL, 0) == -1) {
throw std::runtime_error("Can't read data from gpsd.");
}
if (MODE_SET != (MODE_SET & gps_data_.set)) {
// did not even get mode, nothing to see here
continue;
}
if (gps_data_.fix.mode != last_gps_mode) {
// significant update
dp.emit();
}
last_gps_mode = gps_data_.fix.mode;
}
};
#ifdef WANT_RFKILL
rfkill_.on_update.connect(sigc::hide(sigc::mem_fun(*this, &Gps::update)));
#endif
}
const std::string waybar::modules::Gps::getFixModeName() const {
switch (gps_data_.fix.mode) {
case MODE_NO_FIX:
return "fix-none";
case MODE_2D:
return "fix-2d";
case MODE_3D:
return "fix-3d";
default:
#ifdef WANT_RFKILL
if (rfkill_.getState()) return "disabled";
#endif
return "disconnected";
}
}
const std::string waybar::modules::Gps::getFixModeString() const {
switch (gps_data_.fix.mode) {
case MODE_NO_FIX:
return "No fix";
case MODE_2D:
return "2D Fix";
case MODE_3D:
return "3D Fix";
default:
return "Disconnected";
}
}
const std::string waybar::modules::Gps::getFixStatusString() const {
switch (gps_data_.fix.status) {
case STATUS_GPS:
return "GPS";
case STATUS_DGPS:
return "DGPS";
case STATUS_RTK_FIX:
return "RTK Fixed";
case STATUS_RTK_FLT:
return "RTK Float";
case STATUS_DR:
return "Dead Reckoning";
case STATUS_GNSSDR:
return "GNSS + Dead Reckoning";
case STATUS_TIME:
return "Time Only";
case STATUS_PPS_FIX:
return "PPS Fix";
default:
#ifdef WANT_RFKILL
if (rfkill_.getState()) return "Disabled";
#endif
return "Unknown";
}
}
auto waybar::modules::Gps::update() -> void {
sleep(0); // Wait for gps status change
if ((gps_data_.fix.mode == MODE_NOT_SEEN && hideDisconnected) ||
(gps_data_.fix.mode == MODE_NO_FIX && hideNoFix)) {
event_box_.set_visible(false);
return;
}
// Show the module
if (!event_box_.get_visible()) event_box_.set_visible(true);
std::string tooltip_format;
if (!alt_) {
auto state = getFixModeName();
if (!state_.empty() && label_.get_style_context()->has_class(state_)) {
label_.get_style_context()->remove_class(state_);
}
if (config_["format-" + state].isString()) {
default_format_ = config_["format-" + state].asString();
} else if (config_["format"].isString()) {
default_format_ = config_["format"].asString();
} else {
default_format_ = DEFAULT_FORMAT;
}
if (config_["tooltip-format-" + state].isString()) {
tooltip_format = config_["tooltip-format-" + state].asString();
}
if (!label_.get_style_context()->has_class(state)) {
label_.get_style_context()->add_class(state);
}
format_ = default_format_;
state_ = state;
}
auto format = format_;
fmt::dynamic_format_arg_store<fmt::format_context> store;
store.push_back(fmt::arg("mode", getFixModeString()));
store.push_back(fmt::arg("status", getFixStatusString()));
store.push_back(fmt::arg("latitude", gps_data_.fix.latitude));
store.push_back(fmt::arg("latitude_error", gps_data_.fix.epy));
store.push_back(fmt::arg("longitude", gps_data_.fix.longitude));
store.push_back(fmt::arg("longitude_error", gps_data_.fix.epx));
store.push_back(fmt::arg("altitude_hae", gps_data_.fix.altHAE));
store.push_back(fmt::arg("altitude_msl", gps_data_.fix.altMSL));
store.push_back(fmt::arg("altitude_error", gps_data_.fix.epv));
store.push_back(fmt::arg("speed", gps_data_.fix.speed));
store.push_back(fmt::arg("speed_error", gps_data_.fix.eps));
store.push_back(fmt::arg("climb", gps_data_.fix.climb));
store.push_back(fmt::arg("climb_error", gps_data_.fix.epc));
store.push_back(fmt::arg("satellites_used", gps_data_.satellites_used));
store.push_back(fmt::arg("satellites_visible", gps_data_.satellites_visible));
auto text = fmt::vformat(format, store);
if (tooltipEnabled()) {
if (tooltip_format.empty() && config_["tooltip-format"].isString()) {
tooltip_format = config_["tooltip-format"].asString();
}
if (!tooltip_format.empty()) {
auto tooltip_text = fmt::vformat(tooltip_format, store);
if (label_.get_tooltip_text() != tooltip_text) {
label_.set_tooltip_markup(tooltip_text);
}
} else if (label_.get_tooltip_text() != text) {
label_.set_tooltip_markup(text);
}
}
label_.set_markup(text);
// Call parent update
ALabel::update();
}
waybar::modules::Gps::~Gps() {
gps_stream(&gps_data_, WATCH_DISABLE, NULL);
gps_close(&gps_data_);
}

View File

@@ -66,7 +66,12 @@ auto Language::update() -> void {
void Language::onEvent(const std::string& ev) {
std::lock_guard<std::mutex> lg(mutex_);
std::string kbName(begin(ev) + ev.find_last_of('>') + 1, begin(ev) + ev.find_first_of(','));
auto layoutName = ev.substr(ev.find_last_of(',') + 1);
// Last comma before variants parenthesis, eg:
// activelayout>>micro-star-int'l-co.,-ltd.-msi-gk50-elite-gaming-keyboard,English (US, intl.,
// with dead keys)
std::string beforParenthesis(begin(ev), begin(ev) + ev.find_last_of('('));
auto layoutName = ev.substr(beforParenthesis.find_last_of(',') + 1);
if (config_.isMember("keyboard-name") && kbName != config_["keyboard-name"].asString())
return; // ignore

View File

@@ -16,7 +16,7 @@ Submap::Submap(const std::string& id, const Bar& bar, const Json::Value& config)
ALabel::update();
// Displays widget immediately if always_on_ assuming default submap
// Needs an actual way to retrive current submap on startup
// Needs an actual way to retrieve current submap on startup
if (always_on_) {
submap_ = default_submap_;
label_.get_style_context()->add_class(submap_);
@@ -68,8 +68,7 @@ void Submap::onEvent(const std::string& ev) {
return;
}
auto submapName = ev.substr(ev.find_last_of('>') + 1);
submapName = waybar::util::sanitize_string(submapName);
auto submapName = ev.substr(ev.find_first_of('>') + 2);
if (!submap_.empty()) {
label_.get_style_context()->remove_class(submap_);

View File

@@ -0,0 +1,142 @@
#include "modules/hyprland/windowcount.hpp"
#include <glibmm/fileutils.h>
#include <glibmm/keyfile.h>
#include <glibmm/miscutils.h>
#include <spdlog/spdlog.h>
#include <algorithm>
#include <vector>
#include "modules/hyprland/backend.hpp"
#include "util/sanitize_str.hpp"
namespace waybar::modules::hyprland {
WindowCount::WindowCount(const std::string& id, const Bar& bar, const Json::Value& config)
: AAppIconLabel(config, "windowcount", id, "{count}", 0, true), bar_(bar) {
modulesReady = true;
separateOutputs_ =
config.isMember("separate-outputs") ? config["separate-outputs"].asBool() : true;
if (!gIPC) {
gIPC = std::make_unique<IPC>();
}
queryActiveWorkspace();
update();
dp.emit();
// register for hyprland ipc
gIPC->registerForIPC("fullscreen", this);
gIPC->registerForIPC("workspace", this);
gIPC->registerForIPC("focusedmon", this);
gIPC->registerForIPC("openwindow", this);
gIPC->registerForIPC("closewindow", this);
gIPC->registerForIPC("movewindow", this);
}
WindowCount::~WindowCount() {
gIPC->unregisterForIPC(this);
// wait for possible event handler to finish
std::lock_guard<std::mutex> lg(mutex_);
}
auto WindowCount::update() -> void {
std::lock_guard<std::mutex> lg(mutex_);
std::string format = config_["format"].asString();
std::string formatEmpty = config_["format-empty"].asString();
std::string formatWindowed = config_["format-windowed"].asString();
std::string formatFullscreen = config_["format-fullscreen"].asString();
setClass("empty", workspace_.windows == 0);
setClass("fullscreen", workspace_.hasfullscreen);
if (workspace_.windows == 0 && !formatEmpty.empty()) {
label_.set_markup(fmt::format(fmt::runtime(formatEmpty), workspace_.windows));
} else if (!workspace_.hasfullscreen && !formatWindowed.empty()) {
label_.set_markup(fmt::format(fmt::runtime(formatWindowed), workspace_.windows));
} else if (workspace_.hasfullscreen && !formatFullscreen.empty()) {
label_.set_markup(fmt::format(fmt::runtime(formatFullscreen), workspace_.windows));
} else if (!format.empty()) {
label_.set_markup(fmt::format(fmt::runtime(format), workspace_.windows));
} else {
label_.set_text(fmt::format("{}", workspace_.windows));
}
label_.show();
AAppIconLabel::update();
}
auto WindowCount::getActiveWorkspace() -> Workspace {
const auto workspace = gIPC->getSocket1JsonReply("activeworkspace");
if (workspace.isObject()) {
return Workspace::parse(workspace);
}
return {};
}
auto WindowCount::getActiveWorkspace(const std::string& monitorName) -> Workspace {
const auto monitors = gIPC->getSocket1JsonReply("monitors");
if (monitors.isArray()) {
auto monitor = std::find_if(monitors.begin(), monitors.end(), [&](Json::Value monitor) {
return monitor["name"] == monitorName;
});
if (monitor == std::end(monitors)) {
spdlog::warn("Monitor not found: {}", monitorName);
return Workspace{-1, 0, false};
}
const int id = (*monitor)["activeWorkspace"]["id"].asInt();
const auto workspaces = gIPC->getSocket1JsonReply("workspaces");
if (workspaces.isArray()) {
auto workspace = std::find_if(workspaces.begin(), workspaces.end(),
[&](Json::Value workspace) { return workspace["id"] == id; });
if (workspace == std::end(workspaces)) {
spdlog::warn("No workspace with id {}", id);
return Workspace{-1, 0, false};
}
return Workspace::parse(*workspace);
};
};
return {};
}
auto WindowCount::Workspace::parse(const Json::Value& value) -> WindowCount::Workspace {
return Workspace{
value["id"].asInt(),
value["windows"].asInt(),
value["hasfullscreen"].asBool(),
};
}
void WindowCount::queryActiveWorkspace() {
std::lock_guard<std::mutex> lg(mutex_);
if (separateOutputs_) {
workspace_ = getActiveWorkspace(this->bar_.output->name);
} else {
workspace_ = getActiveWorkspace();
}
}
void WindowCount::onEvent(const std::string& ev) {
queryActiveWorkspace();
dp.emit();
}
void WindowCount::setClass(const std::string& classname, bool enable) {
if (enable) {
if (!bar_.window.get_style_context()->has_class(classname)) {
bar_.window.get_style_context()->add_class(classname);
}
} else {
bar_.window.get_style_context()->remove_class(classname);
}
}
} // namespace waybar::modules::hyprland

View File

@@ -88,7 +88,7 @@ bool WindowCreationPayload::isEmpty(Workspaces &workspace_manager) {
int WindowCreationPayload::incrementTimeSpentUncreated() { return m_timeSpentUncreated++; }
void WindowCreationPayload::moveToWorksace(std::string &new_workspace_name) {
void WindowCreationPayload::moveToWorkspace(std::string &new_workspace_name) {
m_workspaceName = new_workspace_name;
}

View File

@@ -91,19 +91,19 @@ void Workspace::initializeWindowMap(const Json::Value &clients_data) {
}
}
void Workspace::insertWindow(WindowCreationPayload create_window_paylod) {
if (!create_window_paylod.isEmpty(m_workspaceManager)) {
auto repr = create_window_paylod.repr(m_workspaceManager);
void Workspace::insertWindow(WindowCreationPayload create_window_payload) {
if (!create_window_payload.isEmpty(m_workspaceManager)) {
auto repr = create_window_payload.repr(m_workspaceManager);
if (!repr.empty()) {
m_windowMap[create_window_paylod.getAddress()] = repr;
m_windowMap[create_window_payload.getAddress()] = repr;
}
}
};
bool Workspace::onWindowOpened(WindowCreationPayload const &create_window_paylod) {
if (create_window_paylod.getWorkspaceName() == name()) {
insertWindow(create_window_paylod);
bool Workspace::onWindowOpened(WindowCreationPayload const &create_window_payload) {
if (create_window_payload.getWorkspaceName() == name()) {
insertWindow(create_window_payload);
return true;
}
return false;
@@ -173,6 +173,10 @@ std::string &Workspace::selectIcon(std::map<std::string, std::string> &icons_map
}
void Workspace::update(const std::string &format, const std::string &icon) {
if (this->m_workspaceManager.persistentOnly() && !this->isPersistent()) {
m_button.hide();
return;
}
// clang-format off
if (this->m_workspaceManager.activeOnly() && \
!this->isActive() && \

View File

@@ -5,6 +5,7 @@
#include <algorithm>
#include <memory>
#include <sstream>
#include <string>
#include <utility>
@@ -39,7 +40,7 @@ Workspaces::~Workspaces() {
}
void Workspaces::init() {
m_activeWorkspaceName = (m_ipc.getSocket1JsonReply("activeworkspace"))["name"].asString();
m_activeWorkspaceId = m_ipc.getSocket1JsonReply("activeworkspace")["id"].asInt();
initializeWorkspaces();
dp.emit();
@@ -49,13 +50,12 @@ Json::Value Workspaces::createMonitorWorkspaceData(std::string const &name,
std::string const &monitor) {
spdlog::trace("Creating persistent workspace: {} on monitor {}", name, monitor);
Json::Value workspaceData;
try {
// numbered persistent workspaces get the name as ID
workspaceData["id"] = name == "special" ? -99 : std::stoi(name);
} catch (const std::exception &e) {
// named persistent workspaces start with ID=0
workspaceData["id"] = 0;
auto workspaceId = parseWorkspaceId(name);
if (!workspaceId.has_value()) {
workspaceId = 0;
}
workspaceData["id"] = *workspaceId;
workspaceData["name"] = name;
workspaceData["monitor"] = monitor;
workspaceData["windows"] = 0;
@@ -68,9 +68,8 @@ void Workspaces::createWorkspace(Json::Value const &workspace_data,
spdlog::debug("Creating workspace {}", workspaceName);
// avoid recreating existing workspaces
auto workspace = std::find_if(
m_workspaces.begin(), m_workspaces.end(),
[workspaceName](std::unique_ptr<Workspace> const &w) {
auto workspace =
std::ranges::find_if(m_workspaces, [workspaceName](std::unique_ptr<Workspace> const &w) {
return (workspaceName.starts_with("special:") && workspaceName.substr(8) == w->name()) ||
workspaceName == w->name();
});
@@ -80,14 +79,14 @@ void Workspaces::createWorkspace(Json::Value const &workspace_data,
const auto keys = workspace_data.getMemberNames();
const auto *k = "persistent-rule";
if (std::find(keys.begin(), keys.end(), k) != keys.end()) {
if (std::ranges::find(keys, k) != keys.end()) {
spdlog::debug("Set dynamic persistency of workspace {} to: {}", workspaceName,
workspace_data[k].asBool() ? "true" : "false");
(*workspace)->setPersistentRule(workspace_data[k].asBool());
}
k = "persistent-config";
if (std::find(keys.begin(), keys.end(), k) != keys.end()) {
if (std::ranges::find(keys, k) != keys.end()) {
spdlog::debug("Set config persistency of workspace {} to: {}", workspaceName,
workspace_data[k].asBool() ? "true" : "false");
(*workspace)->setPersistentConfig(workspace_data[k].asBool());
@@ -158,18 +157,18 @@ std::string Workspaces::getRewrite(std::string window_class, std::string window_
fmt::arg("title", window_title));
}
std::vector<std::string> Workspaces::getVisibleWorkspaces() {
std::vector<std::string> visibleWorkspaces;
std::vector<int> Workspaces::getVisibleWorkspaces() {
std::vector<int> visibleWorkspaces;
auto monitors = IPC::inst().getSocket1JsonReply("monitors");
for (const auto &monitor : monitors) {
auto ws = monitor["activeWorkspace"];
if (ws.isObject() && ws["name"].isString()) {
visibleWorkspaces.push_back(ws["name"].asString());
if (ws.isObject() && ws["id"].isInt()) {
visibleWorkspaces.push_back(ws["id"].asInt());
}
auto sws = monitor["specialWorkspace"];
auto name = sws["name"].asString();
if (sws.isObject() && sws["name"].isString() && !name.empty()) {
visibleWorkspaces.push_back(!name.starts_with("special:") ? name : name.substr(8));
if (sws.isObject() && sws["id"].isInt() && !name.empty()) {
visibleWorkspaces.push_back(sws["id"].asInt());
}
}
return visibleWorkspaces;
@@ -180,7 +179,7 @@ void Workspaces::initializeWorkspaces() {
// if the workspace rules changed since last initialization, make sure we reset everything:
for (auto &workspace : m_workspaces) {
m_workspacesToRemove.push_back(workspace->name());
m_workspacesToRemove.push_back(std::to_string(workspace->id()));
}
// get all current workspaces
@@ -232,7 +231,7 @@ void Workspaces::loadPersistentWorkspacesFromConfig(Json::Value const &clientsJs
std::vector<std::string> persistentWorkspacesToCreate;
const std::string currentMonitor = m_bar.output->name;
const bool monitorInConfig = std::find(keys.begin(), keys.end(), currentMonitor) != keys.end();
const bool monitorInConfig = std::ranges::find(keys, currentMonitor) != keys.end();
for (const std::string &key : keys) {
// only add if either:
// 1. key is the current monitor name
@@ -247,7 +246,7 @@ void Workspaces::loadPersistentWorkspacesFromConfig(Json::Value const &clientsJs
int amount = value.asInt();
spdlog::debug("Creating {} persistent workspaces for monitor {}", amount, currentMonitor);
for (int i = 0; i < amount; i++) {
persistentWorkspacesToCreate.emplace_back(std::to_string(m_monitorId * amount + i + 1));
persistentWorkspacesToCreate.emplace_back(std::to_string((m_monitorId * amount) + i + 1));
}
}
} else if (value.isArray() && !value.empty()) {
@@ -306,6 +305,7 @@ void Workspaces::loadPersistentWorkspacesFromWorkspaceRules(const Json::Value &c
workspaceData["persistent-rule"] = true;
m_workspacesToCreate.emplace_back(workspaceData, clientsJson);
} else {
// This can be any workspace selector.
m_workspacesToRemove.emplace_back(workspace);
}
}
@@ -316,29 +316,29 @@ void Workspaces::onEvent(const std::string &ev) {
std::string eventName(begin(ev), begin(ev) + ev.find_first_of('>'));
std::string payload = ev.substr(eventName.size() + 2);
if (eventName == "workspace") {
if (eventName == "workspacev2") {
onWorkspaceActivated(payload);
} else if (eventName == "activespecial") {
onSpecialWorkspaceActivated(payload);
} else if (eventName == "destroyworkspace") {
} else if (eventName == "destroyworkspacev2") {
onWorkspaceDestroyed(payload);
} else if (eventName == "createworkspace") {
} else if (eventName == "createworkspacev2") {
onWorkspaceCreated(payload);
} else if (eventName == "focusedmon") {
} else if (eventName == "focusedmonv2") {
onMonitorFocused(payload);
} else if (eventName == "moveworkspace") {
} else if (eventName == "moveworkspacev2") {
onWorkspaceMoved(payload);
} else if (eventName == "openwindow") {
onWindowOpened(payload);
} else if (eventName == "closewindow") {
onWindowClosed(payload);
} else if (eventName == "movewindow") {
} else if (eventName == "movewindowv2") {
onWindowMoved(payload);
} else if (eventName == "urgent") {
setUrgentWorkspace(payload);
} else if (eventName == "renameworkspace") {
onWorkspaceRenamed(payload);
} else if (eventName == "windowtitle") {
} else if (eventName == "windowtitlev2") {
onWindowTitleEvent(payload);
} else if (eventName == "configreloaded") {
onConfigReloaded();
@@ -348,7 +348,11 @@ void Workspaces::onEvent(const std::string &ev) {
}
void Workspaces::onWorkspaceActivated(std::string const &payload) {
m_activeWorkspaceName = payload;
const auto [workspaceIdStr, workspaceName] = splitDoublePayload(payload);
const auto workspaceId = parseWorkspaceId(workspaceIdStr);
if (workspaceId.has_value()) {
m_activeWorkspaceId = *workspaceId;
}
}
void Workspaces::onSpecialWorkspaceActivated(std::string const &payload) {
@@ -357,42 +361,55 @@ void Workspaces::onSpecialWorkspaceActivated(std::string const &payload) {
}
void Workspaces::onWorkspaceDestroyed(std::string const &payload) {
if (!isDoubleSpecial(payload)) {
m_workspacesToRemove.push_back(payload);
const auto [workspaceId, workspaceName] = splitDoublePayload(payload);
if (!isDoubleSpecial(workspaceName)) {
m_workspacesToRemove.push_back(workspaceId);
}
}
void Workspaces::onWorkspaceCreated(std::string const &workspaceName,
Json::Value const &clientsData) {
spdlog::debug("Workspace created: {}", workspaceName);
void Workspaces::onWorkspaceCreated(std::string const &payload, Json::Value const &clientsData) {
spdlog::debug("Workspace created: {}", payload);
const auto [workspaceIdStr, _] = splitDoublePayload(payload);
const auto workspaceId = parseWorkspaceId(workspaceIdStr);
if (!workspaceId.has_value()) {
return;
}
auto const workspaceRules = m_ipc.getSocket1JsonReply("workspacerules");
auto const workspacesJson = m_ipc.getSocket1JsonReply("workspaces");
if (!isWorkspaceIgnored(workspaceName)) {
auto const workspaceRules = m_ipc.getSocket1JsonReply("workspacerules");
for (Json::Value workspaceJson : workspacesJson) {
std::string name = workspaceJson["name"].asString();
if (name == workspaceName) {
if ((allOutputs() || m_bar.output->name == workspaceJson["monitor"].asString()) &&
(showSpecial() || !name.starts_with("special")) && !isDoubleSpecial(workspaceName)) {
for (Json::Value const &rule : workspaceRules) {
auto ruleWorkspaceName = rule.isMember("defaultName")
? rule["defaultName"].asString()
: rule["workspaceString"].asString();
if (ruleWorkspaceName == workspaceName) {
workspaceJson["persistent-rule"] = rule["persistent"].asBool();
break;
}
}
m_workspacesToCreate.emplace_back(workspaceJson, clientsData);
break;
}
} else {
extendOrphans(workspaceJson["id"].asInt(), clientsData);
for (Json::Value workspaceJson : workspacesJson) {
const auto currentId = workspaceJson["id"].asInt();
if (currentId == *workspaceId) {
std::string workspaceName = workspaceJson["name"].asString();
// This workspace name is more up-to-date than the one in the event payload.
if (isWorkspaceIgnored(workspaceName)) {
spdlog::trace("Not creating workspace because it is ignored: id={} name={}", *workspaceId,
workspaceName);
break;
}
if ((allOutputs() || m_bar.output->name == workspaceJson["monitor"].asString()) &&
(showSpecial() || !workspaceName.starts_with("special")) &&
!isDoubleSpecial(workspaceName)) {
for (Json::Value const &rule : workspaceRules) {
auto ruleWorkspaceName = rule.isMember("defaultName")
? rule["defaultName"].asString()
: rule["workspaceString"].asString();
if (ruleWorkspaceName == workspaceName) {
workspaceJson["persistent-rule"] = rule["persistent"].asBool();
break;
}
}
m_workspacesToCreate.emplace_back(workspaceJson, clientsData);
break;
}
} else {
extendOrphans(*workspaceId, clientsData);
}
} else {
spdlog::trace("Not creating workspace because it is ignored: {}", workspaceName);
}
}
@@ -400,32 +417,34 @@ void Workspaces::onWorkspaceMoved(std::string const &payload) {
spdlog::debug("Workspace moved: {}", payload);
// Update active workspace
m_activeWorkspaceName = (m_ipc.getSocket1JsonReply("activeworkspace"))["name"].asString();
m_activeWorkspaceId = (m_ipc.getSocket1JsonReply("activeworkspace"))["id"].asInt();
if (allOutputs()) return;
std::string workspaceName = payload.substr(0, payload.find(','));
std::string monitorName = payload.substr(payload.find(',') + 1);
const auto [workspaceIdStr, workspaceName, monitorName] = splitTriplePayload(payload);
const auto subPayload = makePayload(workspaceIdStr, workspaceName);
if (m_bar.output->name == monitorName) {
Json::Value clientsData = m_ipc.getSocket1JsonReply("clients");
onWorkspaceCreated(workspaceName, clientsData);
onWorkspaceCreated(subPayload, clientsData);
} else {
spdlog::debug("Removing workspace because it was moved to another monitor: {}");
onWorkspaceDestroyed(workspaceName);
spdlog::debug("Removing workspace because it was moved to another monitor: {}", subPayload);
onWorkspaceDestroyed(subPayload);
}
}
void Workspaces::onWorkspaceRenamed(std::string const &payload) {
spdlog::debug("Workspace renamed: {}", payload);
std::string workspaceIdStr = payload.substr(0, payload.find(','));
int workspaceId = workspaceIdStr == "special" ? -99 : std::stoi(workspaceIdStr);
std::string newName = payload.substr(payload.find(',') + 1);
const auto [workspaceIdStr, newName] = splitDoublePayload(payload);
const auto workspaceId = parseWorkspaceId(workspaceIdStr);
if (!workspaceId.has_value()) {
return;
}
for (auto &workspace : m_workspaces) {
if (workspace->id() == workspaceId) {
if (workspace->name() == m_activeWorkspaceName) {
m_activeWorkspaceName = newName;
}
if (workspace->id() == *workspaceId) {
workspace->setName(newName);
break;
}
@@ -435,11 +454,19 @@ void Workspaces::onWorkspaceRenamed(std::string const &payload) {
void Workspaces::onMonitorFocused(std::string const &payload) {
spdlog::trace("Monitor focused: {}", payload);
m_activeWorkspaceName = payload.substr(payload.find(',') + 1);
const auto [monitorName, workspaceIdStr] = splitDoublePayload(payload);
const auto workspaceId = parseWorkspaceId(workspaceIdStr);
if (!workspaceId.has_value()) {
return;
}
m_activeWorkspaceId = *workspaceId;
for (Json::Value &monitor : m_ipc.getSocket1JsonReply("monitors")) {
if (monitor["name"].asString() == payload.substr(0, payload.find(','))) {
auto name = monitor["specialWorkspace"]["name"].asString();
if (monitor["name"].asString() == monitorName) {
const auto name = monitor["specialWorkspace"]["name"].asString();
m_activeSpecialWorkspaceName = !name.starts_with("special:") ? name : name.substr(8);
}
}
@@ -478,11 +505,7 @@ void Workspaces::onWindowClosed(std::string const &addr) {
void Workspaces::onWindowMoved(std::string const &payload) {
spdlog::trace("Window moved: {}", payload);
updateWindowCount();
size_t lastCommaIdx = 0;
size_t nextCommaIdx = payload.find(',');
std::string windowAddress = payload.substr(lastCommaIdx, nextCommaIdx - lastCommaIdx);
std::string workspaceName = payload.substr(nextCommaIdx + 1, payload.length() - nextCommaIdx);
auto [windowAddress, _, workspaceName] = splitTriplePayload(payload);
std::string windowRepr;
@@ -490,7 +513,7 @@ void Workspaces::onWindowMoved(std::string const &payload) {
// and exit
for (auto &window : m_windowsToCreate) {
if (window.getAddress() == windowAddress) {
window.moveToWorksace(workspaceName);
window.moveToWorkspace(workspaceName);
return;
}
}
@@ -518,13 +541,15 @@ void Workspaces::onWindowTitleEvent(std::string const &payload) {
spdlog::trace("Window title changed: {}", payload);
std::optional<std::function<void(WindowCreationPayload)>> inserter;
const auto [windowAddress, _] = splitDoublePayload(payload);
// If the window was an orphan, rename it at the orphan's vector
if (m_orphanWindowMap.contains(payload)) {
if (m_orphanWindowMap.contains(windowAddress)) {
inserter = [this](WindowCreationPayload wcp) { this->registerOrphanWindow(std::move(wcp)); };
} else {
auto windowWorkspace =
std::find_if(m_workspaces.begin(), m_workspaces.end(),
[payload](auto &workspace) { return workspace->containsWindow(payload); });
auto windowWorkspace = std::ranges::find_if(m_workspaces, [windowAddress](auto &workspace) {
return workspace->containsWindow(windowAddress);
});
// If the window exists on a workspace, rename it at the workspace's window
// map
@@ -533,9 +558,9 @@ void Workspaces::onWindowTitleEvent(std::string const &payload) {
(*windowWorkspace)->insertWindow(std::move(wcp));
};
} else {
auto queuedWindow = std::find_if(
m_windowsToCreate.begin(), m_windowsToCreate.end(),
[payload](auto &windowPayload) { return windowPayload.getAddress() == payload; });
auto queuedWindow = std::ranges::find_if(m_windowsToCreate, [payload](auto &windowPayload) {
return windowPayload.getAddress() == payload;
});
// If the window was queued, rename it in the queue
if (queuedWindow != m_windowsToCreate.end()) {
@@ -548,12 +573,11 @@ void Workspaces::onWindowTitleEvent(std::string const &payload) {
Json::Value clientsData = m_ipc.getSocket1JsonReply("clients");
std::string jsonWindowAddress = fmt::format("0x{}", payload);
auto client =
std::find_if(clientsData.begin(), clientsData.end(), [jsonWindowAddress](auto &client) {
return client["address"].asString() == jsonWindowAddress;
});
auto client = std::ranges::find_if(clientsData, [jsonWindowAddress](auto &client) {
return client["address"].asString() == jsonWindowAddress;
});
if (!client->empty()) {
if (client != clientsData.end() && !client->empty()) {
(*inserter)({*client});
}
}
@@ -576,6 +600,7 @@ auto Workspaces::parseConfig(const Json::Value &config) -> void {
populateBoolConfig(config, "all-outputs", m_allOutputs);
populateBoolConfig(config, "show-special", m_showSpecial);
populateBoolConfig(config, "special-visible-only", m_specialVisibleOnly);
populateBoolConfig(config, "persistent-only", m_persistentOnly);
populateBoolConfig(config, "active-only", m_activeOnly);
populateBoolConfig(config, "move-to-monitor", m_moveToMonitor);
@@ -663,40 +688,58 @@ void Workspaces::registerOrphanWindow(WindowCreationPayload create_window_payloa
}
auto Workspaces::registerIpc() -> void {
m_ipc.registerForIPC("workspace", this);
m_ipc.registerForIPC("workspacev2", this);
m_ipc.registerForIPC("activespecial", this);
m_ipc.registerForIPC("createworkspace", this);
m_ipc.registerForIPC("destroyworkspace", this);
m_ipc.registerForIPC("focusedmon", this);
m_ipc.registerForIPC("moveworkspace", this);
m_ipc.registerForIPC("createworkspacev2", this);
m_ipc.registerForIPC("destroyworkspacev2", this);
m_ipc.registerForIPC("focusedmonv2", this);
m_ipc.registerForIPC("moveworkspacev2", this);
m_ipc.registerForIPC("renameworkspace", this);
m_ipc.registerForIPC("openwindow", this);
m_ipc.registerForIPC("closewindow", this);
m_ipc.registerForIPC("movewindow", this);
m_ipc.registerForIPC("movewindowv2", this);
m_ipc.registerForIPC("urgent", this);
m_ipc.registerForIPC("configreloaded", this);
if (windowRewriteConfigUsesTitle()) {
spdlog::info(
"Registering for Hyprland's 'windowtitle' events because a user-defined window "
"Registering for Hyprland's 'windowtitlev2' events because a user-defined window "
"rewrite rule uses the 'title' field.");
m_ipc.registerForIPC("windowtitle", this);
m_ipc.registerForIPC("windowtitlev2", this);
}
}
void Workspaces::removeWorkspacesToRemove() {
for (const auto &workspaceName : m_workspacesToRemove) {
removeWorkspace(workspaceName);
for (const auto &workspaceString : m_workspacesToRemove) {
removeWorkspace(workspaceString);
}
m_workspacesToRemove.clear();
}
void Workspaces::removeWorkspace(std::string const &name) {
spdlog::debug("Removing workspace {}", name);
auto workspace =
std::find_if(m_workspaces.begin(), m_workspaces.end(), [&](std::unique_ptr<Workspace> &x) {
return (name.starts_with("special:") && name.substr(8) == x->name()) || name == x->name();
});
void Workspaces::removeWorkspace(std::string const &workspaceString) {
spdlog::debug("Removing workspace {}", workspaceString);
// If this succeeds, we have a workspace ID.
const auto workspaceId = parseWorkspaceId(workspaceString);
std::string name;
// TODO: At some point we want to support all workspace selectors
// This is just a subset.
// https://wiki.hyprland.org/Configuring/Workspace-Rules/#workspace-selectors
if (workspaceString.starts_with("special:")) {
name = workspaceString.substr(8);
} else if (workspaceString.starts_with("name:")) {
name = workspaceString.substr(5);
} else {
name = workspaceString;
}
const auto workspace = std::ranges::find_if(m_workspaces, [&](std::unique_ptr<Workspace> &x) {
if (workspaceId.has_value()) {
return *workspaceId == x->id();
}
return name == x->name();
});
if (workspace == m_workspaces.end()) {
// happens when a workspace on another monitor is destroyed
@@ -704,7 +747,8 @@ void Workspaces::removeWorkspace(std::string const &name) {
}
if ((*workspace)->isPersistentConfig()) {
spdlog::trace("Not removing config persistent workspace {}", name);
spdlog::trace("Not removing config persistent workspace id={} name={}", (*workspace)->id(),
(*workspace)->name());
return;
}
@@ -716,9 +760,9 @@ void Workspaces::setCurrentMonitorId() {
// get monitor ID from name (used by persistent workspaces)
m_monitorId = 0;
auto monitors = m_ipc.getSocket1JsonReply("monitors");
auto currentMonitor = std::find_if(
monitors.begin(), monitors.end(),
[this](const Json::Value &m) { return m["name"].asString() == m_bar.output->name; });
auto currentMonitor = std::ranges::find_if(monitors, [this](const Json::Value &m) {
return m["name"].asString() == m_bar.output->name;
});
if (currentMonitor == monitors.end()) {
spdlog::error("Monitor '{}' does not have an ID? Using 0", m_bar.output->name);
} else {
@@ -727,63 +771,101 @@ void Workspaces::setCurrentMonitorId() {
}
}
void Workspaces::sortSpecialCentered() {
std::vector<std::unique_ptr<Workspace>> specialWorkspaces;
std::vector<std::unique_ptr<Workspace>> hiddenWorkspaces;
std::vector<std::unique_ptr<Workspace>> normalWorkspaces;
for (auto &workspace : m_workspaces) {
if (workspace->isSpecial()) {
specialWorkspaces.push_back(std::move(workspace));
} else {
if (workspace->button().is_visible()) {
normalWorkspaces.push_back(std::move(workspace));
} else {
hiddenWorkspaces.push_back(std::move(workspace));
}
}
}
m_workspaces.clear();
size_t center = normalWorkspaces.size() / 2;
m_workspaces.insert(m_workspaces.end(), std::make_move_iterator(normalWorkspaces.begin()),
std::make_move_iterator(normalWorkspaces.begin() + center));
m_workspaces.insert(m_workspaces.end(), std::make_move_iterator(specialWorkspaces.begin()),
std::make_move_iterator(specialWorkspaces.end()));
m_workspaces.insert(m_workspaces.end(),
std::make_move_iterator(normalWorkspaces.begin() + center),
std::make_move_iterator(normalWorkspaces.end()));
m_workspaces.insert(m_workspaces.end(), std::make_move_iterator(hiddenWorkspaces.begin()),
std::make_move_iterator(hiddenWorkspaces.end()));
}
void Workspaces::sortWorkspaces() {
std::sort(m_workspaces.begin(), m_workspaces.end(),
[&](std::unique_ptr<Workspace> &a, std::unique_ptr<Workspace> &b) {
// Helper comparisons
auto isIdLess = a->id() < b->id();
auto isNameLess = a->name() < b->name();
std::ranges::sort( //
m_workspaces, [&](std::unique_ptr<Workspace> &a, std::unique_ptr<Workspace> &b) {
// Helper comparisons
auto isIdLess = a->id() < b->id();
auto isNameLess = a->name() < b->name();
switch (m_sortBy) {
case SortMethod::ID:
return isIdLess;
case SortMethod::NAME:
return isNameLess;
case SortMethod::NUMBER:
try {
return std::stoi(a->name()) < std::stoi(b->name());
} catch (const std::invalid_argument &) {
// Handle the exception if necessary.
break;
}
case SortMethod::DEFAULT:
default:
// Handle the default case here.
// normal -> named persistent -> named -> special -> named special
switch (m_sortBy) {
case SortMethod::ID:
return isIdLess;
case SortMethod::NAME:
return isNameLess;
case SortMethod::NUMBER:
try {
return std::stoi(a->name()) < std::stoi(b->name());
} catch (const std::invalid_argument &) {
// Handle the exception if necessary.
break;
}
case SortMethod::DEFAULT:
default:
// Handle the default case here.
// normal -> named persistent -> named -> special -> named special
// both normal (includes numbered persistent) => sort by ID
if (a->id() > 0 && b->id() > 0) {
return isIdLess;
}
// both normal (includes numbered persistent) => sort by ID
if (a->id() > 0 && b->id() > 0) {
return isIdLess;
}
// one normal, one special => normal first
if ((a->isSpecial()) ^ (b->isSpecial())) {
return b->isSpecial();
}
// one normal, one special => normal first
if ((a->isSpecial()) ^ (b->isSpecial())) {
return b->isSpecial();
}
// only one normal, one named
if ((a->id() > 0) ^ (b->id() > 0)) {
return a->id() > 0;
}
// only one normal, one named
if ((a->id() > 0) ^ (b->id() > 0)) {
return a->id() > 0;
}
// both special
if (a->isSpecial() && b->isSpecial()) {
// if one is -99 => put it last
if (a->id() == -99 || b->id() == -99) {
return b->id() == -99;
}
// both are 0 (not yet named persistents) / named specials (-98 <= ID <= -1)
return isNameLess;
}
// sort non-special named workspaces by name (ID <= -1377)
return isNameLess;
break;
// both special
if (a->isSpecial() && b->isSpecial()) {
// if one is -99 => put it last
if (a->id() == -99 || b->id() == -99) {
return b->id() == -99;
}
// both are 0 (not yet named persistents) / named specials
// (-98 <= ID <= -1)
return isNameLess;
}
// Return a default value if none of the cases match.
return isNameLess; // You can adjust this to your specific needs.
});
// sort non-special named workspaces by name (ID <= -1377)
return isNameLess;
break;
}
// Return a default value if none of the cases match.
return isNameLess; // You can adjust this to your specific needs.
});
if (m_sortBy == SortMethod::SPECIAL_CENTERED) {
this->sortSpecialCentered();
}
for (size_t i = 0; i < m_workspaces.size(); ++i) {
m_box.reorder_child(m_workspaces[i]->button(), i);
@@ -801,9 +883,9 @@ void Workspaces::setUrgentWorkspace(std::string const &windowaddress) {
}
}
auto workspace =
std::find_if(m_workspaces.begin(), m_workspaces.end(),
[workspaceId](std::unique_ptr<Workspace> &x) { return x->id() == workspaceId; });
auto workspace = std::ranges::find_if(m_workspaces, [workspaceId](std::unique_ptr<Workspace> &x) {
return x->id() == workspaceId;
});
if (workspace != m_workspaces.end()) {
workspace->get()->setUrgent();
}
@@ -817,11 +899,10 @@ auto Workspaces::update() -> void {
void Workspaces::updateWindowCount() {
const Json::Value workspacesJson = m_ipc.getSocket1JsonReply("workspaces");
for (auto &workspace : m_workspaces) {
auto workspaceJson =
std::find_if(workspacesJson.begin(), workspacesJson.end(), [&](Json::Value const &x) {
return x["name"].asString() == workspace->name() ||
(workspace->isSpecial() && x["name"].asString() == "special:" + workspace->name());
});
auto workspaceJson = std::ranges::find_if(workspacesJson, [&](Json::Value const &x) {
return x["name"].asString() == workspace->name() ||
(workspace->isSpecial() && x["name"].asString() == "special:" + workspace->name());
});
uint32_t count = 0;
if (workspaceJson != workspacesJson.end()) {
try {
@@ -861,26 +942,26 @@ bool Workspaces::updateWindowsToCreate() {
}
void Workspaces::updateWorkspaceStates() {
const std::vector<std::string> visibleWorkspaces = getVisibleWorkspaces();
const std::vector<int> visibleWorkspaces = getVisibleWorkspaces();
auto updatedWorkspaces = m_ipc.getSocket1JsonReply("workspaces");
for (auto &workspace : m_workspaces) {
workspace->setActive(workspace->name() == m_activeWorkspaceName ||
workspace->name() == m_activeSpecialWorkspaceName);
workspace->setActive(
workspace->id() == m_activeWorkspaceId ||
(workspace->isSpecial() && workspace->name() == m_activeSpecialWorkspaceName));
if (workspace->isActive() && workspace->isUrgent()) {
workspace->setUrgent(false);
}
workspace->setVisible(std::find(visibleWorkspaces.begin(), visibleWorkspaces.end(),
workspace->name()) != visibleWorkspaces.end());
workspace->setVisible(std::ranges::find(visibleWorkspaces, workspace->id()) !=
visibleWorkspaces.end());
std::string &workspaceIcon = m_iconsMap[""];
if (m_withIcon) {
workspaceIcon = workspace->selectIcon(m_iconsMap);
}
auto updatedWorkspace = std::find_if(
updatedWorkspaces.begin(), updatedWorkspaces.end(), [&workspace](const auto &w) {
auto wNameRaw = w["name"].asString();
auto wName = wNameRaw.starts_with("special:") ? wNameRaw.substr(8) : wNameRaw;
return wName == workspace->name();
});
auto updatedWorkspace = std::ranges::find_if(updatedWorkspaces, [&workspace](const auto &w) {
auto wNameRaw = w["name"].asString();
auto wName = wNameRaw.starts_with("special:") ? wNameRaw.substr(8) : wNameRaw;
return wName == workspace->name();
});
if (updatedWorkspace != updatedWorkspaces.end()) {
workspace->setOutput((*updatedWorkspace)["monitor"].asString());
}
@@ -908,4 +989,39 @@ int Workspaces::windowRewritePriorityFunction(std::string const &window_rule) {
return 0;
}
template <typename... Args>
std::string Workspaces::makePayload(Args const &...args) {
std::ostringstream result;
bool first = true;
((result << (first ? "" : ",") << args, first = false), ...);
return result.str();
}
std::pair<std::string, std::string> Workspaces::splitDoublePayload(std::string const &payload) {
const std::string part1 = payload.substr(0, payload.find(','));
const std::string part2 = payload.substr(part1.size() + 1);
return {part1, part2};
}
std::tuple<std::string, std::string, std::string> Workspaces::splitTriplePayload(
std::string const &payload) {
const size_t firstComma = payload.find(',');
const size_t secondComma = payload.find(',', firstComma + 1);
const std::string part1 = payload.substr(0, firstComma);
const std::string part2 = payload.substr(firstComma + 1, secondComma - (firstComma + 1));
const std::string part3 = payload.substr(secondComma + 1);
return {part1, part2, part3};
}
std::optional<int> Workspaces::parseWorkspaceId(std::string const &workspaceIdStr) {
try {
return workspaceIdStr == "special" ? -99 : std::stoi(workspaceIdStr);
} catch (std::exception const &e) {
spdlog::error("Failed to parse workspace ID: {}", e.what());
return std::nullopt;
}
}
} // namespace waybar::modules::hyprland

View File

@@ -60,6 +60,7 @@ auto waybar::modules::Memory::update() -> void {
fmt::arg("icon", getIcon(used_ram_percentage, icons)),
fmt::arg("total", total_ram_gigabytes), fmt::arg("swapTotal", total_swap_gigabytes),
fmt::arg("percentage", used_ram_percentage),
fmt::arg("swapState", swaptotal == 0 ? "Off" : "On"),
fmt::arg("swapPercentage", used_swap_percentage), fmt::arg("used", used_ram_gigabytes),
fmt::arg("swapUsed", used_swap_gigabytes), fmt::arg("avail", available_ram_gigabytes),
fmt::arg("swapAvail", available_swap_gigabytes)));
@@ -72,6 +73,7 @@ auto waybar::modules::Memory::update() -> void {
fmt::runtime(tooltip_format), used_ram_percentage,
fmt::arg("total", total_ram_gigabytes), fmt::arg("swapTotal", total_swap_gigabytes),
fmt::arg("percentage", used_ram_percentage),
fmt::arg("swapState", swaptotal == 0 ? "Off" : "On"),
fmt::arg("swapPercentage", used_swap_percentage), fmt::arg("used", used_ram_gigabytes),
fmt::arg("swapUsed", used_swap_gigabytes), fmt::arg("avail", available_ram_gigabytes),
fmt::arg("swapAvail", available_swap_gigabytes)));

View File

@@ -14,7 +14,6 @@ extern "C" {
#include <glib.h>
#include <spdlog/spdlog.h>
namespace waybar::modules::mpris {
const std::string DEFAULT_FORMAT = "{player} ({status}): {dynamic}";
@@ -425,9 +424,11 @@ auto Mpris::onPlayerNameVanished(PlayerctlPlayerManager* manager, PlayerctlPlaye
auto* mpris = static_cast<Mpris*>(data);
if (!mpris) return;
spdlog::debug("mpris: player-vanished callback: {}", player_name->name);
spdlog::debug("mpris: name-vanished callback: {}", player_name->name);
if (std::string(player_name->name) == mpris->player_) {
if (mpris->player_ == "playerctld") {
mpris->dp.emit();
} else if (mpris->player_ == player_name->name) {
mpris->player = nullptr;
mpris->event_box_.set_visible(false);
mpris->dp.emit();
@@ -498,7 +499,10 @@ auto Mpris::getPlayerInfo() -> std::optional<PlayerInfo> {
// > get the list of players [..] in order of activity
// https://github.com/altdesktop/playerctl/blob/b19a71cb9dba635df68d271bd2b3f6a99336a223/playerctl/playerctl-common.c#L248-L249
players = g_list_first(players);
if (players) player_name = static_cast<PlayerctlPlayerName*>(players->data)->name;
if (players)
player_name = static_cast<PlayerctlPlayerName*>(players->data)->name;
else
return std::nullopt; // no players found, hide the widget
}
if (std::any_of(ignored_players_.begin(), ignored_players_.end(),
@@ -584,38 +588,45 @@ errorexit:
}
bool Mpris::handleToggle(GdkEventButton* const& e) {
if (!e || e->type != GdkEventType::GDK_BUTTON_PRESS) {
return false;
}
auto info = getPlayerInfo();
if (!info) return false;
struct ButtonAction {
guint button;
const char* config_key;
std::function<void()> builtin_action;
};
GError* error = nullptr;
waybar::util::ScopeGuard error_deleter([error]() {
waybar::util::ScopeGuard error_deleter([&error]() {
if (error) {
g_error_free(error);
}
});
auto info = getPlayerInfo();
if (!info) return false;
// Command pattern: encapsulate each button's action
const ButtonAction actions[] = {
{1, "on-click", [&]() { playerctl_player_play_pause(player, &error); }},
{2, "on-click-middle", [&]() { playerctl_player_previous(player, &error); }},
{3, "on-click-right", [&]() { playerctl_player_next(player, &error); }},
{8, "on-click-backward", [&]() { playerctl_player_previous(player, &error); }},
{9, "on-click-forward", [&]() { playerctl_player_next(player, &error); }},
};
if (e->type == GdkEventType::GDK_BUTTON_PRESS) {
switch (e->button) {
case 1: // left-click
if (config_["on-click"].isString()) {
return ALabel::handleToggle(e);
}
playerctl_player_play_pause(player, &error);
break;
case 2: // middle-click
if (config_["on-click-middle"].isString()) {
return ALabel::handleToggle(e);
}
playerctl_player_previous(player, &error);
break;
case 3: // right-click
if (config_["on-click-right"].isString()) {
return ALabel::handleToggle(e);
}
playerctl_player_next(player, &error);
break;
for (const auto& action : actions) {
if (e->button == action.button) {
if (config_[action.config_key].isString()) {
return ALabel::handleToggle(e);
}
action.builtin_action();
break;
}
}
if (error) {
spdlog::error("mpris[{}]: error running builtin on-click action: {}", (*info).name,
error->message);

View File

@@ -80,6 +80,7 @@ waybar::modules::Network::readBandwidthUsage() {
waybar::modules::Network::Network(const std::string &id, const Json::Value &config)
: ALabel(config, "network", id, DEFAULT_FORMAT, 60),
ifid_(-1),
addr_pref_(IPV4),
efd_(-1),
ev_fd_(-1),
want_route_dump_(false),
@@ -88,6 +89,7 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf
dump_in_progress_(false),
is_p2p_(false),
cidr_(0),
cidr6_(0),
signal_strength_dbm_(0),
signal_strength_(0),
#ifdef WANT_RFKILL
@@ -101,6 +103,12 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf
// the module start with no text, but the event_box_ is shown.
label_.set_markup("<s></s>");
if (config_["family"] == "ipv6") {
addr_pref_ = IPV6;
} else if (config["family"] == "ipv4_6") {
addr_pref_ = IPV4_6;
}
auto bandwidth = readBandwidthUsage();
if (bandwidth.has_value()) {
bandwidth_down_total_ = (*bandwidth).first;
@@ -215,8 +223,8 @@ void waybar::modules::Network::worker() {
std::lock_guard<std::mutex> lock(mutex_);
if (ifid_ > 0) {
getInfo();
dp.emit();
}
dp.emit();
}
thread_timer_.sleep_for(interval_);
};
@@ -263,14 +271,14 @@ void waybar::modules::Network::worker() {
}
const std::string waybar::modules::Network::getNetworkState() const {
if (ifid_ == -1) {
#ifdef WANT_RFKILL
if (rfkill_.getState()) return "disabled";
if (rfkill_.getState()) return "disabled";
#endif
if (ifid_ == -1) {
return "disconnected";
}
if (!carrier_) return "disconnected";
if (ipaddr_.empty()) return "linked";
if (ipaddr_.empty() && ipaddr6_.empty()) return "linked";
if (essid_.empty()) return "ethernet";
return "wifi";
}
@@ -316,12 +324,24 @@ auto waybar::modules::Network::update() -> void {
}
getState(signal_strength_);
std::string final_ipaddr_;
if (addr_pref_ == ip_addr_pref::IPV4) {
final_ipaddr_ = ipaddr_;
} else if (addr_pref_ == ip_addr_pref::IPV6) {
final_ipaddr_ = ipaddr6_;
} else if (addr_pref_ == ip_addr_pref::IPV4_6) {
final_ipaddr_ = ipaddr_;
final_ipaddr_ += '\n';
final_ipaddr_ += ipaddr6_;
}
auto text = fmt::format(
fmt::runtime(format_), fmt::arg("essid", essid_), fmt::arg("bssid", bssid_),
fmt::arg("signaldBm", signal_strength_dbm_), fmt::arg("signalStrength", signal_strength_),
fmt::arg("signalStrengthApp", signal_strength_app_), fmt::arg("ifname", ifname_),
fmt::arg("netmask", netmask_), fmt::arg("ipaddr", ipaddr_), fmt::arg("gwaddr", gwaddr_),
fmt::arg("cidr", cidr_), fmt::arg("frequency", fmt::format("{:.1f}", frequency_)),
fmt::arg("netmask", netmask_), fmt::arg("netmask6", netmask6_),
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("icon", getIcon(signal_strength_, state_)),
fmt::arg("bandwidthDownBits", pow_format(bandwidth_down * 8ull / interval_.count(), "b/s")),
fmt::arg("bandwidthUpBits", pow_format(bandwidth_up * 8ull / interval_.count(), "b/s")),
@@ -352,8 +372,9 @@ auto waybar::modules::Network::update() -> void {
fmt::runtime(tooltip_format), fmt::arg("essid", essid_), fmt::arg("bssid", bssid_),
fmt::arg("signaldBm", signal_strength_dbm_), fmt::arg("signalStrength", signal_strength_),
fmt::arg("signalStrengthApp", signal_strength_app_), fmt::arg("ifname", ifname_),
fmt::arg("netmask", netmask_), fmt::arg("ipaddr", ipaddr_), fmt::arg("gwaddr", gwaddr_),
fmt::arg("cidr", cidr_), fmt::arg("frequency", fmt::format("{:.1f}", frequency_)),
fmt::arg("netmask", netmask_), fmt::arg("netmask6", netmask6_),
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("icon", getIcon(signal_strength_, state_)),
fmt::arg("bandwidthDownBits",
pow_format(bandwidth_down * 8ull / interval_.count(), "b/s")),
@@ -394,10 +415,13 @@ void waybar::modules::Network::clearIface() {
essid_.clear();
bssid_.clear();
ipaddr_.clear();
ipaddr6_.clear();
gwaddr_.clear();
netmask_.clear();
netmask6_.clear();
carrier_ = false;
cidr_ = 0;
cidr6_ = 0;
signal_strength_dbm_ = 0;
signal_strength_ = 0;
signal_strength_app_.clear();
@@ -521,7 +545,6 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
if (ifa->ifa_scope >= RT_SCOPE_LINK) {
return NL_OK;
}
for (; RTA_OK(ifa_rta, attrlen); ifa_rta = RTA_NEXT(ifa_rta, attrlen)) {
switch (ifa_rta->rta_type) {
case IFA_ADDRESS:
@@ -529,8 +552,20 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
case IFA_LOCAL:
char ipaddr[INET6_ADDRSTRLEN];
if (!is_del_event) {
net->ipaddr_ = inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta), ipaddr, sizeof(ipaddr));
net->cidr_ = ifa->ifa_prefixlen;
if ((net->addr_pref_ == ip_addr_pref::IPV4 ||
net->addr_pref_ == ip_addr_pref::IPV4_6) &&
net->cidr_ == 0 && ifa->ifa_family == AF_INET) {
net->ipaddr_ =
inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta), ipaddr, sizeof(ipaddr));
net->cidr_ = ifa->ifa_prefixlen;
} else if ((net->addr_pref_ == ip_addr_pref::IPV6 ||
net->addr_pref_ == ip_addr_pref::IPV4_6) &&
net->cidr6_ == 0 && ifa->ifa_family == AF_INET6) {
net->ipaddr6_ =
inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta), ipaddr, sizeof(ipaddr));
net->cidr6_ = ifa->ifa_prefixlen;
}
switch (ifa->ifa_family) {
case AF_INET: {
struct in_addr netmask;
@@ -538,21 +573,24 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
net->netmask_ = inet_ntop(ifa->ifa_family, &netmask, ipaddr, sizeof(ipaddr));
}
case AF_INET6: {
struct in6_addr netmask;
struct in6_addr netmask6;
for (int i = 0; i < 16; i++) {
int v = (i + 1) * 8 - ifa->ifa_prefixlen;
if (v < 0) v = 0;
if (v > 8) v = 8;
netmask.s6_addr[i] = ~0 << v;
netmask6.s6_addr[i] = ~0 << v;
}
net->netmask_ = inet_ntop(ifa->ifa_family, &netmask, 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_);
} else {
net->ipaddr_.clear();
net->ipaddr6_.clear();
net->cidr_ = 0;
net->cidr6_ = 0;
net->netmask_.clear();
net->netmask6_.clear();
spdlog::debug("network: {} addr deleted {}/{}", net->ifname_,
inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta), ipaddr, sizeof(ipaddr)),
ifa->ifa_prefixlen);

View File

@@ -147,6 +147,17 @@ void IPC::parseIPC(const std::string &line) {
} else {
spdlog::error("Active window changed on unknown workspace");
}
} else if (const auto &payload = ev["WorkspaceUrgencyChanged"]) {
const auto id = payload["id"].asUInt64();
const auto urgent = payload["urgent"].asBool();
auto it = std::find_if(workspaces_.begin(), workspaces_.end(),
[id](const auto &ws) { return ws["id"].asUInt64() == id; });
if (it != workspaces_.end()) {
auto &ws = *it;
ws["is_urgent"] = urgent;
} else {
spdlog::error("Urgency changed for unknown workspace");
}
} else if (const auto &payload = ev["KeyboardLayoutsChanged"]) {
const auto &layouts = payload["keyboard_layouts"];
const auto &names = layouts["names"];

View File

@@ -20,6 +20,7 @@ Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value
gIPC->registerForIPC("WorkspacesChanged", this);
gIPC->registerForIPC("WorkspaceActivated", this);
gIPC->registerForIPC("WorkspaceActiveWindowChanged", this);
gIPC->registerForIPC("WorkspaceUrgencyChanged", this);
dp.emit();
}
@@ -67,6 +68,11 @@ void Workspaces::doUpdate() {
else
style_context->remove_class("active");
if (ws["is_urgent"].asBool())
style_context->add_class("urgent");
else
style_context->remove_class("urgent");
if (ws["output"]) {
if (ws["output"].asString() == bar_.output->name)
style_context->add_class("current_output");
@@ -166,6 +172,10 @@ std::string Workspaces::getIcon(const std::string &value, const Json::Value &ws)
const auto &icons = config_["format-icons"];
if (!icons) return value;
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_focused"].asBool() && icons["focused"]) return icons["focused"].asString();
if (ws["is_active"].asBool() && icons["active"]) return icons["active"].asString();

View File

@@ -29,14 +29,14 @@ PowerProfilesDaemon::PowerProfilesDaemon(const std::string& id, const Json::Valu
// method on the proxy to see whether or not something's responding
// on the other side.
// NOTE: the DBus adresses are under migration. They should be
// NOTE: the DBus addresses are under migration. They should be
// changed to org.freedesktop.UPower.PowerProfiles at some point.
//
// See
// https://gitlab.freedesktop.org/upower/power-profiles-daemon/-/releases/0.20
//
// The old name is still announced for now. Let's rather use the old
// adresses for compatibility sake.
// addresses for compatibility sake.
//
// Revisit this in 2026, systems should be updated by then.

View File

@@ -15,13 +15,14 @@ using util::PipewireBackend::PRIVACY_NODE_TYPE_AUDIO_OUTPUT;
using util::PipewireBackend::PRIVACY_NODE_TYPE_NONE;
using util::PipewireBackend::PRIVACY_NODE_TYPE_VIDEO_INPUT;
Privacy::Privacy(const std::string& id, const Json::Value& config, const std::string& pos)
Privacy::Privacy(const std::string& id, const Json::Value& config, Gtk::Orientation orientation,
const std::string& pos)
: AModule(config, "privacy", id),
nodes_screenshare(),
nodes_audio_in(),
nodes_audio_out(),
visibility_conn(),
box_(Gtk::ORIENTATION_HORIZONTAL, 0) {
box_(orientation, 0) {
box_.set_name(name_);
event_box_.add(box_);
@@ -67,12 +68,30 @@ Privacy::Privacy(const std::string& id, const Json::Value& config, const std::st
auto iter = typeMap.find(type);
if (iter != typeMap.end()) {
auto& [nodePtr, nodeType] = iter->second;
auto* item = Gtk::make_managed<PrivacyItem>(module, nodeType, nodePtr, pos, iconSize,
transition_duration);
auto* item = Gtk::make_managed<PrivacyItem>(module, nodeType, nodePtr, orientation, pos,
iconSize, transition_duration);
box_.add(*item);
}
}
for (const auto& ignore_item : config_["ignore"]) {
if (!ignore_item.isObject() || !ignore_item["type"].isString() ||
!ignore_item["name"].isString())
continue;
const std::string type = ignore_item["type"].asString();
const std::string name = ignore_item["name"].asString();
auto iter = typeMap.find(type);
if (iter != typeMap.end()) {
auto& [_, nodeType] = iter->second;
ignore.emplace(nodeType, std::move(name));
}
}
if (config_["ignore-monitor"].isBool()) {
ignore_monitor = config_["ignore-monitor"].asBool();
}
backend = util::PipewireBackend::PipewireBackend::getInstance();
backend->privacy_nodes_changed_signal_event.connect(
sigc::mem_fun(*this, &Privacy::onPrivacyNodesChanged));
@@ -87,6 +106,11 @@ void Privacy::onPrivacyNodesChanged() {
nodes_screenshare.clear();
for (auto& node : backend->privacy_nodes) {
if (ignore_monitor && node.second->is_monitor) continue;
auto iter = ignore.find(std::pair(node.second->type, node.second->node_name));
if (iter != ignore.end()) continue;
switch (node.second->state) {
case PW_NODE_STATE_RUNNING:
switch (node.second->type) {

View File

@@ -1,5 +1,7 @@
#include "modules/privacy/privacy_item.hpp"
#include <spdlog/spdlog.h>
#include <string>
#include "glibmm/main.h"
@@ -11,8 +13,9 @@
namespace waybar::modules::privacy {
PrivacyItem::PrivacyItem(const Json::Value &config_, enum PrivacyNodeType privacy_type_,
std::list<PrivacyNodeInfo *> *nodes_, const std::string &pos,
const uint icon_size, const uint transition_duration)
std::list<PrivacyNodeInfo *> *nodes_, Gtk::Orientation orientation,
const std::string &pos, const uint icon_size,
const uint transition_duration)
: Gtk::Revealer(),
privacy_type(privacy_type_),
nodes(nodes_),
@@ -40,16 +43,24 @@ PrivacyItem::PrivacyItem(const Json::Value &config_, enum PrivacyNodeType privac
// Set the reveal transition to not look weird when sliding in
if (pos == "modules-left") {
set_transition_type(Gtk::REVEALER_TRANSITION_TYPE_SLIDE_RIGHT);
set_transition_type(orientation == Gtk::ORIENTATION_HORIZONTAL
? Gtk::REVEALER_TRANSITION_TYPE_SLIDE_RIGHT
: Gtk::REVEALER_TRANSITION_TYPE_SLIDE_DOWN);
} else if (pos == "modules-center") {
set_transition_type(Gtk::REVEALER_TRANSITION_TYPE_CROSSFADE);
} else if (pos == "modules-right") {
set_transition_type(Gtk::REVEALER_TRANSITION_TYPE_SLIDE_LEFT);
set_transition_type(orientation == Gtk::ORIENTATION_HORIZONTAL
? Gtk::REVEALER_TRANSITION_TYPE_SLIDE_LEFT
: Gtk::REVEALER_TRANSITION_TYPE_SLIDE_UP);
}
set_transition_duration(transition_duration);
box_.set_name("privacy-item");
box_.add(icon_);
// We use `set_center_widget` instead of `add` to make sure the icon is
// centered even if the orientation is vertical
box_.set_center_widget(icon_);
icon_.set_pixel_size(icon_size);
add(box_);
@@ -87,20 +98,22 @@ PrivacyItem::PrivacyItem(const Json::Value &config_, enum PrivacyNodeType privac
void PrivacyItem::update_tooltip() {
// Removes all old nodes
for (auto *child : tooltip_window.get_children()) {
tooltip_window.remove(*child);
// despite the remove, still needs a delete to prevent memory leak. Speculating that this might
// work differently in GTK4.
delete child;
}
for (auto *node : *nodes) {
Gtk::Box *box = new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 4);
auto *box = Gtk::make_managed<Gtk::Box>(Gtk::ORIENTATION_HORIZONTAL, 4);
// Set device icon
Gtk::Image *node_icon = new Gtk::Image();
auto *node_icon = Gtk::make_managed<Gtk::Image>();
node_icon->set_pixel_size(tooltipIconSize);
node_icon->set_from_icon_name(node->getIconName(), Gtk::ICON_SIZE_INVALID);
box->add(*node_icon);
// Set model
auto *nodeName = new Gtk::Label(node->getName());
auto *nodeName = Gtk::make_managed<Gtk::Label>(node->getName());
box->add(*nodeName);
tooltip_window.add(*box);

View File

@@ -5,10 +5,12 @@
#include <gtkmm/tooltip.h>
#include <spdlog/spdlog.h>
#include <filesystem>
#include <fstream>
#include <map>
#include "gdk/gdk.h"
#include "modules/sni/icon_manager.hpp"
#include "util/format.hpp"
#include "util/gtk_icon.hpp"
@@ -138,6 +140,7 @@ void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) {
category = get_variant<std::string>(value);
} else if (name == "Id") {
id = get_variant<std::string>(value);
setCustomIcon(id);
} else if (name == "Title") {
title = get_variant<std::string>(value);
if (tooltip.text.empty()) {
@@ -199,6 +202,19 @@ void Item::setStatus(const Glib::ustring& value) {
style->add_class(lower);
}
void Item::setCustomIcon(const std::string& id) {
std::string custom_icon = IconManager::instance().getIconForApp(id);
if (!custom_icon.empty()) {
if (std::filesystem::exists(custom_icon)) {
Glib::RefPtr<Gdk::Pixbuf> custom_pixbuf = Gdk::Pixbuf::create_from_file(custom_icon);
icon_name = ""; // icon_name has priority over pixmap
icon_pixmap = custom_pixbuf;
} else { // if file doesn't exist it's most likely an icon_name
icon_name = custom_icon;
}
}
}
void Item::getUpdatedProperties() {
auto params = Glib::VariantContainerBase::create_tuple(
{Glib::Variant<Glib::ustring>::create(SNI_INTERFACE_NAME)});
@@ -372,14 +388,17 @@ Glib::RefPtr<Gdk::Pixbuf> Item::getIconPixbuf() {
Glib::RefPtr<Gdk::Pixbuf> Item::getIconByName(const std::string& name, int request_size) {
icon_theme->rescan_if_needed();
if (!icon_theme_path.empty() &&
icon_theme->lookup_icon(name.c_str(), request_size,
Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE)) {
return icon_theme->load_icon(name.c_str(), request_size,
Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE);
if (!icon_theme_path.empty()) {
auto icon_info = icon_theme->lookup_icon(name.c_str(), request_size,
Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE);
if (icon_info) {
bool is_sym = false;
return icon_info.load_symbolic(event_box.get_style_context(), is_sym);
}
}
return DefaultGtkIconThemeWrapper::load_icon(name.c_str(), request_size,
Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE);
Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE,
event_box.get_style_context());
}
double Item::getScaledIconSize() {

View File

@@ -2,6 +2,8 @@
#include <spdlog/spdlog.h>
#include "modules/sni/icon_manager.hpp"
namespace waybar::modules::SNI {
Tray::Tray(const std::string& id, const Bar& bar, const Json::Value& config)
@@ -20,6 +22,9 @@ Tray::Tray(const std::string& id, const Bar& bar, const Json::Value& config)
box_.set_spacing(config_["spacing"].asUInt());
}
nb_hosts_ += 1;
if (config_["icons"].isObject()) {
IconManager::instance().setIconsConfig(config_["icons"]);
}
dp.emit();
}

View File

@@ -22,10 +22,10 @@ Language::Language(const std::string& id, const Json::Value& config)
hide_single_ = config["hide-single-layout"].isBool() && config["hide-single-layout"].asBool();
is_variant_displayed = format_.find("{variant}") != std::string::npos;
if (format_.find("{}") != std::string::npos || format_.find("{short}") != std::string::npos) {
displayed_short_flag |= static_cast<std::byte>(DispayedShortFlag::ShortName);
displayed_short_flag |= static_cast<std::byte>(DisplayedShortFlag::ShortName);
}
if (format_.find("{shortDescription}") != std::string::npos) {
displayed_short_flag |= static_cast<std::byte>(DispayedShortFlag::ShortDescription);
displayed_short_flag |= static_cast<std::byte>(DisplayedShortFlag::ShortDescription);
}
if (config.isMember("tooltip-format")) {
tooltip_format_ = config["tooltip-format"].asString();

View File

@@ -41,8 +41,8 @@ void Window::onCmd(const struct Ipc::ipc_response& res) {
std::lock_guard<std::mutex> lock(mutex_);
auto payload = parser_.parse(res.payload);
auto output = payload["output"].isString() ? payload["output"].asString() : "";
std::tie(app_nb_, floating_count_, windowId_, window_, app_id_, app_class_, shell_, layout_) =
getFocusedNode(payload["nodes"], output);
std::tie(app_nb_, floating_count_, windowId_, window_, app_id_, app_class_, shell_, layout_,
marks_) = getFocusedNode(payload["nodes"], output);
updateAppIconName(app_id_, app_class_);
dp.emit();
} catch (const std::exception& e) {
@@ -96,7 +96,7 @@ auto Window::update() -> void {
label_.set_markup(waybar::util::rewriteString(
fmt::format(fmt::runtime(format_), fmt::arg("title", window_), fmt::arg("app_id", app_id_),
fmt::arg("shell", shell_)),
fmt::arg("shell", shell_), fmt::arg("marks", marks_)),
config_["rewrite"]));
if (tooltipEnabled()) {
label_.set_tooltip_text(window_);
@@ -108,7 +108,7 @@ auto Window::update() -> void {
AAppIconLabel::update();
}
void Window::setClass(std::string classname, bool enable) {
void Window::setClass(const std::string& classname, bool enable) {
if (enable) {
if (!bar_.window.get_style_context()->has_class(classname)) {
bar_.window.get_style_context()->add_class(classname);
@@ -169,17 +169,31 @@ std::optional<std::reference_wrapper<const Json::Value>> getSingleChildNode(
return {getSingleChildNode(child)};
}
std::tuple<std::string, std::string, std::string> getWindowInfo(const Json::Value& node) {
std::tuple<std::string, std::string, std::string, std::string> getWindowInfo(
const Json::Value& node, bool showHidden) {
const auto app_id = node["app_id"].isString() ? node["app_id"].asString()
: node["window_properties"]["instance"].asString();
const auto app_class = node["window_properties"]["class"].isString()
? node["window_properties"]["class"].asString()
: "";
const auto shell = node["shell"].isString() ? node["shell"].asString() : "";
return {app_id, app_class, shell};
std::string marks = "";
if (node["marks"].isArray()) {
for (const auto& m : node["marks"]) {
if (!m.isString() || (!showHidden && m.asString().at(0) == '_')) {
continue;
}
if (!marks.empty()) {
marks += ',';
}
marks += m.asString();
}
}
return {app_id, app_class, shell, marks};
}
std::tuple<std::size_t, int, int, std::string, std::string, std::string, std::string, std::string>
std::tuple<std::size_t, int, int, std::string, std::string, std::string, std::string, std::string,
std::string>
gfnWithWorkspace(const Json::Value& nodes, std::string& output, const Json::Value& config_,
const Bar& bar_, Json::Value& parentWorkspace,
const Json::Value& immediateParent) {
@@ -207,7 +221,8 @@ gfnWithWorkspace(const Json::Value& nodes, std::string& output, const Json::Valu
"",
"",
"",
node["layout"].asString()};
node["layout"].asString(),
""};
}
parentWorkspace = node;
} else if ((node["type"].asString() == "con" || node["type"].asString() == "floating_con") &&
@@ -215,7 +230,8 @@ gfnWithWorkspace(const Json::Value& nodes, std::string& output, const Json::Valu
// found node
spdlog::trace("actual output {}, output found {}, node (focused) found {}", bar_.output->name,
output, node["name"].asString());
const auto [app_id, app_class, shell] = getWindowInfo(node);
const auto [app_id, app_class, shell, marks] =
getWindowInfo(node, config_["show-hidden-marks"].asBool());
int nb = node.size();
int floating_count = 0;
std::string workspace_layout = "";
@@ -232,20 +248,21 @@ gfnWithWorkspace(const Json::Value& nodes, std::string& output, const Json::Valu
app_id,
app_class,
shell,
workspace_layout};
workspace_layout,
marks};
}
// iterate
auto [nb, f, id, name, app_id, app_class, shell, workspace_layout] =
auto [nb, f, id, name, app_id, app_class, shell, workspace_layout, marks] =
gfnWithWorkspace(node["nodes"], output, config_, bar_, parentWorkspace, node);
auto [nb2, f2, id2, name2, app_id2, app_class2, shell2, workspace_layout2] =
auto [nb2, f2, id2, name2, app_id2, app_class2, shell2, workspace_layout2, marks2] =
gfnWithWorkspace(node["floating_nodes"], output, config_, bar_, parentWorkspace, node);
// if ((id > 0 || ((id2 < 0 || name2.empty()) && id > -1)) && !name.empty()) {
if ((id > 0) || (id2 < 0 && id > -1)) {
return {nb, f, id, name, app_id, app_class, shell, workspace_layout};
return {nb, f, id, name, app_id, app_class, shell, workspace_layout, marks};
} else if (id2 > 0 && !name2.empty()) {
return {nb2, f2, id2, name2, app_id2, app_class, shell2, workspace_layout2};
return {nb2, f2, id2, name2, app_id2, app_class, shell2, workspace_layout2, marks2};
}
}
@@ -258,10 +275,12 @@ gfnWithWorkspace(const Json::Value& nodes, std::string& output, const Json::Valu
std::string app_id = "";
std::string app_class = "";
std::string workspace_layout = "";
std::string marks = "";
if (all_leaf_nodes.first == 1) {
const auto single_child = getSingleChildNode(immediateParent);
if (single_child.has_value()) {
std::tie(app_id, app_class, workspace_layout) = getWindowInfo(single_child.value());
std::tie(app_id, app_class, workspace_layout, marks) =
getWindowInfo(single_child.value(), config_["show-hidden-marks"].asBool());
}
}
return {all_leaf_nodes.first,
@@ -273,13 +292,15 @@ gfnWithWorkspace(const Json::Value& nodes, std::string& output, const Json::Valu
app_id,
app_class,
workspace_layout,
immediateParent["layout"].asString()};
immediateParent["layout"].asString(),
marks};
}
return {0, 0, -1, "", "", "", "", ""};
return {0, 0, -1, "", "", "", "", "", ""};
}
std::tuple<std::size_t, int, int, std::string, std::string, std::string, std::string, std::string>
std::tuple<std::size_t, int, int, std::string, std::string, std::string, std::string, std::string,
std::string>
Window::getFocusedNode(const Json::Value& nodes, std::string& output) {
Json::Value placeholder = Json::Value::null;
return gfnWithWorkspace(nodes, output, config_, bar_, placeholder, placeholder);

View File

@@ -57,9 +57,9 @@ Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value
box_.get_style_context()->add_class(MODULE_CLASS);
event_box_.add(box_);
if (config_["format-window-separator"].isString()) {
m_formatWindowSeperator = config_["format-window-separator"].asString();
m_formatWindowSeparator = config_["format-window-separator"].asString();
} else {
m_formatWindowSeperator = " ";
m_formatWindowSeparator = " ";
}
const Json::Value &windowRewrite = config["window-rewrite"];
if (windowRewrite.isObject()) {
@@ -271,7 +271,7 @@ void Workspaces::updateWindows(const Json::Value &node, std::string &windows) {
window = fmt::format(fmt::runtime(window), fmt::arg("name", title),
fmt::arg("class", windowClass));
windows.append(window);
windows.append(m_formatWindowSeperator);
windows.append(m_formatWindowSeparator);
}
}
for (const Json::Value &child : node["nodes"]) {
@@ -340,7 +340,7 @@ auto Workspaces::update() -> void {
fmt::runtime(format), fmt::arg("icon", getIcon(output, *it)), fmt::arg("value", output),
fmt::arg("name", trimWorkspaceName(output)), fmt::arg("index", (*it)["num"].asString()),
fmt::arg("windows",
windows.substr(0, windows.length() - m_formatWindowSeperator.length())),
windows.substr(0, windows.length() - m_formatWindowSeparator.length())),
fmt::arg("output", (*it)["output"].asString()));
}
if (!config_["disable-markup"].asBool()) {
@@ -494,16 +494,34 @@ std::string Workspaces::trimWorkspaceName(std::string name) {
return name;
}
bool is_focused_recursive(const Json::Value &node) {
// If a workspace has a focused container then get_tree will say
// that the workspace itself isn't focused. Therefore we need to
// check if any of its nodes are focused as well.
// some layouts like tabbed have many nested nodes
// all nested nodes must be checked for focused flag
if (node["focused"].asBool()) {
return true;
}
for (const auto &child : node["nodes"]) {
if (is_focused_recursive(child)) {
return true;
}
}
for (const auto &child : node["floating_nodes"]) {
if (is_focused_recursive(child)) {
return true;
}
}
return false;
}
void Workspaces::onButtonReady(const Json::Value &node, Gtk::Button &button) {
if (config_["current-only"].asBool()) {
// If a workspace has a focused container then get_tree will say
// that the workspace itself isn't focused. Therefore we need to
// check if any of its nodes are focused as well.
bool focused = node["focused"].asBool() ||
std::any_of(node["nodes"].begin(), node["nodes"].end(),
[](const auto &child) { return child["focused"].asBool(); });
if (focused) {
if (is_focused_recursive(node)) {
button.show();
} else {
button.hide();

View File

@@ -16,6 +16,7 @@ SystemdFailedUnits::SystemdFailedUnits(const std::string& id, const Json::Value&
update_pending(false),
nr_failed_system(0),
nr_failed_user(0),
nr_failed(0),
last_status() {
if (config["hide-on-ok"].isBool()) {
hide_on_ok = config["hide-on-ok"].asBool();
@@ -67,11 +68,38 @@ auto SystemdFailedUnits::notify_cb(const Glib::ustring& sender_name,
}
}
void SystemdFailedUnits::updateData() {
update_pending = false;
void SystemdFailedUnits::RequestSystemState() {
auto load = [](const char* kind, Glib::RefPtr<Gio::DBus::Proxy>& proxy) -> std::string {
try {
if (!proxy) return "unknown";
auto parameters = Glib::VariantContainerBase(
g_variant_new("(ss)", "org.freedesktop.systemd1.Manager", "SystemState"));
Glib::VariantContainerBase data = proxy->call_sync("Get", parameters);
if (data && data.is_of_type(Glib::VariantType("(v)"))) {
Glib::VariantBase variant;
g_variant_get(data.gobj_copy(), "(v)", &variant);
if (variant && variant.is_of_type(Glib::VARIANT_TYPE_STRING)) {
return g_variant_get_string(variant.gobj_copy(), NULL);
}
}
} catch (Glib::Error& e) {
spdlog::error("Failed to get {} state: {}", kind, e.what().c_str());
}
return "unknown";
};
system_state = load("systemwide", system_proxy);
user_state = load("user", user_proxy);
if (system_state == "running" && user_state == "running")
overall_state = "ok";
else
overall_state = "degraded";
}
void SystemdFailedUnits::RequestFailedUnits() {
auto load = [](const char* kind, Glib::RefPtr<Gio::DBus::Proxy>& proxy) -> uint32_t {
try {
if (!proxy) return 0;
auto parameters = Glib::VariantContainerBase(
g_variant_new("(ss)", "org.freedesktop.systemd1.Manager", "NFailedUnits"));
Glib::VariantContainerBase data = proxy->call_sync("Get", parameters);
@@ -79,9 +107,7 @@ void SystemdFailedUnits::updateData() {
Glib::VariantBase variant;
g_variant_get(data.gobj_copy(), "(v)", &variant);
if (variant && variant.is_of_type(Glib::VARIANT_TYPE_UINT32)) {
uint32_t value = 0;
g_variant_get(variant.gobj_copy(), "u", &value);
return value;
return g_variant_get_uint32(variant.gobj_copy());
}
}
} catch (Glib::Error& e) {
@@ -90,40 +116,46 @@ void SystemdFailedUnits::updateData() {
return 0;
};
if (system_proxy) {
nr_failed_system = load("systemwide", system_proxy);
}
if (user_proxy) {
nr_failed_user = load("user", user_proxy);
}
nr_failed_system = load("systemwide", system_proxy);
nr_failed_user = load("user", user_proxy);
nr_failed = nr_failed_system + nr_failed_user;
}
void SystemdFailedUnits::updateData() {
update_pending = false;
RequestSystemState();
if (overall_state == "degraded") RequestFailedUnits();
dp.emit();
}
auto SystemdFailedUnits::update() -> void {
uint32_t nr_failed = nr_failed_system + nr_failed_user;
if (last_status == overall_state) return;
// Hide if needed.
if (nr_failed == 0 && hide_on_ok) {
if (overall_state == "ok" && hide_on_ok) {
event_box_.set_visible(false);
return;
}
if (!event_box_.get_visible()) {
event_box_.set_visible(true);
}
event_box_.set_visible(true);
// Set state class.
const std::string status = nr_failed == 0 ? "ok" : "degraded";
if (!last_status.empty() && label_.get_style_context()->has_class(last_status)) {
label_.get_style_context()->remove_class(last_status);
}
if (!label_.get_style_context()->has_class(status)) {
label_.get_style_context()->add_class(status);
if (!label_.get_style_context()->has_class(overall_state)) {
label_.get_style_context()->add_class(overall_state);
}
last_status = status;
last_status = overall_state;
label_.set_markup(fmt::format(
fmt::runtime(nr_failed == 0 ? format_ok : format_), fmt::arg("nr_failed", nr_failed),
fmt::arg("nr_failed_system", nr_failed_system), fmt::arg("nr_failed_user", nr_failed_user)));
fmt::arg("nr_failed_system", nr_failed_system), fmt::arg("nr_failed_user", nr_failed_user),
fmt::arg("system_state", system_state), fmt::arg("user_state", user_state),
fmt::arg("overall_state", overall_state)));
ALabel::update();
}

View File

@@ -45,7 +45,7 @@ waybar::modules::Temperature::Temperature(const std::string& id, const Json::Val
file_path_ = fmt::format("/sys/class/thermal/thermal_zone{}/temp", zone);
}
// check if file_path_ can be used to retrive the temperature
// check if file_path_ can be used to retrieve the temperature
std::ifstream temp(file_path_);
if (!temp.is_open()) {
throw std::runtime_error("Can't open " + file_path_);
@@ -114,13 +114,17 @@ float waybar::modules::Temperature::getTemperature() {
auto zone = config_["thermal-zone"].isInt() ? config_["thermal-zone"].asInt() : 0;
if (sysctlbyname(fmt::format("hw.acpi.thermal.tz{}.temperature", zone).c_str(), &temp, &size,
NULL, 0) != 0) {
throw std::runtime_error(fmt::format(
"sysctl hw.acpi.thermal.tz{}.temperature or dev.cpu.{}.temperature failed", zone, zone));
// First, try with dev.cpu
if ((sysctlbyname(fmt::format("dev.cpu.{}.temperature", zone).c_str(), &temp, &size, NULL, 0) ==
0) ||
(sysctlbyname(fmt::format("hw.acpi.thermal.tz{}.temperature", zone).c_str(), &temp, &size,
NULL, 0) == 0)) {
auto temperature_c = ((float)temp - 2732) / 10;
return temperature_c;
}
auto temperature_c = ((float)temp - 2732) / 10;
return temperature_c;
throw std::runtime_error(fmt::format(
"sysctl hw.acpi.thermal.tz{}.temperature and dev.cpu.{}.temperature failed", zone, zone));
#else // Linux
std::ifstream temp(file_path_);
@@ -148,4 +152,4 @@ bool waybar::modules::Temperature::isWarning(uint16_t temperature_c) {
bool waybar::modules::Temperature::isCritical(uint16_t temperature_c) {
return config_["critical-threshold"].isInt() &&
temperature_c >= config_["critical-threshold"].asInt();
}
}

View File

@@ -0,0 +1,445 @@
#include "modules/wayfire/backend.hpp"
#include <json/json.h>
#include <spdlog/spdlog.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <algorithm>
#include <bit>
#include <cstdint>
#include <cstdlib>
#include <exception>
#include <ranges>
#include <thread>
namespace waybar::modules::wayfire {
std::weak_ptr<IPC> IPC::instance;
// C++23: std::byteswap
inline auto byteswap(uint32_t x) -> uint32_t {
return (x & 0xff000000) >> 24 | (x & 0x00ff0000) >> 8 | (x & 0x0000ff00) << 8 |
(x & 0x000000ff) << 24;
}
auto pack_and_write(Sock& sock, std::string&& buf) -> void {
uint32_t len = buf.size();
if constexpr (std::endian::native != std::endian::little) len = byteswap(len);
(void)write(sock.fd, &len, 4);
(void)write(sock.fd, buf.data(), buf.size());
}
auto read_exact(Sock& sock, size_t n) -> std::string {
auto buf = std::string(n, 0);
for (size_t i = 0; i < n;) i += read(sock.fd, &buf[i], n - i);
return buf;
}
// https://github.com/WayfireWM/pywayfire/blob/69b7c21/wayfire/ipc.py#L438
inline auto is_mapped_toplevel_view(const Json::Value& view) -> bool {
return view["mapped"].asBool() && view["role"] != "desktop-environment" &&
view["pid"].asInt() != -1;
}
auto State::Wset::count_ws(const Json::Value& pos) -> Workspace& {
auto x = pos["x"].asInt();
auto y = pos["y"].asInt();
return wss.at(ws_w * y + x);
}
auto State::Wset::locate_ws(const Json::Value& geo) -> Workspace& {
return const_cast<Workspace&>(std::as_const(*this).locate_ws(geo));
}
auto State::Wset::locate_ws(const Json::Value& geo) const -> const Workspace& {
const auto& out = output.value().get();
auto [qx, rx] = std::div(geo["x"].asInt(), out.w);
auto [qy, ry] = std::div(geo["y"].asInt(), out.h);
auto x = std::max(0, (int)ws_x + qx - int{rx < 0});
auto y = std::max(0, (int)ws_y + qy - int{ry < 0});
return wss.at(ws_w * y + x);
}
auto State::update_view(const Json::Value& view) -> void {
auto id = view["id"].asUInt();
// erase old view information
if (views.contains(id)) {
auto& old_view = views.at(id);
auto& ws = wsets.at(old_view["wset-index"].asUInt()).locate_ws(old_view["geometry"]);
ws.num_views--;
if (old_view["sticky"].asBool()) ws.num_sticky_views--;
views.erase(id);
}
// insert or assign new view information
if (is_mapped_toplevel_view(view)) {
try {
// view["wset-index"] could be messed up
auto& ws = wsets.at(view["wset-index"].asUInt()).locate_ws(view["geometry"]);
ws.num_views++;
if (view["sticky"].asBool()) ws.num_sticky_views++;
views.emplace(id, view);
} catch (const std::exception&) {
}
}
}
auto IPC::get_instance() -> std::shared_ptr<IPC> {
auto p = instance.lock();
if (!p) instance = p = std::shared_ptr<IPC>(new IPC);
return p;
}
auto IPC::connect() -> Sock {
auto* path = std::getenv("WAYFIRE_SOCKET");
if (path == nullptr) {
throw std::runtime_error{"Wayfire IPC: ipc not available"};
}
auto sock = socket(AF_UNIX, SOCK_STREAM, 0);
if (sock == -1) {
throw std::runtime_error{"Wayfire IPC: socket() failed"};
}
auto addr = sockaddr_un{.sun_family = AF_UNIX};
std::strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
addr.sun_path[sizeof(addr.sun_path) - 1] = 0;
if (::connect(sock, (const sockaddr*)&addr, sizeof(addr)) == -1) {
close(sock);
throw std::runtime_error{"Wayfire IPC: connect() failed"};
}
return {sock};
}
auto IPC::receive(Sock& sock) -> Json::Value {
auto len = *reinterpret_cast<uint32_t*>(read_exact(sock, 4).data());
if constexpr (std::endian::native != std::endian::little) len = byteswap(len);
auto buf = read_exact(sock, len);
Json::Value json;
std::string err;
auto* reader = reader_builder.newCharReader();
if (!reader->parse(&*buf.begin(), &*buf.end(), &json, &err)) {
throw std::runtime_error{"Wayfire IPC: parse json failed: " + err};
}
return json;
}
auto IPC::send(const std::string& method, Json::Value&& data) -> Json::Value {
spdlog::debug("Wayfire IPC: send method \"{}\"", method);
auto sock = connect();
Json::Value json;
json["method"] = method;
json["data"] = std::move(data);
pack_and_write(sock, Json::writeString(writer_builder, json));
auto res = receive(sock);
root_event_handler(method, res);
return res;
}
auto IPC::start() -> void {
spdlog::info("Wayfire IPC: starting");
// init state
send("window-rules/list-outputs", {});
send("window-rules/list-wsets", {});
send("window-rules/list-views", {});
send("window-rules/get-focused-view", {});
send("window-rules/get-focused-output", {});
std::thread([&] {
auto sock = connect();
{
Json::Value json;
json["method"] = "window-rules/events/watch";
pack_and_write(sock, Json::writeString(writer_builder, json));
if (receive(sock)["result"] != "ok") {
spdlog::error(
"Wayfire IPC: method \"window-rules/events/watch\""
" have failed");
return;
}
}
while (auto json = receive(sock)) {
auto ev = json["event"].asString();
spdlog::debug("Wayfire IPC: received event \"{}\"", ev);
root_event_handler(ev, json);
}
}).detach();
}
auto IPC::register_handler(const std::string& event, const EventHandler& handler) -> void {
auto _ = std::lock_guard{handlers_mutex};
handlers.emplace_back(event, handler);
}
auto IPC::unregister_handler(EventHandler& handler) -> void {
auto _ = std::lock_guard{handlers_mutex};
handlers.remove_if([&](auto& e) { return &e.second.get() == &handler; });
}
auto IPC::root_event_handler(const std::string& event, const Json::Value& data) -> void {
bool new_output_detected;
{
auto _ = lock_state();
update_state_handler(event, data);
new_output_detected = state.new_output_detected;
state.new_output_detected = false;
}
if (new_output_detected) {
send("window-rules/list-outputs", {});
send("window-rules/list-wsets", {});
}
{
auto _ = std::lock_guard{handlers_mutex};
for (const auto& [_event, handler] : handlers)
if (_event == event) handler(event);
}
}
auto IPC::update_state_handler(const std::string& event, const Json::Value& data) -> void {
// IPC events
// https://github.com/WayfireWM/wayfire/blob/053b222/plugins/ipc-rules/ipc-events.hpp#L108-L125
/*
[x] view-mapped
[x] view-unmapped
[-] view-set-output // for detect new output
[ ] view-geometry-changed // -> view-workspace-changed
[x] view-wset-changed
[x] view-focused
[x] view-title-changed
[x] view-app-id-changed
[x] plugin-activation-state-changed
[x] output-gain-focus
[ ] view-tiled
[ ] view-minimized
[ ] view-fullscreened
[x] view-sticky
[x] view-workspace-changed
[x] output-wset-changed
[x] wset-workspace-changed
*/
if (event == "view-mapped") {
// data: { event, view }
state.update_view(data["view"]);
return;
}
if (event == "view-unmapped") {
// data: { event, view }
try {
// data["view"]["wset-index"] could be messed up
state.update_view(data["view"]);
state.maybe_empty_focus_wset_idx = data["view"]["wset-index"].asUInt();
} catch (const std::exception&) {
}
return;
}
if (event == "view-set-output") {
// data: { event, output?, view }
// new output event
if (!state.outputs.contains(data["view"]["output-name"].asString())) {
state.new_output_detected = true;
}
return;
}
if (event == "view-wset-changed") {
// data: { event, old-wset: wset, new-wset: wset, view }
state.maybe_empty_focus_wset_idx = data["old-wset"]["index"].asUInt();
state.update_view(data["view"]);
return;
}
if (event == "view-focused") {
// data: { event, view? }
if (const auto& view = data["view"]) {
try {
// view["wset-index"] could be messed up
auto& wset = state.wsets.at(view["wset-index"].asUInt());
wset.focused_view_id = view["id"].asUInt();
} catch (const std::exception&) {
}
} else {
// focused to null
if (state.wsets.contains(state.maybe_empty_focus_wset_idx))
state.wsets.at(state.maybe_empty_focus_wset_idx).focused_view_id = {};
}
return;
}
if (event == "view-title-changed" || event == "view-app-id-changed" || event == "view-sticky") {
// data: { event, view }
state.update_view(data["view"]);
return;
}
if (event == "plugin-activation-state-changed") {
// data: { event, plugin: name, state: bool, output: id, output-data: output }
auto plugin = data["plugin"].asString();
auto plugin_state = data["state"].asBool();
if (plugin == "vswitch") {
state.vswitching = plugin_state;
if (plugin_state) {
state.maybe_empty_focus_wset_idx = data["output-data"]["wset-index"].asUInt();
}
}
return;
}
if (event == "output-gain-focus") {
// data: { event, output }
state.focused_output_name = data["output"]["name"].asString();
return;
}
if (event == "view-workspace-changed") {
// data: { event, from: point, to: point, view }
if (state.vswitching) {
if (state.vswitch_sticky_view_id == 0) {
auto& wset = state.wsets.at(data["view"]["wset-index"].asUInt());
auto& old_ws = wset.locate_ws(state.views.at(data["view"]["id"].asUInt())["geometry"]);
auto& new_ws = wset.count_ws(data["to"]);
old_ws.num_views--;
new_ws.num_views++;
if (data["view"]["sticky"].asBool()) {
old_ws.num_sticky_views--;
new_ws.num_sticky_views++;
}
state.update_view(data["view"]);
state.vswitch_sticky_view_id = data["view"]["id"].asUInt();
} else {
state.vswitch_sticky_view_id = {};
}
return;
}
state.update_view(data["view"]);
return;
}
if (event == "output-wset-changed") {
// data: { event, new-wset: wset.name, output: id, new-wset-data: wset, output-data: output }
auto& output = state.outputs.at(data["output-data"]["name"].asString());
auto wset_idx = data["new-wset-data"]["index"].asUInt();
state.wsets.at(wset_idx).output = output;
output.wset_idx = wset_idx;
return;
}
if (event == "wset-workspace-changed") {
// data: { event, previous-workspace: point, new-workspace: point,
// output: id, wset: wset.name, output-data: output, wset-data: wset }
auto wset_idx = data["wset-data"]["index"].asUInt();
auto& wset = state.wsets.at(wset_idx);
wset.ws_x = data["new-workspace"]["x"].asUInt();
wset.ws_y = data["new-workspace"]["y"].asUInt();
// correct existing views geometry
auto& out = wset.output.value().get();
auto dx = (int)out.w * ((int)wset.ws_x - data["previous-workspace"]["x"].asInt());
auto dy = (int)out.h * ((int)wset.ws_y - data["previous-workspace"]["y"].asInt());
for (auto& [_, view] : state.views) {
if (view["wset-index"].asUInt() == wset_idx &&
view["id"].asUInt() != state.vswitch_sticky_view_id) {
view["geometry"]["x"] = view["geometry"]["x"].asInt() - dx;
view["geometry"]["y"] = view["geometry"]["y"].asInt() - dy;
}
}
return;
}
// IPC responses
// https://github.com/WayfireWM/wayfire/blob/053b222/plugins/ipc-rules/ipc-rules.cpp#L27-L37
if (event == "window-rules/list-views") {
// data: [ view ]
state.views.clear();
for (auto& [_, wset] : state.wsets) std::ranges::fill(wset.wss, State::Workspace{});
for (const auto& view : data | std::views::filter(is_mapped_toplevel_view)) {
state.update_view(view);
}
return;
}
if (event == "window-rules/list-outputs") {
// data: [ output ]
state.outputs.clear();
for (const auto& output_data : data) {
state.outputs.emplace(output_data["name"].asString(),
State::Output{
.id = output_data["id"].asUInt(),
.w = output_data["geometry"]["width"].asUInt(),
.h = output_data["geometry"]["height"].asUInt(),
.wset_idx = output_data["wset-index"].asUInt(),
});
}
return;
}
if (event == "window-rules/list-wsets") {
// data: [ wset ]
std::unordered_map<size_t, State::Wset> wsets;
for (const auto& wset_data : data) {
auto wset_idx = wset_data["index"].asUInt();
auto output_name = wset_data["output-name"].asString();
auto output = state.outputs.contains(output_name)
? std::optional{std::ref(state.outputs.at(output_name))}
: std::nullopt;
const auto& ws_data = wset_data["workspace"];
auto ws_w = ws_data["grid_width"].asUInt();
auto ws_h = ws_data["grid_height"].asUInt();
wsets.emplace(wset_idx, State::Wset{
.output = output,
.wss = std::vector<State::Workspace>(ws_w * ws_h),
.ws_w = ws_w,
.ws_h = ws_h,
.ws_x = ws_data["x"].asUInt(),
.ws_y = ws_data["y"].asUInt(),
});
if (state.wsets.contains(wset_idx)) {
auto& old_wset = state.wsets.at(wset_idx);
auto& new_wset = wsets.at(wset_idx);
new_wset.wss = std::move(old_wset.wss);
new_wset.focused_view_id = old_wset.focused_view_id;
}
}
state.wsets = std::move(wsets);
return;
}
if (event == "window-rules/get-focused-view") {
// data: { ok, info: view? }
if (const auto& view = data["info"]) {
auto& wset = state.wsets.at(view["wset-index"].asUInt());
wset.focused_view_id = view["id"].asUInt();
state.update_view(view);
}
return;
}
if (event == "window-rules/get-focused-output") {
// data: { ok, info: output }
state.focused_output_name = data["info"]["name"].asString();
return;
}
}
} // namespace waybar::modules::wayfire

View File

@@ -0,0 +1,77 @@
#include "modules/wayfire/window.hpp"
#include <gtkmm/button.h>
#include <gtkmm/label.h>
#include <spdlog/spdlog.h>
#include "util/rewrite_string.hpp"
#include "util/sanitize_str.hpp"
namespace waybar::modules::wayfire {
Window::Window(const std::string& id, const Bar& bar, const Json::Value& config)
: AAppIconLabel(config, "window", id, "{title}", 0, true),
ipc{IPC::get_instance()},
handler{[this](const auto&) { dp.emit(); }},
bar_{bar} {
ipc->register_handler("view-unmapped", handler);
ipc->register_handler("view-focused", handler);
ipc->register_handler("view-title-changed", handler);
ipc->register_handler("view-app-id-changed", handler);
ipc->register_handler("window-rules/get-focused-view", handler);
dp.emit();
}
Window::~Window() { ipc->unregister_handler(handler); }
auto Window::update() -> void {
update_icon_label();
AAppIconLabel::update();
}
auto Window::update_icon_label() -> void {
auto _ = ipc->lock_state();
const auto& output = ipc->get_outputs().at(bar_.output->name);
const auto& wset = ipc->get_wsets().at(output.wset_idx);
const auto& views = ipc->get_views();
auto ctx = bar_.window.get_style_context();
if (views.contains(wset.focused_view_id)) {
const auto& view = views.at(wset.focused_view_id);
auto title = view["title"].asString();
auto app_id = view["app-id"].asString();
// update label
label_.set_markup(waybar::util::rewriteString(
fmt::format(fmt::runtime(format_), fmt::arg("title", waybar::util::sanitize_string(title)),
fmt::arg("app_id", waybar::util::sanitize_string(app_id))),
config_["rewrite"]));
// update window#waybar.solo
if (wset.locate_ws(view["geometry"]).num_views > 1)
ctx->remove_class("solo");
else
ctx->add_class("solo");
// update window#waybar.<app_id>
ctx->remove_class(old_app_id_);
ctx->add_class(old_app_id_ = app_id);
// update window#waybar.empty
ctx->remove_class("empty");
//
updateAppIconName(app_id, "");
label_.show();
} else {
ctx->add_class("empty");
updateAppIconName("", "");
label_.hide();
}
}
} // namespace waybar::modules::wayfire

View File

@@ -0,0 +1,183 @@
#include "modules/wayfire/workspaces.hpp"
#include <gtkmm/button.h>
#include <gtkmm/label.h>
#include <spdlog/spdlog.h>
#include <string>
#include <utility>
#include "modules/wayfire/backend.hpp"
namespace waybar::modules::wayfire {
Workspaces::Workspaces(const std::string& id, const Bar& bar, const Json::Value& config)
: AModule{config, "workspaces", id, false, !config["disable-scroll"].asBool()},
ipc{IPC::get_instance()},
handler{[this](const auto&) { dp.emit(); }},
bar_{bar} {
// init box_
box_.set_name("workspaces");
if (!id.empty()) box_.get_style_context()->add_class(id);
box_.get_style_context()->add_class(MODULE_CLASS);
event_box_.add(box_);
// scroll events
if (!config_["disable-scroll"].asBool()) {
auto& target = config_["enable-bar-scroll"].asBool() ? const_cast<Bar&>(bar_).window
: dynamic_cast<Gtk::Widget&>(box_);
target.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);
target.signal_scroll_event().connect(sigc::mem_fun(*this, &Workspaces::handleScroll));
}
// listen events
ipc->register_handler("view-mapped", handler);
ipc->register_handler("view-unmapped", handler);
ipc->register_handler("view-wset-changed", handler);
ipc->register_handler("output-gain-focus", handler);
ipc->register_handler("view-sticky", handler);
ipc->register_handler("view-workspace-changed", handler);
ipc->register_handler("output-wset-changed", handler);
ipc->register_handler("wset-workspace-changed", handler);
ipc->register_handler("window-rules/list-views", handler);
ipc->register_handler("window-rules/list-outputs", handler);
ipc->register_handler("window-rules/list-wsets", handler);
ipc->register_handler("window-rules/get-focused-output", handler);
// initial render
dp.emit();
}
Workspaces::~Workspaces() { ipc->unregister_handler(handler); }
auto Workspaces::handleScroll(GdkEventScroll* e) -> bool {
// Ignore emulated scroll events on window
if (gdk_event_get_pointer_emulated((GdkEvent*)e) != 0) return false;
auto dir = AModule::getScrollDir(e);
if (dir == SCROLL_DIR::NONE) return true;
int delta;
if (dir == SCROLL_DIR::DOWN || dir == SCROLL_DIR::RIGHT)
delta = 1;
else if (dir == SCROLL_DIR::UP || dir == SCROLL_DIR::LEFT)
delta = -1;
else
return true;
// cycle workspace
Json::Value data;
{
auto _ = ipc->lock_state();
const auto& output = ipc->get_outputs().at(bar_.output->name);
const auto& wset = ipc->get_wsets().at(output.wset_idx);
auto n = wset.ws_w * wset.ws_h;
auto i = (wset.ws_idx() + delta + n) % n;
data["x"] = i % wset.ws_w;
data["y"] = i / wset.ws_h;
data["output-id"] = output.id;
}
ipc->send("vswitch/set-workspace", std::move(data));
return true;
}
auto Workspaces::update() -> void {
update_box();
AModule::update();
}
auto Workspaces::update_box() -> void {
auto _ = ipc->lock_state();
const auto& output_name = bar_.output->name;
const auto& output = ipc->get_outputs().at(output_name);
const auto& wset = ipc->get_wsets().at(output.wset_idx);
auto output_focused = ipc->get_focused_output_name() == output_name;
auto ws_w = wset.ws_w;
auto ws_h = wset.ws_h;
auto num_wss = ws_w * ws_h;
// add buttons for new workspaces
for (auto i = buttons_.size(); i < num_wss; i++) {
auto& btn = buttons_.emplace_back("");
box_.pack_start(btn, false, false, 0);
btn.set_relief(Gtk::RELIEF_NONE);
if (!config_["disable-click"].asBool()) {
btn.signal_pressed().connect([=, this] {
Json::Value data;
data["x"] = i % ws_w;
data["y"] = i / ws_h;
data["output-id"] = output.id;
ipc->send("vswitch/set-workspace", std::move(data));
});
}
}
// remove buttons for removed workspaces
buttons_.resize(num_wss);
// update buttons
for (size_t i = 0; i < num_wss; i++) {
const auto& ws = wset.wss[i];
auto& btn = buttons_[i];
auto ctx = btn.get_style_context();
auto ws_focused = i == wset.ws_idx();
auto ws_empty = ws.num_views == 0;
// update #workspaces button.focused
if (ws_focused)
ctx->add_class("focused");
else
ctx->remove_class("focused");
// update #workspaces button.empty
if (ws_empty)
ctx->add_class("empty");
else
ctx->remove_class("empty");
// update #workspaces button.current_output
if (output_focused)
ctx->add_class("current_output");
else
ctx->remove_class("current_output");
// update label
auto label = std::to_string(i + 1);
if (config_["format"].isString()) {
auto format = config_["format"].asString();
auto ws_idx = std::to_string(i + 1);
const auto& icons = config_["format-icons"];
std::string icon;
if (!icons)
icon = ws_idx;
else if (ws_focused && icons["focused"])
icon = icons["focused"].asString();
else if (icons[ws_idx])
icon = icons[ws_idx].asString();
else if (icons["default"])
icon = icons["default"].asString();
else
icon = ws_idx;
label = fmt::format(fmt::runtime(format), fmt::arg("icon", icon), fmt::arg("index", ws_idx),
fmt::arg("output", output_name));
}
if (!config_["disable-markup"].asBool())
static_cast<Gtk::Label*>(btn.get_children()[0])->set_markup(label);
else
btn.set_label(label);
//
if (config_["current-only"].asBool() && i != wset.ws_idx())
btn.hide();
else
btn.show();
}
}
} // namespace waybar::modules::wayfire

View File

@@ -4,6 +4,8 @@
bool isValidNodeId(uint32_t id) { return id > 0 && id < G_MAXUINT32; }
std::list<waybar::modules::Wireplumber*> waybar::modules::Wireplumber::modules;
waybar::modules::Wireplumber::Wireplumber(const std::string& id, const Json::Value& config)
: ALabel(config, "wireplumber", id, "{volume}%"),
wp_core_(nullptr),
@@ -16,22 +18,28 @@ waybar::modules::Wireplumber::Wireplumber(const std::string& id, const Json::Val
muted_(false),
volume_(0.0),
min_step_(0.0),
node_id_(0) {
node_id_(0),
type_(nullptr) {
waybar::modules::Wireplumber::modules.push_back(this);
wp_init(WP_INIT_PIPEWIRE);
wp_core_ = wp_core_new(nullptr, nullptr, nullptr);
apis_ = g_ptr_array_new_with_free_func(g_object_unref);
om_ = wp_object_manager_new();
prepare();
type_ = g_strdup(config_["node-type"].isString() ? config_["node-type"].asString().c_str()
: "Audio/Sink");
spdlog::debug("[{}]: connecting to pipewire...", name_);
prepare(this);
spdlog::debug("[{}]: connecting to pipewire: '{}'...", name_, type_);
if (wp_core_connect(wp_core_) == 0) {
spdlog::error("[{}]: Could not connect to PipeWire", name_);
spdlog::error("[{}]: Could not connect to PipeWire: '{}'", name_, type_);
throw std::runtime_error("Could not connect to PipeWire\n");
}
spdlog::debug("[{}]: connected!", name_);
spdlog::debug("[{}]: {} connected!", name_, type_);
g_signal_connect_swapped(om_, "installed", (GCallback)onObjectManagerInstalled, this);
@@ -39,6 +47,7 @@ waybar::modules::Wireplumber::Wireplumber(const std::string& id, const Json::Val
}
waybar::modules::Wireplumber::~Wireplumber() {
waybar::modules::Wireplumber::modules.remove(this);
wp_core_disconnect(wp_core_);
g_clear_pointer(&apis_, g_ptr_array_unref);
g_clear_object(&om_);
@@ -46,13 +55,15 @@ waybar::modules::Wireplumber::~Wireplumber() {
g_clear_object(&mixer_api_);
g_clear_object(&def_nodes_api_);
g_free(default_node_name_);
g_free(type_);
}
void waybar::modules::Wireplumber::updateNodeName(waybar::modules::Wireplumber* self, uint32_t id) {
spdlog::debug("[{}]: updating node name with node.id {}", self->name_, id);
spdlog::debug("[{}]: updating '{}' node name with node.id {}", self->name_, self->type_, id);
if (!isValidNodeId(id)) {
spdlog::warn("[{}]: '{}' is not a valid node ID. Ignoring node name update.", self->name_, id);
spdlog::warn("[{}]: '{}' is not a valid node ID. Ignoring '{}' node name update.", self->name_,
id, self->type_);
return;
}
@@ -80,7 +91,7 @@ void waybar::modules::Wireplumber::updateNodeName(waybar::modules::Wireplumber*
self->node_name_ = nick != nullptr ? nick
: description != nullptr ? description
: "Unknown node name";
spdlog::debug("[{}]: Updating node name to: {}", self->name_, self->node_name_);
spdlog::debug("[{}]: Updating '{}' node name to: {}", self->name_, self->type_, self->node_name_);
}
void waybar::modules::Wireplumber::updateVolume(waybar::modules::Wireplumber* self, uint32_t id) {
@@ -88,7 +99,8 @@ void waybar::modules::Wireplumber::updateVolume(waybar::modules::Wireplumber* se
GVariant* variant = nullptr;
if (!isValidNodeId(id)) {
spdlog::error("[{}]: '{}' is not a valid node ID. Ignoring volume update.", self->name_, id);
spdlog::error("[{}]: '{}' is not a valid '{}' node ID. Ignoring volume update.", self->name_,
id, self->type_);
return;
}
@@ -109,13 +121,22 @@ void waybar::modules::Wireplumber::updateVolume(waybar::modules::Wireplumber* se
}
void waybar::modules::Wireplumber::onMixerChanged(waybar::modules::Wireplumber* self, uint32_t id) {
spdlog::debug("[{}]: (onMixerChanged) - id: {}", self->name_, id);
g_autoptr(WpNode) node = static_cast<WpNode*>(wp_object_manager_lookup(
self->om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_G_PROPERTY, "bound-id", "=u", id, nullptr));
if (node == nullptr) {
spdlog::warn("[{}]: (onMixerChanged) - Object with id {} not found", self->name_, id);
// log a warning only if no other widget is targeting the id.
// this reduces log spam when multiple instances of the module are used on different node types.
if (id != self->node_id_) {
for (auto const& module : waybar::modules::Wireplumber::modules) {
if (module->node_id_ == id) {
return;
}
}
}
spdlog::warn("[{}]: (onMixerChanged: {}) - Object with id {} not found", self->name_,
self->type_, id);
return;
}
@@ -123,26 +144,27 @@ void waybar::modules::Wireplumber::onMixerChanged(waybar::modules::Wireplumber*
if (self->node_id_ != id) {
spdlog::debug(
"[{}]: (onMixerChanged) - ignoring mixer update for node: id: {}, name: {} as it is not "
"the default node: {} with id: {}",
self->name_, id, name, self->default_node_name_, self->node_id_);
"[{}]: (onMixerChanged: {}) - ignoring mixer update for node: id: {}, name: {} as it is "
"not the default node: {} with id: {}",
self->name_, self->type_, id, name, self->default_node_name_, self->node_id_);
return;
}
spdlog::debug("[{}]: (onMixerChanged) - Need to update volume for node with id {} and name {}",
self->name_, id, name);
spdlog::debug(
"[{}]: (onMixerChanged: {}) - Need to update volume for node with id {} and name {}",
self->name_, self->type_, id, name);
updateVolume(self, id);
}
void waybar::modules::Wireplumber::onDefaultNodesApiChanged(waybar::modules::Wireplumber* self) {
spdlog::debug("[{}]: (onDefaultNodesApiChanged)", self->name_);
spdlog::debug("[{}]: (onDefaultNodesApiChanged: {})", self->name_, self->type_);
uint32_t defaultNodeId;
g_signal_emit_by_name(self->def_nodes_api_, "get-default-node", "Audio/Sink", &defaultNodeId);
g_signal_emit_by_name(self->def_nodes_api_, "get-default-node", self->type_, &defaultNodeId);
if (!isValidNodeId(defaultNodeId)) {
spdlog::warn("[{}]: '{}' is not a valid node ID. Ignoring node change.", self->name_,
defaultNodeId);
spdlog::warn("[{}]: '{}' is not a valid node ID. Ignoring '{}' node change.", self->name_,
defaultNodeId, self->type_);
return;
}
@@ -151,8 +173,8 @@ void waybar::modules::Wireplumber::onDefaultNodesApiChanged(waybar::modules::Wir
"=u", defaultNodeId, nullptr));
if (node == nullptr) {
spdlog::warn("[{}]: (onDefaultNodesApiChanged) - Object with id {} not found", self->name_,
defaultNodeId);
spdlog::warn("[{}]: (onDefaultNodesApiChanged: {}) - Object with id {} not found", self->name_,
self->type_, defaultNodeId);
return;
}
@@ -160,21 +182,22 @@ void waybar::modules::Wireplumber::onDefaultNodesApiChanged(waybar::modules::Wir
wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(node), "node.name");
spdlog::debug(
"[{}]: (onDefaultNodesApiChanged) - got the following default node: Node(name: {}, id: {})",
self->name_, defaultNodeName, defaultNodeId);
"[{}]: (onDefaultNodesApiChanged: {}) - got the following default node: Node(name: {}, id: "
"{})",
self->name_, self->type_, defaultNodeName, defaultNodeId);
if (g_strcmp0(self->default_node_name_, defaultNodeName) == 0 &&
self->node_id_ == defaultNodeId) {
spdlog::debug(
"[{}]: (onDefaultNodesApiChanged) - Default node has not changed. Node(name: {}, id: {}). "
"Ignoring.",
self->name_, self->default_node_name_, defaultNodeId);
"[{}]: (onDefaultNodesApiChanged: {}) - Default node has not changed. Node(name: {}, id: "
"{}). Ignoring.",
self->name_, self->type_, self->default_node_name_, defaultNodeId);
return;
}
spdlog::debug(
"[{}]: (onDefaultNodesApiChanged) - Default node changed to -> Node(name: {}, id: {})",
self->name_, defaultNodeName, defaultNodeId);
"[{}]: (onDefaultNodesApiChanged: {}) - Default node changed to -> Node(name: {}, id: {})",
self->name_, self->type_, defaultNodeName, defaultNodeId);
g_free(self->default_node_name_);
self->default_node_name_ = g_strdup(defaultNodeName);
@@ -200,13 +223,14 @@ void waybar::modules::Wireplumber::onObjectManagerInstalled(waybar::modules::Wir
throw std::runtime_error("Mixer api is not loaded\n");
}
g_signal_emit_by_name(self->def_nodes_api_, "get-default-configured-node-name", "Audio/Sink",
g_signal_emit_by_name(self->def_nodes_api_, "get-default-configured-node-name", self->type_,
&self->default_node_name_);
g_signal_emit_by_name(self->def_nodes_api_, "get-default-node", "Audio/Sink", &self->node_id_);
g_signal_emit_by_name(self->def_nodes_api_, "get-default-node", self->type_, &self->node_id_);
if (self->default_node_name_ != nullptr) {
spdlog::debug("[{}]: (onObjectManagerInstalled) - default configured node name: {} and id: {}",
self->name_, self->default_node_name_, self->node_id_);
spdlog::debug(
"[{}]: (onObjectManagerInstalled: {}) - default configured node name: {} and id: {}",
self->name_, self->type_, self->default_node_name_, self->node_id_);
}
updateVolume(self, self->node_id_);
@@ -243,10 +267,10 @@ void waybar::modules::Wireplumber::activatePlugins() {
}
}
void waybar::modules::Wireplumber::prepare() {
spdlog::debug("[{}]: preparing object manager", name_);
void waybar::modules::Wireplumber::prepare(waybar::modules::Wireplumber* self) {
spdlog::debug("[{}]: preparing object manager: '{}'", name_, self->type_);
wp_object_manager_add_interest(om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_PW_PROPERTY, "media.class",
"=s", "Audio/Sink", nullptr);
"=s", self->type_, nullptr);
}
void waybar::modules::Wireplumber::onDefaultNodesApiLoaded(WpObject* p, GAsyncResult* res,
@@ -275,7 +299,7 @@ void waybar::modules::Wireplumber::onMixerApiLoaded(WpObject* p, GAsyncResult* r
gboolean success = FALSE;
g_autoptr(GError) error = nullptr;
success = wp_core_load_component_finish(self->wp_core_, res, nullptr);
success = wp_core_load_component_finish(self->wp_core_, res, &error);
if (success == FALSE) {
spdlog::error("[{}]: mixer API load failed", self->name_);

View File

@@ -383,8 +383,38 @@ std::string Task::state_string(bool shortened) const {
}
void Task::handle_title(const char *title) {
if (title_.empty()) {
spdlog::debug(fmt::format("Task ({}) setting title to {}", id_, title_));
} else {
spdlog::debug(fmt::format("Task ({}) overwriting title '{}' with '{}'", id_, title_, title));
}
title_ = title;
hide_if_ignored();
if (!with_icon_ && !with_name_ || app_info_) {
return;
}
set_app_info_from_app_id_list(title_);
name_ = app_info_ ? app_info_->get_display_name() : title;
if (!with_icon_) {
return;
}
int icon_size = config_["icon-size"].isInt() ? config_["icon-size"].asInt() : 16;
bool found = false;
for (auto &icon_theme : tbar_->icon_themes()) {
if (image_load_icon(icon_, icon_theme, app_info_, icon_size)) {
found = true;
break;
}
}
if (found)
icon_.show();
else
spdlog::debug("Couldn't find icon for {}", title_);
}
void Task::set_minimize_hint() {
@@ -682,6 +712,9 @@ void Task::update() {
fmt::format(fmt::runtime(format_tooltip_), fmt::arg("title", title), fmt::arg("name", name),
fmt::arg("app_id", app_id), fmt::arg("state", state_string()),
fmt::arg("short_state", state_string(true)));
txt = waybar::util::rewriteString(txt, config_["rewrite"]);
if (markup)
button.set_tooltip_markup(txt);
else

View File

@@ -24,6 +24,8 @@ AudioBackend::AudioBackend(std::function<void()> on_updated_cb, private_construc
source_volume_(0),
source_muted_(false),
on_updated_cb_(std::move(on_updated_cb)) {
// Initialize pa_volume_ with safe defaults
pa_cvolume_init(&pa_volume_);
mainloop_ = pa_threaded_mainloop_new();
if (mainloop_ == nullptr) {
throw std::runtime_error("pa_mainloop_new() failed.");
@@ -131,7 +133,12 @@ void AudioBackend::subscribeCb(pa_context *context, pa_subscription_event_type_t
void AudioBackend::volumeModifyCb(pa_context *c, int success, void *data) {
auto *backend = static_cast<AudioBackend *>(data);
if (success != 0) {
pa_context_get_sink_info_by_index(backend->context_, backend->sink_idx_, sinkInfoCb, data);
if ((backend->context_ != nullptr) &&
pa_context_get_state(backend->context_) == PA_CONTEXT_READY) {
pa_context_get_sink_info_by_index(backend->context_, backend->sink_idx_, sinkInfoCb, data);
}
} else {
spdlog::debug("Volume modification failed");
}
}
@@ -180,11 +187,20 @@ void AudioBackend::sinkInfoCb(pa_context * /*context*/, const pa_sink_info *i, i
}
if (backend->current_sink_name_ == i->name) {
backend->pa_volume_ = i->volume;
float volume =
static_cast<float>(pa_cvolume_avg(&(backend->pa_volume_))) / float{PA_VOLUME_NORM};
backend->sink_idx_ = i->index;
backend->volume_ = std::round(volume * 100.0F);
// Safely copy the volume structure
if (pa_cvolume_valid(&i->volume) != 0) {
backend->pa_volume_ = i->volume;
float volume =
static_cast<float>(pa_cvolume_avg(&(backend->pa_volume_))) / float{PA_VOLUME_NORM};
backend->sink_idx_ = i->index;
backend->volume_ = std::round(volume * 100.0F);
} else {
spdlog::error("Invalid volume structure received from PulseAudio");
// Initialize with safe defaults
pa_cvolume_init(&backend->pa_volume_);
backend->volume_ = 0;
}
backend->muted_ = i->mute != 0;
backend->desc_ = i->description;
backend->monitor_ = i->monitor_source_name;
@@ -230,43 +246,109 @@ void AudioBackend::serverInfoCb(pa_context *context, const pa_server_info *i, vo
}
void AudioBackend::changeVolume(uint16_t volume, uint16_t min_volume, uint16_t max_volume) {
double volume_tick = static_cast<double>(PA_VOLUME_NORM) / 100;
pa_cvolume pa_volume = pa_volume_;
// Early return if context is not ready
if ((context_ == nullptr) || pa_context_get_state(context_) != PA_CONTEXT_READY) {
spdlog::error("PulseAudio context not ready");
return;
}
// Prepare volume structure
pa_cvolume pa_volume;
pa_cvolume_init(&pa_volume);
// Use existing volume structure if valid, otherwise create a safe default
if ((pa_cvolume_valid(&pa_volume_) != 0) && (pa_channels_valid(pa_volume_.channels) != 0)) {
pa_volume = pa_volume_;
} else {
// Set stereo as a safe default
pa_volume.channels = 2;
spdlog::debug("Using default stereo volume structure");
}
// Set the volume safely
volume = std::clamp(volume, min_volume, max_volume);
pa_cvolume_set(&pa_volume, pa_volume_.channels, volume * volume_tick);
pa_volume_t vol = volume * (static_cast<double>(PA_VOLUME_NORM) / 100);
// Set all channels to the same volume manually to avoid pa_cvolume_set
for (uint8_t i = 0; i < pa_volume.channels; i++) {
pa_volume.values[i] = vol;
}
// Apply the volume change
pa_threaded_mainloop_lock(mainloop_);
pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume, volumeModifyCb, this);
pa_threaded_mainloop_unlock(mainloop_);
}
void AudioBackend::changeVolume(ChangeType change_type, double step, uint16_t max_volume) {
double volume_tick = static_cast<double>(PA_VOLUME_NORM) / 100;
pa_volume_t change = volume_tick;
pa_cvolume pa_volume = pa_volume_;
// Early return if context is not ready
if ((context_ == nullptr) || pa_context_get_state(context_) != PA_CONTEXT_READY) {
spdlog::error("PulseAudio context not ready");
return;
}
// Prepare volume structure
pa_cvolume pa_volume;
pa_cvolume_init(&pa_volume);
// Use existing volume structure if valid, otherwise create a safe default
if ((pa_cvolume_valid(&pa_volume_) != 0) && (pa_channels_valid(pa_volume_.channels) != 0)) {
pa_volume = pa_volume_;
} else {
// Set stereo as a safe default
pa_volume.channels = 2;
spdlog::debug("Using default stereo volume structure");
// Initialize all channels to current volume level
double volume_tick = static_cast<double>(PA_VOLUME_NORM) / 100;
pa_volume_t vol = volume_ * volume_tick;
for (uint8_t i = 0; i < pa_volume.channels; i++) {
pa_volume.values[i] = vol;
}
// No need to continue with volume change if we had to create a new structure
pa_threaded_mainloop_lock(mainloop_);
pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume, volumeModifyCb, this);
pa_threaded_mainloop_unlock(mainloop_);
return;
}
// Calculate volume change
double volume_tick = static_cast<double>(PA_VOLUME_NORM) / 100;
pa_volume_t change;
max_volume = std::min(max_volume, static_cast<uint16_t>(PA_VOLUME_UI_MAX));
if (change_type == ChangeType::Increase) {
if (volume_ < max_volume) {
if (volume_ + step > max_volume) {
change = round((max_volume - volume_) * volume_tick);
} else {
change = round(step * volume_tick);
}
pa_cvolume_inc(&pa_volume, change);
if (change_type == ChangeType::Increase && volume_ < max_volume) {
// Calculate how much to increase
if (volume_ + step > max_volume) {
change = round((max_volume - volume_) * volume_tick);
} else {
change = round(step * volume_tick);
}
} else if (change_type == ChangeType::Decrease) {
if (volume_ > 0) {
if (volume_ - step < 0) {
change = round(volume_ * volume_tick);
} else {
change = round(step * volume_tick);
}
pa_cvolume_dec(&pa_volume, change);
// Manually increase each channel's volume
for (uint8_t i = 0; i < pa_volume.channels; i++) {
pa_volume.values[i] = std::min(pa_volume.values[i] + change, PA_VOLUME_MAX);
}
} else if (change_type == ChangeType::Decrease && volume_ > 0) {
// Calculate how much to decrease
if (volume_ - step < 0) {
change = round(volume_ * volume_tick);
} else {
change = round(step * volume_tick);
}
// Manually decrease each channel's volume
for (uint8_t i = 0; i < pa_volume.channels; i++) {
pa_volume.values[i] = (pa_volume.values[i] > change) ? (pa_volume.values[i] - change) : 0;
}
} else {
// No change needed
return;
}
// Apply the volume change
pa_threaded_mainloop_lock(mainloop_);
pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume, volumeModifyCb, this);
pa_threaded_mainloop_unlock(mainloop_);

View File

@@ -150,6 +150,7 @@ BacklightBackend::BacklightBackend(std::chrono::milliseconds interval,
throw std::runtime_error("No backlight found");
}
#ifdef HAVE_LOGIN_PROXY
// Connect to the login interface
login_proxy_ = Gio::DBus::Proxy::create_for_bus_sync(
Gio::DBus::BusType::BUS_TYPE_SYSTEM, "org.freedesktop.login1",
@@ -160,6 +161,7 @@ BacklightBackend::BacklightBackend(std::chrono::milliseconds interval,
Gio::DBus::BusType::BUS_TYPE_SYSTEM, "org.freedesktop.login1",
"/org/freedesktop/login1/session/self", "org.freedesktop.login1.Session");
}
#endif
udev_thread_ = [this] {
std::unique_ptr<udev, UdevDeleter> udev{udev_new()};

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