mirror of
https://github.com/rad4day/Waybar.git
synced 2023-12-21 10:22:59 +01:00
Compare commits
392 Commits
Author | SHA1 | Date | |
---|---|---|---|
115c6e36e6 | |||
210f4454f0 | |||
77d8376fef | |||
1aa7587cac | |||
b6655e475b | |||
903fc2b6a2 | |||
50fc63b749 | |||
383d999fec | |||
56ebb09e5f | |||
35d6da3965 | |||
be1d2a02ca | |||
5e7ba0c9e3 | |||
18717d4b12 | |||
4dc1989744 | |||
54085dbde0 | |||
4635e8c5f8 | |||
adc67b6f75 | |||
ebe4424795 | |||
e3f56b8110 | |||
8f3fbebede | |||
a595b61e0f | |||
91339f6ad4 | |||
3c18c43b9a | |||
ac20428fdf | |||
ad5ea7ad2b | |||
851508df5e | |||
fc818dd794 | |||
e066e3080e | |||
1a93a6cfa5 | |||
074b559da5 | |||
7a61a00fb3 | |||
d4da04a750 | |||
f3819ee954 | |||
2697d432a4 | |||
061cb76fc4 | |||
6c188455a4 | |||
12caae8fd2 | |||
26ea6fae32 | |||
26419e45b7 | |||
6be741afc9 | |||
c80cc873af | |||
2b42872b6c | |||
895bc878f8 | |||
a0ee9e7fc1 | |||
941cf47693 | |||
32d42749f9 | |||
96caa9f094 | |||
f4f1267a71 | |||
bcadf64031 | |||
8974bbf7b4 | |||
1c08d26af0 | |||
c4cc7ae396 | |||
65dd245362 | |||
667d0a45f4 | |||
53fc750fc3 | |||
27df7a9aa7 | |||
d575646c2d | |||
3a6e5be59d | |||
81f0bcb3a3 | |||
9ae99c2621 | |||
5abdecc402 | |||
1540797cd9 | |||
5d8bc35476 | |||
5c574975b7 | |||
18cbd2aa1a | |||
ce404199de | |||
7f6bef2049 | |||
1489a539f8 | |||
766c8a1035 | |||
8c41aaae04 | |||
c0d84853ea | |||
2301788a81 | |||
d22d6a4522 | |||
d9b5c2595a | |||
4a85ec0f59 | |||
621d686310 | |||
812832d38a | |||
19069482e2 | |||
1e560cf0c9 | |||
0c9cdf7e0b | |||
5a4f7a70ef | |||
ce56a80792 | |||
db95644d26 | |||
548bd2ab1a | |||
7b9b10afc6 | |||
8ec321ddaf | |||
13d25d403e | |||
3218612d3b | |||
9bc86347be | |||
d1f8b42d22 | |||
0e3be30e01 | |||
795246263f | |||
89a8bd976e | |||
9b399ea2bb | |||
9608e0dabf | |||
1d1cfda90d | |||
ad20c0af2d | |||
eae65099d0 | |||
39f42cdd7e | |||
f573e32d0b | |||
5cbbd65ac4 | |||
2240c79b1a | |||
ece86c96d7 | |||
9e8a71c4ef | |||
7069429c03 | |||
d41a60d2d9 | |||
05f7727dae | |||
9bc6fae15b | |||
9dac851f6d | |||
b6e24bd527 | |||
b6d0a4b63f | |||
cf5ddb2a5e | |||
4b5dc1bb3a | |||
22ff26252b | |||
a832814b41 | |||
2fb671f5fa | |||
3c2fa1625d | |||
d5112678c3 | |||
0c18e57937 | |||
b4e19678b7 | |||
8fe42ebd2e | |||
bb7b376fa6 | |||
89afa8e149 | |||
59040c53e4 | |||
02560a6537 | |||
0472d279e4 | |||
166504256a | |||
2290fe10aa | |||
9fa7bfc0cb | |||
6712cd05a3 | |||
977d21b5f6 | |||
0a4841371b | |||
deab028e52 | |||
c51a973d60 | |||
5a5f8c3b9f | |||
b1dc3005b7 | |||
98f7a10a51 | |||
23991b6543 | |||
331dfa87da | |||
29a8f52464 | |||
6bfb674d1b | |||
ef4c6a9ba3 | |||
ffeecf626c | |||
4154492603 | |||
5baffbf8f8 | |||
60c1706273 | |||
cb6af026f6 | |||
18ea53fcbc | |||
23955fdcc2 | |||
a06ed00727 | |||
15761ef802 | |||
ad3f46214d | |||
13fda1607f | |||
9fda6695ea | |||
b24f9ea569 | |||
e10c9dd011 | |||
b0eab5d793 | |||
17bb5643ae | |||
769b12f16a | |||
b511103fd9 | |||
122fe33636 | |||
8498ddefca | |||
48117a2e97 | |||
decb13eef0 | |||
a015b2e3db | |||
562e4157c0 | |||
01bfbc4656 | |||
7669029bfe | |||
0b66454d5c | |||
75a6dddea5 | |||
08b4a83331 | |||
c5e4d26320 | |||
d8bc6c92bb | |||
110c66dd32 | |||
6eb9606f23 | |||
03ebbf6d98 | |||
174db444d6 | |||
9972384597 | |||
8b4dafd701 | |||
f18eb71ad7 | |||
7af6e8413c | |||
fbedc3d133 | |||
fe5ac0fe08 | |||
f638fe473a | |||
8d04da1551 | |||
8534175c59 | |||
6778a79033 | |||
6e5a0bc80a | |||
fe547901fa | |||
cee08eccd1 | |||
67c7302938 | |||
8489646b66 | |||
6938921e92 | |||
7b73cc472f | |||
1c91c71dcd | |||
6142dfba6a | |||
13239417d8 | |||
5f083193e4 | |||
a51ac59252 | |||
8da940f929 | |||
ce9e8aead3 | |||
73ce2a99ff | |||
b028a47d57 | |||
4bf577e89b | |||
5991bbb741 | |||
d7d606b721 | |||
0c1d3e30b6 | |||
ccc60b4245 | |||
8912bd3ed0 | |||
9f3b34e4d9 | |||
6eba62f060 | |||
1f16d7955d | |||
1f7d399b8e | |||
4fff2eaaa0 | |||
b377520a38 | |||
95ecff0551 | |||
2c380a53ca | |||
aacd0fcc65 | |||
4f76c9bd43 | |||
8d9e322507 | |||
94e53c3777 | |||
12c42fc6e4 | |||
5186dd27e6 | |||
9d9f959769 | |||
4e256cf3f0 | |||
eae94ee14a | |||
e0260ac4fc | |||
7b4b5e55a2 | |||
cb49650ea4 | |||
d60bb90b77 | |||
ddfe036f00 | |||
38afa345dd | |||
ab38f13052 | |||
9ee701974f | |||
8940c3bbe8 | |||
51f2c6bc46 | |||
c058a2d196 | |||
a4fff66bec | |||
024fd42e27 | |||
c413c1ec23 | |||
2d80d31527 | |||
a87a967a97 | |||
a57e431437 | |||
61783aafaa | |||
e5787a2617 | |||
9aec6bbed4 | |||
4f6a9b1bc2 | |||
28e7a96e37 | |||
710f933fa6 | |||
bad72de960 | |||
65166109c9 | |||
91156dfc75 | |||
af2113931a | |||
68e4457f3a | |||
1b4ddbca3a | |||
445ad22580 | |||
88a5f713ed | |||
2009ceb350 | |||
77a2eff2ce | |||
cf832798fb | |||
3f3f2d9c2c | |||
b47705ac21 | |||
b33be38877 | |||
a5fe6f40b8 | |||
245f7f4b11 | |||
1418f96e46 | |||
84a8f79bbe | |||
4b6253e810 | |||
929fc16994 | |||
811f0896c9 | |||
7729ca3427 | |||
100d4d3499 | |||
7f5fd1ac86 | |||
1f5c07a07f | |||
67d482d28b | |||
1440ed29d4 | |||
311c5779ea | |||
9880c6929f | |||
99138ffdcd | |||
08e886ebc6 | |||
6fdbc27998 | |||
40e6360722 | |||
642e28166b | |||
6dfa31fb17 | |||
c91cc2218b | |||
6f2bfd43bf | |||
f43f8773c4 | |||
bb072675ba | |||
fa43072be7 | |||
86a43b9042 | |||
2506c0104a | |||
948eba92a5 | |||
ad09072a6d | |||
9c2b5efe7b | |||
91cdf80c65 | |||
9cce5ea6b5 | |||
8310700bbb | |||
78aaa5c1b4 | |||
7c1303f57c | |||
569517c531 | |||
1c2e0083ba | |||
a8edc0886d | |||
8e1f85e1c3 | |||
5420a91046 | |||
2a52efa99a | |||
d3c59c42ef | |||
368e4813de | |||
36857ae72b | |||
982d571b2e | |||
e62b634f72 | |||
e8278431d2 | |||
14f626d422 | |||
d08fbb2ef2 | |||
5da268077c | |||
20160749e7 | |||
194f4c2f18 | |||
9e34be7b16 | |||
6e041d5275 | |||
33617b67f0 | |||
efaac20d82 | |||
ce97df34e6 | |||
23b9923eeb | |||
999c1b6b81 | |||
1a98ecf6b0 | |||
5444a66e71 | |||
f49a7a1acb | |||
28dfb0ba41 | |||
94a882bf95 | |||
da2d603b53 | |||
7aaa3df701 | |||
729553d3bc | |||
f78a802d11 | |||
826a549d1f | |||
99918205ed | |||
c65ec9e14f | |||
c1427ff807 | |||
0bb436f949 | |||
0fc7ef6685 | |||
c9bbaa7241 | |||
63fdf66ad6 | |||
9357a6cb88 | |||
dbc06abf18 | |||
4d067619a8 | |||
cf3d6545c3 | |||
f3a6e2d494 | |||
cdce3e03ea | |||
b25b7d29fc | |||
71d7596b6f | |||
06e699c862 | |||
a03283d65f | |||
ef38061edd | |||
7e13e26c29 | |||
5f7329f5b9 | |||
2213380dc0 | |||
66d8035ed1 | |||
7cdf178f8d | |||
af3c868a5b | |||
b16c8972c7 | |||
1c9b62de07 | |||
fc89b01ba6 | |||
70e67c5daa | |||
5ad3b6018a | |||
ba278985e8 | |||
5300461c79 | |||
d0f60c47bf | |||
07f2470e36 | |||
f8f1e791a3 | |||
dc38640341 | |||
a7941a00c5 | |||
f4ffb21c8c | |||
ebdeb86703 | |||
bc13453155 | |||
23e5181cac | |||
452dcaa5d3 | |||
5905078e56 | |||
52361ed360 | |||
87b43c2171 | |||
ae88d7d8dc | |||
6d2ba7a75b | |||
03a641ed83 | |||
3e2197a82a | |||
79883dbce4 | |||
8a0e76c8d8 | |||
285a264aae | |||
22409d27c5 | |||
42b6c089f3 | |||
0ad29a5960 | |||
4c251578e9 | |||
7638f5c595 | |||
edd4d8ee11 | |||
2b11b7ef8c | |||
94a4d41a65 |
14
.github/workflows/freebsd.yml
vendored
14
.github/workflows/freebsd.yml
vendored
@ -4,19 +4,25 @@ on: [ push, pull_request ]
|
||||
|
||||
jobs:
|
||||
clang:
|
||||
runs-on: macos-latest # until https://github.com/actions/runner/issues/385
|
||||
# Run actions in a FreeBSD vm on the macos-10.15 runner
|
||||
# https://github.com/actions/runner/issues/385 - for FreeBSD runner support
|
||||
# https://github.com/actions/virtual-environments/issues/4060 - for lack of VirtualBox on MacOS 11 runners
|
||||
runs-on: macos-10.15
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Test in FreeBSD VM
|
||||
uses: vmactions/freebsd-vm@v0.0.9 # aka FreeBSD 12.2
|
||||
uses: vmactions/freebsd-vm@v0.1.6 # aka FreeBSD 13.0
|
||||
with:
|
||||
mem: 2048
|
||||
usesh: true
|
||||
prepare: |
|
||||
export CPPFLAGS=-isystem/usr/local/include LDFLAGS=-L/usr/local/lib # sndio
|
||||
sed -i '' 's/quarterly/latest/' /etc/pkg/FreeBSD.conf
|
||||
pkg install -y git # subprojects/date
|
||||
pkg install -y gtk-layer-shell gtkmm30 jsoncpp libdbusmenu sndio \
|
||||
libfmt libmpdclient libudev-devd meson pkgconf pulseaudio scdoc spdlog
|
||||
pkg install -y catch evdev-proto gtk-layer-shell gtkmm30 jsoncpp \
|
||||
libdbusmenu libevdev libfmt libmpdclient libudev-devd meson \
|
||||
pkgconf pulseaudio scdoc sndio spdlog wayland-protocols
|
||||
run: |
|
||||
meson build -Dman-pages=enabled
|
||||
ninja -C build
|
||||
meson test -C build --no-rebuild --print-errorlogs --suite waybar
|
||||
|
27
.github/workflows/linux.yml
vendored
Normal file
27
.github/workflows/linux.yml
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
name: linux
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
distro:
|
||||
- alpine
|
||||
- archlinux
|
||||
- debian
|
||||
- fedora
|
||||
- opensuse
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: alexays/waybar:${{ matrix.distro }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: configure
|
||||
run: meson -Dman-pages=enabled build
|
||||
- name: build
|
||||
run: ninja -C build
|
||||
- name: test
|
||||
run: meson test -C build --no-rebuild --print-errorlogs --suite waybar
|
38
.travis.yml
38
.travis.yml
@ -1,38 +0,0 @@
|
||||
language: cpp
|
||||
|
||||
services:
|
||||
- docker
|
||||
|
||||
git:
|
||||
submodules: false
|
||||
|
||||
env:
|
||||
- distro: debian
|
||||
- distro: archlinux
|
||||
- distro: fedora
|
||||
- distro: alpine
|
||||
- distro: opensuse
|
||||
|
||||
before_install:
|
||||
- docker pull alexays/waybar:${distro}
|
||||
- find . -type f \( -name '*.cpp' -o -name '*.h' \) -print0 | xargs -r0 clang-format -i
|
||||
|
||||
script:
|
||||
- echo FROM alexays/waybar:${distro} > Dockerfile
|
||||
- echo ADD . /root >> Dockerfile
|
||||
- docker build -t waybar .
|
||||
- docker run waybar /bin/sh -c "cd /root && meson build -Dman-pages=enabled && ninja -C build"
|
||||
|
||||
jobs:
|
||||
include:
|
||||
- os: freebsd
|
||||
compiler: clang
|
||||
env:
|
||||
before_install:
|
||||
- export CPPFLAGS+=-isystem/usr/local/include LDFLAGS+=-L/usr/local/lib # sndio
|
||||
- sudo sed -i '' 's/quarterly/latest/' /etc/pkg/FreeBSD.conf
|
||||
- sudo pkg install -y gtk-layer-shell gtkmm30 jsoncpp libdbusmenu sndio
|
||||
libfmt libmpdclient libudev-devd meson pulseaudio scdoc spdlog
|
||||
script:
|
||||
- meson build -Dman-pages=enabled
|
||||
- ninja -C build
|
@ -2,4 +2,4 @@
|
||||
|
||||
FROM alpine:latest
|
||||
|
||||
RUN apk add --no-cache git meson alpine-sdk libinput-dev wayland-dev wayland-protocols mesa-dev libxkbcommon-dev eudev-dev pixman-dev gtkmm3-dev jsoncpp-dev pugixml-dev libnl3-dev pulseaudio-dev libmpdclient-dev sndio-dev scdoc
|
||||
RUN apk add --no-cache git meson alpine-sdk libinput-dev wayland-dev wayland-protocols mesa-dev libxkbcommon-dev eudev-dev pixman-dev gtkmm3-dev jsoncpp-dev pugixml-dev libnl3-dev pulseaudio-dev libmpdclient-dev sndio-dev scdoc libxkbcommon tzdata
|
||||
|
@ -1,6 +1,6 @@
|
||||
# vim: ft=Dockerfile
|
||||
|
||||
FROM archlinux/base:latest
|
||||
FROM archlinux:base-devel
|
||||
|
||||
RUN pacman -Syu --noconfirm && \
|
||||
pacman -S git meson base-devel libinput wayland wayland-protocols pixman libxkbcommon mesa gtkmm3 jsoncpp pugixml scdoc libpulse libdbusmenu-gtk3 libmpdclient gobject-introspection --noconfirm
|
||||
pacman -S git meson base-devel libinput wayland wayland-protocols pixman libxkbcommon mesa gtkmm3 jsoncpp pugixml scdoc libpulse libdbusmenu-gtk3 libmpdclient gobject-introspection --noconfirm libxkbcommon
|
||||
|
@ -3,5 +3,5 @@
|
||||
FROM debian:sid
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y build-essential meson ninja-build git pkg-config libinput10 libpugixml-dev libinput-dev wayland-protocols libwayland-client0 libwayland-cursor0 libwayland-dev libegl1-mesa-dev libgles2-mesa-dev libgbm-dev libxkbcommon-dev libudev-dev libpixman-1-dev libgtkmm-3.0-dev libjsoncpp-dev scdoc libdbusmenu-gtk3-dev libnl-3-dev libnl-genl-3-dev libpulse-dev libmpdclient-dev gobject-introspection libgirepository1.0-dev && \
|
||||
apt-get install -y build-essential meson ninja-build git pkg-config libinput10 libpugixml-dev libinput-dev wayland-protocols libwayland-client0 libwayland-cursor0 libwayland-dev libegl1-mesa-dev libgles2-mesa-dev libgbm-dev libxkbcommon-dev libudev-dev libpixman-1-dev libgtkmm-3.0-dev libjsoncpp-dev scdoc libdbusmenu-gtk3-dev libnl-3-dev libnl-genl-3-dev libpulse-dev libmpdclient-dev gobject-introspection libgirepository1.0-dev libxkbcommon-dev libxkbregistry-dev libxkbregistry0 && \
|
||||
apt-get clean
|
||||
|
@ -1,7 +1,12 @@
|
||||
# vim: ft=Dockerfile
|
||||
|
||||
FROM fedora:32
|
||||
FROM fedora:latest
|
||||
|
||||
RUN dnf install sway meson git libinput-devel wayland-devel wayland-protocols-devel pugixml-devel egl-wayland-devel mesa-libEGL-devel mesa-libGLES-devel mesa-libgbm-devel libxkbcommon-devel libudev-devel pixman-devel gtkmm30-devel jsoncpp-devel scdoc -y && \
|
||||
dnf group install "C Development Tools and Libraries" -y && \
|
||||
RUN dnf install -y @c-development git-core meson scdoc 'pkgconfig(date)' \
|
||||
'pkgconfig(dbusmenu-gtk3-0.4)' 'pkgconfig(fmt)' 'pkgconfig(gdk-pixbuf-2.0)' \
|
||||
'pkgconfig(gio-unix-2.0)' 'pkgconfig(gtk-layer-shell-0)' 'pkgconfig(gtkmm-3.0)' \
|
||||
'pkgconfig(jsoncpp)' 'pkgconfig(libinput)' 'pkgconfig(libmpdclient)' \
|
||||
'pkgconfig(libnl-3.0)' 'pkgconfig(libnl-genl-3.0)' 'pkgconfig(libpulse)' \
|
||||
'pkgconfig(libudev)' 'pkgconfig(pugixml)' 'pkgconfig(sigc++-2.0)' 'pkgconfig(spdlog)' \
|
||||
'pkgconfig(wayland-client)' 'pkgconfig(wayland-cursor)' 'pkgconfig(wayland-protocols)' 'pkgconfig(xkbregistry)' && \
|
||||
dnf clean all -y
|
||||
|
@ -3,5 +3,7 @@
|
||||
FROM opensuse/tumbleweed:latest
|
||||
|
||||
RUN zypper -n up && \
|
||||
zypper addrepo https://download.opensuse.org/repositories/X11:Wayland/openSUSE_Tumbleweed/X11:Wayland.repo | echo 'a' && \
|
||||
zypper -n refresh && \
|
||||
zypper -n install -t pattern devel_C_C++ && \
|
||||
zypper -n install git meson clang libinput10 libinput-devel pugixml-devel libwayland-client0 libwayland-cursor0 wayland-protocols-devel wayland-devel Mesa-libEGL-devel Mesa-libGLESv2-devel libgbm-devel libxkbcommon-devel libudev-devel libpixman-1-0-devel gtkmm3-devel jsoncpp-devel scdoc
|
||||
zypper -n install git meson clang libinput10 libinput-devel pugixml-devel libwayland-client0 libwayland-cursor0 wayland-protocols-devel wayland-devel Mesa-libEGL-devel Mesa-libGLESv2-devel libgbm-devel libxkbcommon-devel libudev-devel libpixman-1-0-devel gtkmm3-devel jsoncpp-devel libxkbregistry-devel scdoc
|
||||
|
3
Makefile
3
Makefile
@ -16,5 +16,8 @@ install: build
|
||||
run: build
|
||||
./build/waybar
|
||||
|
||||
debug-run: build-debug
|
||||
./build/waybar --log-level debug
|
||||
|
||||
clean:
|
||||
rm -rf build
|
||||
|
10
README.md
10
README.md
@ -1,8 +1,8 @@
|
||||
# Waybar [](https://travis-ci.org/Alexays/Waybar) [](LICENSE) [](https://paypal.me/ARouillard)<br>
|
||||
# Waybar [](LICENSE) [](https://paypal.me/ARouillard)<br>
|
||||
|
||||
> Highly customizable Wayland bar for Sway and Wlroots based compositors.<br>
|
||||
> Available in Arch [community](https://www.archlinux.org/packages/community/x86_64/waybar/) or
|
||||
[AUR](https://aur.archlinux.org/packages/waybar-git/), [openSUSE](https://build.opensuse.org/package/show/X11:Wayland/waybar), and [Alpine Linux](https://pkgs.alpinelinux.org/packages?name=waybar)<br>
|
||||
[AUR](https://aur.archlinux.org/packages/waybar-git/), [Gentoo](https://packages.gentoo.org/packages/gui-apps/waybar), [openSUSE](https://build.opensuse.org/package/show/X11:Wayland/waybar), and [Alpine Linux](https://pkgs.alpinelinux.org/packages?name=waybar)<br>
|
||||
> *Waybar [examples](https://github.com/Alexays/Waybar/wiki/Examples)*
|
||||
|
||||
#### Current features
|
||||
@ -68,6 +68,8 @@ libappindicator-gtk3 [Tray module]
|
||||
libdbusmenu-gtk3 [Tray module]
|
||||
libmpdclient [MPD module]
|
||||
libsndio [sndio module]
|
||||
libevdev [KeyboardState module]
|
||||
xkbregistry
|
||||
```
|
||||
|
||||
**Build dependencies**
|
||||
@ -86,6 +88,7 @@ sudo apt install \
|
||||
clang-tidy \
|
||||
gobject-introspection \
|
||||
libdbusmenu-gtk3-dev \
|
||||
libevdev-dev \
|
||||
libfmt-dev \
|
||||
libgirepository1.0-dev \
|
||||
libgtk-3-dev \
|
||||
@ -99,7 +102,8 @@ sudo apt install \
|
||||
libsigc++-2.0-dev \
|
||||
libspdlog-dev \
|
||||
libwayland-dev \
|
||||
scdoc
|
||||
scdoc \
|
||||
libxkbregistry-dev
|
||||
```
|
||||
|
||||
|
||||
|
25
include/AIconLabel.hpp
Normal file
25
include/AIconLabel.hpp
Normal file
@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <gtkmm/box.h>
|
||||
#include <gtkmm/image.h>
|
||||
|
||||
#include "ALabel.hpp"
|
||||
|
||||
namespace waybar {
|
||||
|
||||
class AIconLabel : public ALabel {
|
||||
public:
|
||||
AIconLabel(const Json::Value &config, const std::string &name, const std::string &id,
|
||||
const std::string &format, uint16_t interval = 0, bool ellipsize = false,
|
||||
bool enable_click = false, bool enable_scroll = false);
|
||||
virtual ~AIconLabel() = default;
|
||||
virtual auto update() -> void;
|
||||
|
||||
protected:
|
||||
Gtk::Image image_;
|
||||
Gtk::Box box_;
|
||||
|
||||
bool iconEnabled() const;
|
||||
};
|
||||
|
||||
} // namespace waybar
|
@ -14,7 +14,7 @@ class ALabel : public AModule {
|
||||
virtual ~ALabel() = default;
|
||||
virtual auto update() -> void;
|
||||
virtual std::string getIcon(uint16_t, const std::string &alt = "", uint16_t max = 0);
|
||||
virtual std::string getIcon(uint16_t, std::vector<std::string> &alts, uint16_t max = 0);
|
||||
virtual std::string getIcon(uint16_t, const std::vector<std::string> &alts, uint16_t max = 0);
|
||||
|
||||
protected:
|
||||
Gtk::Label label_;
|
||||
|
@ -8,6 +8,9 @@
|
||||
#include <gtkmm/window.h>
|
||||
#include <json/json.h>
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "AModule.hpp"
|
||||
#include "xdg-output-unstable-v1-client-protocol.h"
|
||||
|
||||
@ -36,6 +39,19 @@ struct bar_margins {
|
||||
int left = 0;
|
||||
};
|
||||
|
||||
struct bar_mode {
|
||||
bar_layer layer;
|
||||
bool exclusive;
|
||||
bool passthrough;
|
||||
bool visible;
|
||||
};
|
||||
|
||||
#ifdef HAVE_SWAY
|
||||
namespace modules::sway {
|
||||
class BarIpcClient;
|
||||
}
|
||||
#endif // HAVE_SWAY
|
||||
|
||||
class BarSurface {
|
||||
protected:
|
||||
BarSurface() = default;
|
||||
@ -44,6 +60,7 @@ class BarSurface {
|
||||
virtual void setExclusiveZone(bool enable) = 0;
|
||||
virtual void setLayer(bar_layer layer) = 0;
|
||||
virtual void setMargins(const struct bar_margins &margins) = 0;
|
||||
virtual void setPassThrough(bool enable) = 0;
|
||||
virtual void setPosition(const std::string_view &position) = 0;
|
||||
virtual void setSize(uint32_t width, uint32_t height) = 0;
|
||||
virtual void commit(){};
|
||||
@ -53,37 +70,56 @@ class BarSurface {
|
||||
|
||||
class Bar {
|
||||
public:
|
||||
using bar_mode_map = std::map<std::string_view, struct bar_mode>;
|
||||
static const bar_mode_map PRESET_MODES;
|
||||
static const std::string_view MODE_DEFAULT;
|
||||
static const std::string_view MODE_INVISIBLE;
|
||||
|
||||
Bar(struct waybar_output *w_output, const Json::Value &);
|
||||
Bar(const Bar &) = delete;
|
||||
~Bar() = default;
|
||||
~Bar();
|
||||
|
||||
void setMode(const std::string_view &);
|
||||
void setVisible(bool visible);
|
||||
void toggle();
|
||||
void handleSignal(int);
|
||||
|
||||
struct waybar_output *output;
|
||||
Json::Value config;
|
||||
struct wl_surface * surface;
|
||||
struct wl_surface *surface;
|
||||
bool visible = true;
|
||||
bool vertical = false;
|
||||
Gtk::Window window;
|
||||
|
||||
#ifdef HAVE_SWAY
|
||||
std::string bar_id;
|
||||
#endif
|
||||
|
||||
private:
|
||||
void onMap(GdkEventAny *);
|
||||
auto setupWidgets() -> void;
|
||||
void getModules(const Factory &, const std::string &);
|
||||
void getModules(const Factory &, const std::string &, Gtk::Box*);
|
||||
void setupAltFormatKeyForModule(const std::string &module_name);
|
||||
void setupAltFormatKeyForModuleList(const char *module_list_name);
|
||||
void setMode(const bar_mode &);
|
||||
|
||||
/* Copy initial set of modes to allow customization */
|
||||
bar_mode_map configured_modes = PRESET_MODES;
|
||||
std::string last_mode_{MODE_DEFAULT};
|
||||
|
||||
std::unique_ptr<BarSurface> surface_impl_;
|
||||
bar_layer layer_;
|
||||
Gtk::Box left_;
|
||||
Gtk::Box center_;
|
||||
Gtk::Box right_;
|
||||
Gtk::Box box_;
|
||||
std::vector<std::unique_ptr<waybar::AModule>> modules_left_;
|
||||
std::vector<std::unique_ptr<waybar::AModule>> modules_center_;
|
||||
std::vector<std::unique_ptr<waybar::AModule>> modules_right_;
|
||||
std::vector<std::shared_ptr<waybar::AModule>> modules_left_;
|
||||
std::vector<std::shared_ptr<waybar::AModule>> modules_center_;
|
||||
std::vector<std::shared_ptr<waybar::AModule>> modules_right_;
|
||||
#ifdef HAVE_SWAY
|
||||
using BarIpcClient = modules::sway::BarIpcClient;
|
||||
std::unique_ptr<BarIpcClient> _ipc_client;
|
||||
#endif
|
||||
std::vector<std::shared_ptr<waybar::AModule>> modules_all_;
|
||||
};
|
||||
|
||||
} // namespace waybar
|
||||
|
@ -3,11 +3,10 @@
|
||||
#include <fmt/format.h>
|
||||
#include <gdk/gdk.h>
|
||||
#include <gdk/gdkwayland.h>
|
||||
#include <unistd.h>
|
||||
#include <wayland-client.h>
|
||||
#include <wordexp.h>
|
||||
|
||||
#include "bar.hpp"
|
||||
#include "config.hpp"
|
||||
|
||||
struct zwlr_layer_shell_v1;
|
||||
struct zwp_idle_inhibitor_v1;
|
||||
@ -29,16 +28,14 @@ class Client {
|
||||
struct zxdg_output_manager_v1 * xdg_output_manager = nullptr;
|
||||
struct zwp_idle_inhibit_manager_v1 *idle_inhibit_manager = nullptr;
|
||||
std::vector<std::unique_ptr<Bar>> bars;
|
||||
Config config;
|
||||
std::string bar_id;
|
||||
|
||||
private:
|
||||
Client() = default;
|
||||
std::tuple<const std::string, const std::string> getConfigs(const std::string &config,
|
||||
const std::string &style) const;
|
||||
void bindInterfaces();
|
||||
const std::string getValidPath(const std::vector<std::string> &paths) const;
|
||||
const std::string getStyle(const std::string &style);
|
||||
void bindInterfaces();
|
||||
void handleOutput(struct waybar_output &output);
|
||||
bool isValidOutput(const Json::Value &config, struct waybar_output &output);
|
||||
auto setupConfig(const std::string &config_file) -> void;
|
||||
auto setupCss(const std::string &css_file) -> void;
|
||||
struct waybar_output & getOutput(void *);
|
||||
std::vector<Json::Value> getOutputConfigs(struct waybar_output &output);
|
||||
@ -53,7 +50,6 @@ class Client {
|
||||
void handleMonitorRemoved(Glib::RefPtr<Gdk::Monitor> monitor);
|
||||
void handleDeferredMonitorRemoval(Glib::RefPtr<Gdk::Monitor> monitor);
|
||||
|
||||
Json::Value config_;
|
||||
Glib::RefPtr<Gtk::StyleContext> style_context_;
|
||||
Glib::RefPtr<Gtk::CssProvider> css_provider_;
|
||||
std::list<struct waybar_output> outputs_;
|
||||
|
39
include/config.hpp
Normal file
39
include/config.hpp
Normal file
@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include <json/json.h>
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#ifndef SYSCONFDIR
|
||||
#define SYSCONFDIR "/etc"
|
||||
#endif
|
||||
|
||||
namespace waybar {
|
||||
|
||||
class Config {
|
||||
public:
|
||||
static const std::vector<std::string> CONFIG_DIRS;
|
||||
|
||||
/* Try to find any of provided names in the supported set of config directories */
|
||||
static std::optional<std::string> findConfigPath(
|
||||
const std::vector<std::string> &names, const std::vector<std::string> &dirs = CONFIG_DIRS);
|
||||
|
||||
Config() = default;
|
||||
|
||||
void load(const std::string &config);
|
||||
|
||||
Json::Value &getConfig() { return config_; }
|
||||
|
||||
std::vector<Json::Value> getOutputConfigs(const std::string &name, const std::string &identifier);
|
||||
|
||||
private:
|
||||
void setupConfig(Json::Value &dst, const std::string &config_file, int depth);
|
||||
void resolveConfigIncludes(Json::Value &config, int depth);
|
||||
void mergeConfig(Json::Value &a_config_, Json::Value &b_config_);
|
||||
|
||||
std::string config_file_;
|
||||
|
||||
Json::Value config_;
|
||||
};
|
||||
} // namespace waybar
|
@ -14,6 +14,7 @@
|
||||
#endif
|
||||
#ifdef HAVE_WLR
|
||||
#include "modules/wlr/taskbar.hpp"
|
||||
#include "modules/wlr/workspace_manager.hpp"
|
||||
#endif
|
||||
#ifdef HAVE_RIVER
|
||||
#include "modules/river/tags.hpp"
|
||||
@ -38,6 +39,9 @@
|
||||
#ifdef HAVE_LIBUDEV
|
||||
#include "modules/backlight.hpp"
|
||||
#endif
|
||||
#ifdef HAVE_LIBEVDEV
|
||||
#include "modules/keyboard_state.hpp"
|
||||
#endif
|
||||
#ifdef HAVE_LIBPULSE
|
||||
#include "modules/pulseaudio.hpp"
|
||||
#endif
|
||||
@ -47,6 +51,9 @@
|
||||
#ifdef HAVE_LIBSNDIO
|
||||
#include "modules/sndio.hpp"
|
||||
#endif
|
||||
#ifdef HAVE_GIO_UNIX
|
||||
#include "modules/inhibitor.hpp"
|
||||
#endif
|
||||
#include "bar.hpp"
|
||||
#include "modules/custom.hpp"
|
||||
#include "modules/temperature.hpp"
|
||||
|
21
include/group.hpp
Normal file
21
include/group.hpp
Normal file
@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include <gtkmm/widget.h>
|
||||
#include <gtkmm/box.h>
|
||||
#include <json/json.h>
|
||||
#include "AModule.hpp"
|
||||
#include "bar.hpp"
|
||||
#include "factory.hpp"
|
||||
|
||||
namespace waybar {
|
||||
|
||||
class Group : public AModule {
|
||||
public:
|
||||
Group(const std::string&, const Bar&, const Json::Value&);
|
||||
~Group() = default;
|
||||
auto update() -> void;
|
||||
operator Gtk::Widget &();
|
||||
Gtk::Box box;
|
||||
};
|
||||
|
||||
} // namespace waybar
|
@ -1,21 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include <fmt/format.h>
|
||||
#if FMT_VERSION < 60000
|
||||
#include <fmt/time.h>
|
||||
#else
|
||||
#include <fmt/chrono.h>
|
||||
#endif
|
||||
#include <date/tz.h>
|
||||
#include "ALabel.hpp"
|
||||
#include "util/sleeper_thread.hpp"
|
||||
|
||||
namespace waybar::modules {
|
||||
namespace waybar {
|
||||
|
||||
struct waybar_time {
|
||||
std::locale locale;
|
||||
date::zoned_seconds ztime;
|
||||
};
|
||||
struct waybar_time;
|
||||
|
||||
namespace modules {
|
||||
|
||||
const std::string kCalendarPlaceholder = "calendar";
|
||||
const std::string KTimezonedTimeListPlaceholder = "timezoned_time_list";
|
||||
|
||||
class Clock : public ALabel {
|
||||
public:
|
||||
@ -26,17 +22,22 @@ class Clock : public ALabel {
|
||||
private:
|
||||
util::SleeperThread thread_;
|
||||
std::locale locale_;
|
||||
const date::time_zone* time_zone_;
|
||||
bool fixed_time_zone_;
|
||||
int time_zone_idx_;
|
||||
std::vector<const date::time_zone*> time_zones_;
|
||||
int current_time_zone_idx_;
|
||||
date::year_month_day cached_calendar_ymd_ = date::January/1/0;
|
||||
std::string cached_calendar_text_;
|
||||
bool is_calendar_in_tooltip_;
|
||||
bool is_timezoned_list_in_tooltip_;
|
||||
|
||||
bool handleScroll(GdkEventScroll* e);
|
||||
|
||||
auto calendar_text(const waybar_time& wtime) -> std::string;
|
||||
auto weekdays_header(const date::weekday& first_dow, std::ostream& os) -> void;
|
||||
auto first_day_of_week() -> date::weekday;
|
||||
const date::time_zone* current_timezone();
|
||||
bool is_timezone_fixed();
|
||||
auto timezones_text(std::chrono::_V2::system_clock::time_point *now) -> std::string;
|
||||
};
|
||||
|
||||
} // namespace waybar::modules
|
||||
} // namespace modules
|
||||
} // namespace waybar
|
||||
|
@ -19,11 +19,11 @@ class Cpu : public ALabel {
|
||||
auto update() -> void;
|
||||
|
||||
private:
|
||||
double getCpuLoad();
|
||||
std::tuple<uint16_t, std::string> getCpuUsage();
|
||||
std::tuple<float, float, float> getCpuFrequency();
|
||||
std::vector<std::tuple<size_t, size_t>> parseCpuinfo();
|
||||
std::vector<float> parseCpuFrequencies();
|
||||
double getCpuLoad();
|
||||
std::tuple<std::vector<uint16_t>, std::string> getCpuUsage();
|
||||
std::tuple<float, float, float> getCpuFrequency();
|
||||
std::vector<std::tuple<size_t, size_t>> parseCpuinfo();
|
||||
std::vector<float> parseCpuFrequencies();
|
||||
|
||||
std::vector<std::tuple<size_t, size_t>> prev_times_;
|
||||
|
||||
|
27
include/modules/inhibitor.hpp
Normal file
27
include/modules/inhibitor.hpp
Normal file
@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <gio/gio.h>
|
||||
|
||||
#include "ALabel.hpp"
|
||||
#include "bar.hpp"
|
||||
|
||||
namespace waybar::modules {
|
||||
|
||||
class Inhibitor : public ALabel {
|
||||
public:
|
||||
Inhibitor(const std::string&, const waybar::Bar&, const Json::Value&);
|
||||
~Inhibitor() override;
|
||||
auto update() -> void;
|
||||
auto activated() -> bool;
|
||||
|
||||
private:
|
||||
auto handleToggle(::GdkEventButton* const& e) -> bool;
|
||||
|
||||
const std::unique_ptr<::GDBusConnection, void(*)(::GDBusConnection*)> dbus_;
|
||||
const std::string inhibitors_;
|
||||
int handle_ = -1;
|
||||
};
|
||||
|
||||
} // namespace waybar::modules
|
45
include/modules/keyboard_state.hpp
Normal file
45
include/modules/keyboard_state.hpp
Normal file
@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include <fmt/format.h>
|
||||
#if FMT_VERSION < 60000
|
||||
#include <fmt/time.h>
|
||||
#else
|
||||
#include <fmt/chrono.h>
|
||||
#endif
|
||||
#include "AModule.hpp"
|
||||
#include "bar.hpp"
|
||||
#include "util/sleeper_thread.hpp"
|
||||
#include <gtkmm/label.h>
|
||||
|
||||
extern "C" {
|
||||
#include <libevdev/libevdev.h>
|
||||
}
|
||||
|
||||
namespace waybar::modules {
|
||||
|
||||
class KeyboardState : public AModule {
|
||||
public:
|
||||
KeyboardState(const std::string&, const waybar::Bar&, const Json::Value&);
|
||||
~KeyboardState();
|
||||
auto update() -> void;
|
||||
|
||||
private:
|
||||
Gtk::Box box_;
|
||||
Gtk::Label numlock_label_;
|
||||
Gtk::Label capslock_label_;
|
||||
Gtk::Label scrolllock_label_;
|
||||
|
||||
std::string numlock_format_;
|
||||
std::string capslock_format_;
|
||||
std::string scrolllock_format_;
|
||||
const std::chrono::seconds interval_;
|
||||
std::string icon_locked_;
|
||||
std::string icon_unlocked_;
|
||||
|
||||
int fd_;
|
||||
libevdev* dev_;
|
||||
|
||||
util::SleeperThread thread_;
|
||||
};
|
||||
|
||||
} // namespace waybar::modules
|
@ -2,9 +2,7 @@
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <fmt/format.h>
|
||||
#include <ifaddrs.h>
|
||||
#include <linux/nl80211.h>
|
||||
#include <net/if.h>
|
||||
#include <netlink/genl/ctrl.h>
|
||||
#include <netlink/genl/genl.h>
|
||||
#include <netlink/netlink.h>
|
||||
@ -28,26 +26,24 @@ class Network : public ALabel {
|
||||
static const uint8_t EPOLL_MAX = 200;
|
||||
|
||||
static int handleEvents(struct nl_msg*, void*);
|
||||
static int handleEventsDone(struct nl_msg*, void*);
|
||||
static int handleScan(struct nl_msg*, void*);
|
||||
|
||||
void askForStateDump(void);
|
||||
|
||||
void worker();
|
||||
void createInfoSocket();
|
||||
void createEventSocket();
|
||||
int getExternalInterface(int skip_idx = -1) const;
|
||||
void getInterfaceAddress();
|
||||
int netlinkRequest(void*, uint32_t, uint32_t groups = 0) const;
|
||||
int netlinkResponse(void*, uint32_t, uint32_t groups = 0) const;
|
||||
void parseEssid(struct nlattr**);
|
||||
void parseSignal(struct nlattr**);
|
||||
void parseFreq(struct nlattr**);
|
||||
bool associatedOrJoined(struct nlattr**);
|
||||
bool checkInterface(struct ifinfomsg* rtif, std::string name);
|
||||
int getPreferredIface(int skip_idx = -1, bool wait = true) const;
|
||||
bool checkInterface(std::string name);
|
||||
auto getInfo() -> void;
|
||||
void checkNewInterface(struct ifinfomsg* rtif);
|
||||
const std::string getNetworkState() const;
|
||||
void clearIface();
|
||||
bool wildcardMatch(const std::string& pattern, const std::string& text) const;
|
||||
std::optional<std::pair<unsigned long long, unsigned long long>> readBandwidthUsage();
|
||||
|
||||
int ifid_;
|
||||
sa_family_t family_;
|
||||
@ -59,18 +55,27 @@ class Network : public ALabel {
|
||||
int nl80211_id_;
|
||||
std::mutex mutex_;
|
||||
|
||||
bool want_route_dump_;
|
||||
bool want_link_dump_;
|
||||
bool want_addr_dump_;
|
||||
bool dump_in_progress_;
|
||||
|
||||
unsigned long long bandwidth_down_total_;
|
||||
unsigned long long bandwidth_up_total_;
|
||||
|
||||
std::string state_;
|
||||
std::string essid_;
|
||||
bool carrier_;
|
||||
std::string ifname_;
|
||||
std::string ipaddr_;
|
||||
std::string gwaddr_;
|
||||
std::string netmask_;
|
||||
int cidr_;
|
||||
int32_t signal_strength_dbm_;
|
||||
uint8_t signal_strength_;
|
||||
uint32_t frequency_;
|
||||
std::string signal_strength_app_;
|
||||
float frequency_;
|
||||
uint32_t route_priority;
|
||||
|
||||
util::SleeperThread thread_;
|
||||
util::SleeperThread thread_timer_;
|
||||
|
@ -24,7 +24,7 @@ class Pulseaudio : public ALabel {
|
||||
static void volumeModifyCb(pa_context*, int, void*);
|
||||
|
||||
bool handleScroll(GdkEventScroll* e);
|
||||
const std::string getPortIcon() const;
|
||||
const std::vector<std::string> getPulseIcon() const;
|
||||
|
||||
pa_threaded_mainloop* mainloop_;
|
||||
pa_mainloop_api* mainloop_api_;
|
||||
@ -38,7 +38,8 @@ class Pulseaudio : public ALabel {
|
||||
std::string form_factor_;
|
||||
std::string desc_;
|
||||
std::string monitor_;
|
||||
std::string default_sink_name_;
|
||||
std::string current_sink_name_;
|
||||
bool current_sink_running_;
|
||||
// SOURCE
|
||||
uint32_t source_idx_{0};
|
||||
uint16_t source_volume_;
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include "AModule.hpp"
|
||||
#include "bar.hpp"
|
||||
#include "river-status-unstable-v1-client-protocol.h"
|
||||
#include "river-control-unstable-v1-client-protocol.h"
|
||||
#include "xdg-output-unstable-v1-client-protocol.h"
|
||||
|
||||
namespace waybar::modules::river {
|
||||
@ -18,8 +19,14 @@ class Tags : public waybar::AModule {
|
||||
// Handlers for wayland events
|
||||
void handle_focused_tags(uint32_t tags);
|
||||
void handle_view_tags(struct wl_array *tags);
|
||||
void handle_urgent_tags(uint32_t tags);
|
||||
|
||||
void handle_primary_clicked(uint32_t tag);
|
||||
bool handle_button_press(GdkEventButton *event_button, uint32_t tag);
|
||||
|
||||
struct zriver_status_manager_v1 *status_manager_;
|
||||
struct zriver_control_v1 *control_;
|
||||
struct wl_seat *seat_;
|
||||
|
||||
private:
|
||||
const waybar::Bar & bar_;
|
||||
|
@ -5,13 +5,15 @@
|
||||
#include <glibmm/refptr.h>
|
||||
#include <json/json.h>
|
||||
#include <tuple>
|
||||
#include "bar.hpp"
|
||||
#include "modules/sni/item.hpp"
|
||||
|
||||
namespace waybar::modules::SNI {
|
||||
|
||||
class Host {
|
||||
public:
|
||||
Host(const std::size_t id, const Json::Value&, const std::function<void(std::unique_ptr<Item>&)>&,
|
||||
Host(const std::size_t id, const Json::Value&, const Bar&,
|
||||
const std::function<void(std::unique_ptr<Item>&)>&,
|
||||
const std::function<void(std::unique_ptr<Item>&)>&);
|
||||
~Host();
|
||||
|
||||
@ -36,6 +38,7 @@ class Host {
|
||||
GCancellable* cancellable_ = nullptr;
|
||||
SnWatcher* watcher_ = nullptr;
|
||||
const Json::Value& config_;
|
||||
const Bar& bar_;
|
||||
const std::function<void(std::unique_ptr<Item>&)> on_add_;
|
||||
const std::function<void(std::unique_ptr<Item>&)> on_remove_;
|
||||
};
|
||||
|
@ -11,11 +11,21 @@
|
||||
#include <libdbusmenu-gtk/dbusmenu-gtk.h>
|
||||
#include <sigc++/trackable.h>
|
||||
|
||||
#include <set>
|
||||
#include <string_view>
|
||||
|
||||
#include "bar.hpp"
|
||||
|
||||
namespace waybar::modules::SNI {
|
||||
|
||||
struct ToolTip {
|
||||
Glib::ustring icon_name;
|
||||
Glib::ustring text;
|
||||
};
|
||||
|
||||
class Item : public sigc::trackable {
|
||||
public:
|
||||
Item(const std::string&, const std::string&, const Json::Value&);
|
||||
Item(const std::string&, const std::string&, const Json::Value&, const Bar&);
|
||||
~Item() = default;
|
||||
|
||||
std::string bus_name;
|
||||
@ -27,10 +37,8 @@ class Item : public sigc::trackable {
|
||||
Gtk::EventBox event_box;
|
||||
std::string category;
|
||||
std::string id;
|
||||
std::string status;
|
||||
|
||||
std::string title;
|
||||
int32_t window_id;
|
||||
std::string icon_name;
|
||||
Glib::RefPtr<Gdk::Pixbuf> icon_pixmap;
|
||||
Glib::RefPtr<Gtk::IconTheme> icon_theme;
|
||||
@ -39,6 +47,7 @@ class Item : public sigc::trackable {
|
||||
std::string attention_movie_name;
|
||||
std::string icon_theme_path;
|
||||
std::string menu;
|
||||
ToolTip tooltip;
|
||||
DbusmenuGtkMenu* dbus_menu = nullptr;
|
||||
Gtk::Menu* gtk_menu = nullptr;
|
||||
/**
|
||||
@ -49,8 +58,10 @@ class Item : public sigc::trackable {
|
||||
bool item_is_menu = true;
|
||||
|
||||
private:
|
||||
void onConfigure(GdkEventConfigure* ev);
|
||||
void proxyReady(Glib::RefPtr<Gio::AsyncResult>& result);
|
||||
void setProperty(const Glib::ustring& name, Glib::VariantBase& value);
|
||||
void setStatus(const Glib::ustring& value);
|
||||
void getUpdatedProperties();
|
||||
void processUpdatedProperties(Glib::RefPtr<Gio::AsyncResult>& result);
|
||||
void onSignal(const Glib::ustring& sender_name, const Glib::ustring& signal_name,
|
||||
@ -58,14 +69,24 @@ class Item : public sigc::trackable {
|
||||
|
||||
void updateImage();
|
||||
Glib::RefPtr<Gdk::Pixbuf> extractPixBuf(GVariant* variant);
|
||||
Glib::RefPtr<Gdk::Pixbuf> getIconPixbuf();
|
||||
Glib::RefPtr<Gdk::Pixbuf> getIconByName(const std::string& name, int size);
|
||||
double getScaledIconSize();
|
||||
static void onMenuDestroyed(Item* self, GObject* old_menu_pointer);
|
||||
void makeMenu();
|
||||
bool handleClick(GdkEventButton* const& /*ev*/);
|
||||
bool handleScroll(GdkEventScroll* const&);
|
||||
|
||||
// smooth scrolling threshold
|
||||
gdouble scroll_threshold_ = 0;
|
||||
gdouble distance_scrolled_x_ = 0;
|
||||
gdouble distance_scrolled_y_ = 0;
|
||||
// visibility of items with Status == Passive
|
||||
bool show_passive_ = false;
|
||||
|
||||
Glib::RefPtr<Gio::DBus::Proxy> proxy_;
|
||||
Glib::RefPtr<Gio::Cancellable> cancellable_;
|
||||
bool update_pending_;
|
||||
std::set<std::string_view> update_pending_;
|
||||
};
|
||||
|
||||
} // namespace waybar::modules::SNI
|
||||
|
49
include/modules/sway/bar.hpp
Normal file
49
include/modules/sway/bar.hpp
Normal file
@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
|
||||
#include "modules/sway/ipc/client.hpp"
|
||||
#include "util/SafeSignal.hpp"
|
||||
#include "util/json.hpp"
|
||||
|
||||
namespace waybar {
|
||||
|
||||
class Bar;
|
||||
|
||||
namespace modules::sway {
|
||||
|
||||
/*
|
||||
* Supported subset of i3/sway IPC barconfig object
|
||||
*/
|
||||
struct swaybar_config {
|
||||
std::string id;
|
||||
std::string mode;
|
||||
std::string hidden_state;
|
||||
};
|
||||
|
||||
/**
|
||||
* swaybar IPC client
|
||||
*/
|
||||
class BarIpcClient {
|
||||
public:
|
||||
BarIpcClient(waybar::Bar& bar);
|
||||
|
||||
private:
|
||||
void onInitialConfig(const struct Ipc::ipc_response& res);
|
||||
void onIpcEvent(const struct Ipc::ipc_response&);
|
||||
void onConfigUpdate(const swaybar_config& config);
|
||||
void onVisibilityUpdate(bool visible_by_modifier);
|
||||
void update();
|
||||
|
||||
Bar& bar_;
|
||||
util::JsonParser parser_;
|
||||
Ipc ipc_;
|
||||
|
||||
swaybar_config bar_config_;
|
||||
bool visible_by_modifier_ = false;
|
||||
|
||||
SafeSignal<bool> signal_visible_;
|
||||
SafeSignal<swaybar_config> signal_config_;
|
||||
};
|
||||
|
||||
} // namespace modules::sway
|
||||
} // namespace waybar
|
@ -29,4 +29,8 @@ enum ipc_command_type {
|
||||
IPC_EVENT_BINDING = ((1 << 31) | 5),
|
||||
IPC_EVENT_SHUTDOWN = ((1 << 31) | 6),
|
||||
IPC_EVENT_TICK = ((1 << 31) | 7),
|
||||
|
||||
// sway-specific event types
|
||||
IPC_EVENT_BAR_STATE_UPDATE = ((1<<31) | 20),
|
||||
IPC_EVENT_INPUT = ((1<<31) | 21),
|
||||
};
|
||||
|
@ -1,6 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <xkbcommon/xkbregistry.h>
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include "ALabel.hpp"
|
||||
#include "bar.hpp"
|
||||
#include "client.hpp"
|
||||
@ -16,13 +21,50 @@ class Language : public ALabel, public sigc::trackable {
|
||||
auto update() -> void;
|
||||
|
||||
private:
|
||||
enum class DispayedShortFlag {
|
||||
None = 0,
|
||||
ShortName = 1,
|
||||
ShortDescription = 1 << 1
|
||||
};
|
||||
|
||||
struct Layout {
|
||||
std::string full_name;
|
||||
std::string short_name;
|
||||
std::string variant;
|
||||
std::string short_description;
|
||||
std::string country_flag() const;
|
||||
};
|
||||
|
||||
class XKBContext {
|
||||
public:
|
||||
XKBContext();
|
||||
~XKBContext();
|
||||
auto next_layout() -> Layout*;
|
||||
private:
|
||||
rxkb_context* context_ = nullptr;
|
||||
rxkb_layout* xkb_layout_ = nullptr;
|
||||
Layout* layout_ = nullptr;
|
||||
std::map<std::string, rxkb_layout*> base_layouts_by_name_;
|
||||
};
|
||||
|
||||
void onEvent(const struct Ipc::ipc_response&);
|
||||
void onCmd(const struct Ipc::ipc_response&);
|
||||
|
||||
std::string lang_;
|
||||
util::JsonParser parser_;
|
||||
std::mutex mutex_;
|
||||
Ipc ipc_;
|
||||
auto set_current_layout(std::string current_layout) -> void;
|
||||
auto init_layouts_map(const std::vector<std::string>& used_layouts) -> void;
|
||||
|
||||
const static std::string XKB_LAYOUT_NAMES_KEY;
|
||||
const static std::string XKB_ACTIVE_LAYOUT_NAME_KEY;
|
||||
|
||||
Layout layout_;
|
||||
std::string tooltip_format_ = "";
|
||||
std::map<std::string, Layout> layouts_map_;
|
||||
bool is_variant_displayed;
|
||||
std::byte displayed_short_flag = static_cast<std::byte>(DispayedShortFlag::None);
|
||||
|
||||
util::JsonParser parser_;
|
||||
std::mutex mutex_;
|
||||
Ipc ipc_;
|
||||
};
|
||||
|
||||
} // namespace waybar::modules::sway
|
||||
|
@ -2,7 +2,8 @@
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <tuple>
|
||||
#include "ALabel.hpp"
|
||||
|
||||
#include "AIconLabel.hpp"
|
||||
#include "bar.hpp"
|
||||
#include "client.hpp"
|
||||
#include "modules/sway/ipc/client.hpp"
|
||||
@ -10,7 +11,7 @@
|
||||
|
||||
namespace waybar::modules::sway {
|
||||
|
||||
class Window : public ALabel, public sigc::trackable {
|
||||
class Window : public AIconLabel, public sigc::trackable {
|
||||
public:
|
||||
Window(const std::string&, const waybar::Bar&, const Json::Value&);
|
||||
~Window() = default;
|
||||
@ -22,6 +23,8 @@ class Window : public ALabel, public sigc::trackable {
|
||||
std::tuple<std::size_t, int, std::string, std::string> getFocusedNode(const Json::Value& nodes,
|
||||
std::string& output);
|
||||
void getTree();
|
||||
std::string rewriteTitle(const std::string& title);
|
||||
void updateAppIcon();
|
||||
|
||||
const Bar& bar_;
|
||||
std::string window_;
|
||||
|
@ -3,11 +3,14 @@
|
||||
#include "AModule.hpp"
|
||||
#include "bar.hpp"
|
||||
#include "client.hpp"
|
||||
#include "giomm/desktopappinfo.h"
|
||||
#include "util/json.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <unordered_set>
|
||||
|
||||
#include <gdk/gdk.h>
|
||||
|
||||
@ -60,14 +63,18 @@ class Task
|
||||
Gtk::Image icon_;
|
||||
Gtk::Label text_before_;
|
||||
Gtk::Label text_after_;
|
||||
bool button_visible_;
|
||||
Glib::RefPtr<Gio::DesktopAppInfo> app_info_;
|
||||
bool button_visible_ = false;
|
||||
bool ignored_ = false;
|
||||
|
||||
bool with_icon_;
|
||||
bool with_icon_ = false;
|
||||
bool with_name_ = false;
|
||||
std::string format_before_;
|
||||
std::string format_after_;
|
||||
|
||||
std::string format_tooltip_;
|
||||
|
||||
std::string name_;
|
||||
std::string title_;
|
||||
std::string app_id_;
|
||||
uint32_t state_ = 0;
|
||||
@ -75,6 +82,9 @@ class Task
|
||||
private:
|
||||
std::string repr() const;
|
||||
std::string state_string(bool = false) const;
|
||||
void set_app_info_from_app_id_list(const std::string& app_id_list);
|
||||
bool image_load_icon(Gtk::Image& image, const Glib::RefPtr<Gtk::IconTheme>& icon_theme, Glib::RefPtr<Gio::DesktopAppInfo> app_info, int size);
|
||||
void hide_if_ignored();
|
||||
|
||||
public:
|
||||
/* Getter functions */
|
||||
@ -132,6 +142,8 @@ class Taskbar : public waybar::AModule
|
||||
std::vector<TaskPtr> tasks_;
|
||||
|
||||
std::vector<Glib::RefPtr<Gtk::IconTheme>> icon_themes_;
|
||||
std::unordered_set<std::string> ignore_list_;
|
||||
std::map<std::string, std::string> app_ids_replace_map_;
|
||||
|
||||
struct zwlr_foreign_toplevel_manager_v1 *manager_;
|
||||
struct wl_seat *seat_;
|
||||
@ -154,7 +166,9 @@ class Taskbar : public waybar::AModule
|
||||
bool show_output(struct wl_output *) const;
|
||||
bool all_outputs() const;
|
||||
|
||||
std::vector<Glib::RefPtr<Gtk::IconTheme>> icon_themes() const;
|
||||
const std::vector<Glib::RefPtr<Gtk::IconTheme>>& icon_themes() const;
|
||||
const std::unordered_set<std::string>& ignore_list() const;
|
||||
const std::map<std::string, std::string>& app_ids_replace_map() const;
|
||||
};
|
||||
|
||||
} /* namespace waybar::modules::wlr */
|
||||
|
160
include/modules/wlr/workspace_manager.hpp
Normal file
160
include/modules/wlr/workspace_manager.hpp
Normal file
@ -0,0 +1,160 @@
|
||||
#pragma once
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <gtkmm/button.h>
|
||||
#include <gtkmm/image.h>
|
||||
#include <gtkmm/label.h>
|
||||
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "AModule.hpp"
|
||||
#include "bar.hpp"
|
||||
#include "ext-workspace-unstable-v1-client-protocol.h"
|
||||
|
||||
namespace waybar::modules::wlr {
|
||||
|
||||
class WorkspaceManager;
|
||||
class WorkspaceGroup;
|
||||
|
||||
class Workspace {
|
||||
public:
|
||||
Workspace(const waybar::Bar &bar, const Json::Value &config, WorkspaceGroup &workspace_group,
|
||||
zext_workspace_handle_v1 *workspace, uint32_t id);
|
||||
~Workspace();
|
||||
auto update() -> void;
|
||||
|
||||
auto id() const -> uint32_t { return id_; }
|
||||
auto is_active() const -> bool { return state_ & static_cast<uint32_t>(State::ACTIVE); }
|
||||
auto is_urgent() const -> bool { return state_ & static_cast<uint32_t>(State::URGENT); }
|
||||
auto is_hidden() const -> bool { return state_ & static_cast<uint32_t>(State::HIDDEN); }
|
||||
// wlr stuff
|
||||
auto handle_name(const std::string &name) -> void;
|
||||
auto handle_coordinates(const std::vector<uint32_t> &coordinates) -> void;
|
||||
auto handle_state(const std::vector<uint32_t> &state) -> void;
|
||||
auto handle_remove() -> void;
|
||||
|
||||
auto handle_done() -> void;
|
||||
auto handle_clicked(GdkEventButton *bt) -> bool;
|
||||
auto show() -> void;
|
||||
auto hide() -> void;
|
||||
auto get_button_ref() -> Gtk::Button & { return button_; }
|
||||
auto get_name() -> std::string & { return name_; }
|
||||
auto get_coords() -> std::vector<uint32_t> & { return coordinates_; }
|
||||
|
||||
enum class State {
|
||||
ACTIVE = (1 << 0),
|
||||
URGENT = (1 << 1),
|
||||
HIDDEN = (1 << 2),
|
||||
};
|
||||
|
||||
private:
|
||||
auto get_icon() -> std::string;
|
||||
|
||||
const Bar &bar_;
|
||||
const Json::Value &config_;
|
||||
WorkspaceGroup &workspace_group_;
|
||||
|
||||
// wlr stuff
|
||||
zext_workspace_handle_v1 *workspace_handle_;
|
||||
uint32_t state_ = 0;
|
||||
|
||||
uint32_t id_;
|
||||
std::string name_;
|
||||
std::vector<uint32_t> coordinates_;
|
||||
static std::map<std::string, std::string> icons_map_;
|
||||
std::string format_;
|
||||
bool with_icon_ = false;
|
||||
|
||||
Gtk::Button button_;
|
||||
Gtk::Box content_;
|
||||
Gtk::Label label_;
|
||||
};
|
||||
|
||||
class WorkspaceGroup {
|
||||
public:
|
||||
WorkspaceGroup(const waybar::Bar &bar, Gtk::Box &box, const Json::Value &config,
|
||||
WorkspaceManager &manager, zext_workspace_group_handle_v1 *workspace_group_handle,
|
||||
uint32_t id);
|
||||
~WorkspaceGroup();
|
||||
auto update() -> void;
|
||||
|
||||
auto id() const -> uint32_t { return id_; }
|
||||
auto is_visible() const -> bool;
|
||||
auto remove_workspace(uint32_t id_) -> void;
|
||||
auto active_only() const -> bool;
|
||||
auto creation_delayed() const -> bool;
|
||||
auto workspaces() -> std::vector<std::unique_ptr<Workspace>> & { return workspaces_; }
|
||||
|
||||
auto sort_workspaces() -> void;
|
||||
auto set_need_to_sort() -> void { need_to_sort = true; }
|
||||
auto add_button(Gtk::Button &button) -> void;
|
||||
auto remove_button(Gtk::Button &button) -> void;
|
||||
|
||||
// wlr stuff
|
||||
auto handle_workspace_create(zext_workspace_handle_v1 *workspace_handle) -> void;
|
||||
auto handle_remove() -> void;
|
||||
auto handle_output_enter(wl_output *output) -> void;
|
||||
auto handle_output_leave() -> void;
|
||||
auto handle_done() -> void;
|
||||
auto commit() -> void;
|
||||
|
||||
private:
|
||||
static uint32_t workspace_global_id;
|
||||
const waybar::Bar &bar_;
|
||||
Gtk::Box &box_;
|
||||
const Json::Value &config_;
|
||||
WorkspaceManager &workspace_manager_;
|
||||
|
||||
// wlr stuff
|
||||
zext_workspace_group_handle_v1 *workspace_group_handle_;
|
||||
wl_output *output_ = nullptr;
|
||||
|
||||
uint32_t id_;
|
||||
std::vector<std::unique_ptr<Workspace>> workspaces_;
|
||||
bool need_to_sort = false;
|
||||
};
|
||||
|
||||
class WorkspaceManager : public AModule {
|
||||
public:
|
||||
WorkspaceManager(const std::string &id, const waybar::Bar &bar, const Json::Value &config);
|
||||
~WorkspaceManager() override;
|
||||
auto update() -> void override;
|
||||
|
||||
auto all_outputs() const -> bool { return all_outputs_; }
|
||||
auto active_only() const -> bool { return active_only_; }
|
||||
auto workspace_comparator() const
|
||||
-> std::function<bool(std::unique_ptr<Workspace> &, std::unique_ptr<Workspace> &)>;
|
||||
auto creation_delayed() const -> bool { return creation_delayed_; }
|
||||
|
||||
auto sort_workspaces() -> void;
|
||||
auto remove_workspace_group(uint32_t id_) -> void;
|
||||
|
||||
// wlr stuff
|
||||
auto register_manager(wl_registry *registry, uint32_t name, uint32_t version) -> void;
|
||||
auto handle_workspace_group_create(zext_workspace_group_handle_v1 *workspace_group_handle)
|
||||
-> void;
|
||||
auto handle_done() -> void;
|
||||
auto handle_finished() -> void;
|
||||
auto commit() -> void;
|
||||
|
||||
private:
|
||||
const waybar::Bar &bar_;
|
||||
Gtk::Box box_;
|
||||
std::vector<std::unique_ptr<WorkspaceGroup>> groups_;
|
||||
|
||||
// wlr stuff
|
||||
zext_workspace_manager_v1 *workspace_manager_ = nullptr;
|
||||
|
||||
static uint32_t group_global_id;
|
||||
|
||||
bool sort_by_name_ = true;
|
||||
bool sort_by_coordinates_ = true;
|
||||
bool all_outputs_ = false;
|
||||
bool active_only_ = false;
|
||||
bool creation_delayed_ = false;
|
||||
};
|
||||
|
||||
} // namespace waybar::modules::wlr
|
8
include/modules/wlr/workspace_manager_binding.hpp
Normal file
8
include/modules/wlr/workspace_manager_binding.hpp
Normal file
@ -0,0 +1,8 @@
|
||||
#include "ext-workspace-unstable-v1-client-protocol.h"
|
||||
|
||||
namespace waybar::modules::wlr {
|
||||
void add_registry_listener(void *data);
|
||||
void add_workspace_listener(zext_workspace_handle_v1 *workspace_handle, void *data);
|
||||
void add_workspace_group_listener(zext_workspace_group_handle_v1 *workspace_group_handle, void *data);
|
||||
zext_workspace_manager_v1* workspace_manager_bind(wl_registry *registry, uint32_t name, uint32_t version, void *data);
|
||||
}
|
75
include/util/SafeSignal.hpp
Normal file
75
include/util/SafeSignal.hpp
Normal file
@ -0,0 +1,75 @@
|
||||
#pragma once
|
||||
|
||||
#include <glibmm/dispatcher.h>
|
||||
#include <sigc++/signal.h>
|
||||
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <thread>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
namespace waybar {
|
||||
|
||||
/**
|
||||
* Thread-safe signal wrapper.
|
||||
* Uses Glib::Dispatcher to pass events to another thread and locked queue to pass the arguments.
|
||||
*/
|
||||
template <typename... Args>
|
||||
struct SafeSignal : sigc::signal<void(std::decay_t<Args>...)> {
|
||||
public:
|
||||
SafeSignal() { dp_.connect(sigc::mem_fun(*this, &SafeSignal::handle_event)); }
|
||||
|
||||
template <typename... EmitArgs>
|
||||
void emit(EmitArgs&&... args) {
|
||||
if (main_tid_ == std::this_thread::get_id()) {
|
||||
/*
|
||||
* Bypass the queue if the method is called the main thread.
|
||||
* Ensures that events emitted from the main thread are processed synchronously and saves a
|
||||
* few CPU cycles on locking/queuing.
|
||||
* As a downside, this makes main thread events prioritized over the other threads and
|
||||
* disrupts chronological order.
|
||||
*/
|
||||
signal_t::emit(std::forward<EmitArgs>(args)...);
|
||||
} else {
|
||||
{
|
||||
std::unique_lock lock(mutex_);
|
||||
queue_.emplace(std::forward<EmitArgs>(args)...);
|
||||
}
|
||||
dp_.emit();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename... EmitArgs>
|
||||
inline void operator()(EmitArgs&&... args) {
|
||||
emit(std::forward<EmitArgs>(args)...);
|
||||
}
|
||||
|
||||
protected:
|
||||
using signal_t = sigc::signal<void(std::decay_t<Args>...)>;
|
||||
using slot_t = decltype(std::declval<signal_t>().make_slot());
|
||||
using arg_tuple_t = std::tuple<std::decay_t<Args>...>;
|
||||
// ensure that unwrapped methods are not accessible
|
||||
using signal_t::emit_reverse;
|
||||
using signal_t::make_slot;
|
||||
|
||||
void handle_event() {
|
||||
for (std::unique_lock lock(mutex_); !queue_.empty(); lock.lock()) {
|
||||
auto args = queue_.front();
|
||||
queue_.pop();
|
||||
lock.unlock();
|
||||
std::apply(cached_fn_, args);
|
||||
}
|
||||
}
|
||||
|
||||
Glib::Dispatcher dp_;
|
||||
std::mutex mutex_;
|
||||
std::queue<arg_tuple_t> queue_;
|
||||
const std::thread::id main_tid_ = std::this_thread::get_id();
|
||||
// cache functor for signal emission to avoid recreating it on each event
|
||||
const slot_t cached_fn_ = make_slot();
|
||||
};
|
||||
|
||||
} // namespace waybar
|
@ -68,7 +68,10 @@ inline int close(FILE* fp, pid_t pid) {
|
||||
inline FILE* open(const std::string& cmd, int& pid) {
|
||||
if (cmd == "") return nullptr;
|
||||
int fd[2];
|
||||
pipe(fd);
|
||||
if (pipe(fd) != 0){
|
||||
spdlog::error("Unable to pipe fd");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
pid_t child_pid = fork();
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <glibmm/ustring.h>
|
||||
|
||||
class pow_format {
|
||||
public:
|
||||
@ -35,7 +36,11 @@ namespace fmt {
|
||||
// The rationale for ignoring it is that the only reason to specify
|
||||
// an alignment and a with is to get a fixed width bar, and ">" is
|
||||
// sufficient in this implementation.
|
||||
#if FMT_VERSION < 80000
|
||||
width = parse_nonnegative_int(it, end, ctx);
|
||||
#else
|
||||
width = detail::parse_nonnegative_int(it, end, -1);
|
||||
#endif
|
||||
}
|
||||
return it;
|
||||
}
|
||||
@ -80,5 +85,15 @@ namespace fmt {
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Glib ustirng support
|
||||
template <>
|
||||
struct formatter<Glib::ustring> : formatter<std::string> {
|
||||
template <typename FormatContext>
|
||||
auto format(const Glib::ustring& value, FormatContext& ctx) {
|
||||
return formatter<std::string>::format(value, ctx);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,20 @@
|
||||
|
||||
namespace waybar::util {
|
||||
|
||||
/**
|
||||
* Defer pthread_cancel until the end of a current scope.
|
||||
*
|
||||
* Required to protect a scope where it's unsafe to raise `__forced_unwind` exception.
|
||||
* An example of these is a call of a method marked as `noexcept`; an attempt to cancel within such
|
||||
* a method may result in a `std::terminate` call.
|
||||
*/
|
||||
class CancellationGuard {
|
||||
int oldstate;
|
||||
public:
|
||||
CancellationGuard() { pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate); }
|
||||
~CancellationGuard() { pthread_setcancelstate(oldstate, &oldstate); }
|
||||
};
|
||||
|
||||
class SleeperThread {
|
||||
public:
|
||||
SleeperThread() = default;
|
||||
@ -33,14 +47,16 @@ class SleeperThread {
|
||||
bool isRunning() const { return do_run_; }
|
||||
|
||||
auto sleep_for(std::chrono::system_clock::duration dur) {
|
||||
std::unique_lock lk(mutex_);
|
||||
std::unique_lock lk(mutex_);
|
||||
CancellationGuard cancel_lock;
|
||||
return condvar_.wait_for(lk, dur, [this] { return signal_ || !do_run_; });
|
||||
}
|
||||
|
||||
auto sleep_until(
|
||||
std::chrono::time_point<std::chrono::system_clock, std::chrono::system_clock::duration>
|
||||
time_point) {
|
||||
std::unique_lock lk(mutex_);
|
||||
std::unique_lock lk(mutex_);
|
||||
CancellationGuard cancel_lock;
|
||||
return condvar_.wait_until(lk, time_point, [this] { return signal_ || !do_run_; });
|
||||
}
|
||||
|
||||
|
17
include/util/string.hpp
Normal file
17
include/util/string.hpp
Normal file
@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
const std::string WHITESPACE = " \n\r\t\f\v";
|
||||
|
||||
inline std::string ltrim(const std::string& s) {
|
||||
size_t begin = s.find_first_not_of(WHITESPACE);
|
||||
return (begin == std::string::npos) ? "" : s.substr(begin);
|
||||
}
|
||||
|
||||
inline std::string rtrim(const std::string& s) {
|
||||
size_t end = s.find_last_not_of(WHITESPACE);
|
||||
return (end == std::string::npos) ? "" : s.substr(0, end + 1);
|
||||
}
|
||||
|
||||
inline std::string trim(const std::string& s) { return rtrim(ltrim(s)); }
|
39
include/util/waybar_time.hpp
Normal file
39
include/util/waybar_time.hpp
Normal file
@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include <date/tz.h>
|
||||
#include <fmt/format.h>
|
||||
|
||||
namespace waybar {
|
||||
|
||||
struct waybar_time {
|
||||
std::locale locale;
|
||||
date::zoned_seconds ztime;
|
||||
};
|
||||
|
||||
} // namespace waybar
|
||||
|
||||
template <>
|
||||
struct fmt::formatter<waybar::waybar_time> {
|
||||
std::string_view specs;
|
||||
|
||||
template <typename ParseContext>
|
||||
constexpr auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
auto it = ctx.begin();
|
||||
if (it != ctx.end() && *it == ':') {
|
||||
++it;
|
||||
}
|
||||
auto end = it;
|
||||
while (end != ctx.end() && *end != '}') {
|
||||
++end;
|
||||
}
|
||||
if (end != it) {
|
||||
specs = {it, std::string_view::size_type(end - it)};
|
||||
}
|
||||
return end;
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const waybar::waybar_time& t, FormatContext& ctx) {
|
||||
return format_to(ctx.out(), "{}", date::format(t.locale, fmt::to_string(specs), t.ztime));
|
||||
}
|
||||
};
|
@ -120,7 +120,7 @@ The two arguments are:
|
||||
|
||||
# CUSTOM FORMATS
|
||||
|
||||
The *battery* module allows to define custom formats based on up to two factors. The best fitting format will be selected.
|
||||
The *battery* module allows one to define custom formats based on up to two factors. The best fitting format will be selected.
|
||||
|
||||
*format-<state>*: With *states*, a custom format can be set depending on the capacity of your battery.
|
||||
|
||||
|
@ -24,7 +24,8 @@ The *clock* module displays the current date and time.
|
||||
*timezone*: ++
|
||||
typeof: string ++
|
||||
default: inferred local timezone ++
|
||||
The timezone to display the time in, e.g. America/New_York.
|
||||
The timezone to display the time in, e.g. America/New_York. ++
|
||||
This field will be ignored if *timezones* field is set and have at least one value.
|
||||
|
||||
*timezones*: ++
|
||||
typeof: list of strings ++
|
||||
@ -85,11 +86,17 @@ The *clock* module displays the current date and time.
|
||||
typeof: double ++
|
||||
Threshold to be used when scrolling.
|
||||
|
||||
*tooltip*: ++
|
||||
typeof: bool ++
|
||||
default: true ++
|
||||
Option to disable tooltip on hover.
|
||||
|
||||
View all valid format options in *strftime(3)*.
|
||||
|
||||
# FORMAT REPLACEMENTS
|
||||
|
||||
*{calendar}*: Current month calendar
|
||||
*{timezoned_time_list}*: List of time in the rest timezones, if more than one timezone is set in the config
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
|
@ -20,6 +20,11 @@ The *cpu* module displays the current cpu utilization.
|
||||
default: {usage}% ++
|
||||
The format, how information should be displayed. On {} data gets inserted.
|
||||
|
||||
*format-icons*: ++
|
||||
typeof: array/object ++
|
||||
Based on the current usage, the corresponding icon gets selected. ++
|
||||
The order is *low* to *high*. Or by the state if it is an object.
|
||||
|
||||
*max-length*: ++
|
||||
typeof: integer ++
|
||||
The maximum length in character the module should display.
|
||||
@ -77,7 +82,9 @@ The *cpu* module displays the current cpu utilization.
|
||||
|
||||
*{load}*: Current cpu load.
|
||||
|
||||
*{usage}*: Current cpu usage.
|
||||
*{usage}*: Current overall cpu usage.
|
||||
|
||||
*{usage*{n}*}*: Current cpu core n usage. Cores are numbered from zero, so first core will be {usage0} and 4th will be {usage3}.
|
||||
|
||||
*{avg_frequency}*: Current cpu average frequency (based on all cores) in GHz.
|
||||
|
||||
@ -85,7 +92,13 @@ The *cpu* module displays the current cpu utilization.
|
||||
|
||||
*{min_frequency}*: Current cpu min frequency (based on the core with the lowest frequency) in GHz.
|
||||
|
||||
# EXAMPLE
|
||||
*{icon}*: Icon for overall cpu usage.
|
||||
|
||||
*{icon*{n}*}*: Icon for cpu core n usage. Use like {icon0}.
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
Basic configuration:
|
||||
|
||||
```
|
||||
"cpu": {
|
||||
@ -95,6 +108,16 @@ The *cpu* module displays the current cpu utilization.
|
||||
}
|
||||
```
|
||||
|
||||
Cpu usage per core rendered as icons:
|
||||
|
||||
```
|
||||
"cpu": {
|
||||
"interval": 1,
|
||||
"format": "{icon0}{icon1}{icon2}{icon3} {usage:>2}% ",
|
||||
"format-icons": ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"],
|
||||
},
|
||||
```
|
||||
|
||||
# STYLE
|
||||
|
||||
- *#cpu*
|
||||
|
@ -151,7 +151,8 @@ $text\\n$tooltip\\n$class*
|
||||
"max-length": 40,
|
||||
"interval": 30, // Remove this if your script is endless and write in loop
|
||||
"exec": "$HOME/.config/waybar/mediaplayer.sh 2> /dev/null", // Script in resources folder
|
||||
"exec-if": "pgrep spotify"
|
||||
"exec-if": "pgrep spotify",
|
||||
"return-type": "json"
|
||||
}
|
||||
```
|
||||
|
||||
|
92
man/waybar-inhibitor.5.scd
Normal file
92
man/waybar-inhibitor.5.scd
Normal file
@ -0,0 +1,92 @@
|
||||
waybar-inhibitor(5)
|
||||
|
||||
# NAME
|
||||
|
||||
waybar - inhibitor module
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
The *inhibitor* module allows to take an inhibitor lock that logind provides.
|
||||
See *systemd-inhibit*(1) for more information.
|
||||
|
||||
# CONFIGURATION
|
||||
|
||||
*what*: ++
|
||||
typeof: string or array ++
|
||||
The inhibitor lock or locks that should be taken when active. The available inhibitor locks are *idle*, *shutdown*, *sleep*, *handle-power-key*, *handle-suspend-key*, *handle-hibernate-key* and *handle-lid-switch*.
|
||||
|
||||
*format*: ++
|
||||
typeof: string ++
|
||||
The format, how the state should be displayed.
|
||||
|
||||
*format-icons*: ++
|
||||
typeof: array ++
|
||||
Based on the current state, the corresponding icon gets selected.
|
||||
|
||||
*rotate*: ++
|
||||
typeof: integer ++
|
||||
Positive value to rotate the text label.
|
||||
|
||||
*max-length*: ++
|
||||
typeof: integer ++
|
||||
The maximum length in character the module should display.
|
||||
|
||||
*min-length*: ++
|
||||
typeof: integer ++
|
||||
The minimum length in characters the module should take up.
|
||||
|
||||
*align*: ++
|
||||
typeof: float ++
|
||||
The alignment of the text, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text.
|
||||
|
||||
*on-click*: ++
|
||||
typeof: string ++
|
||||
Command to execute when clicked on the module. A click also toggles the state
|
||||
|
||||
*on-click-middle*: ++
|
||||
typeof: string ++
|
||||
Command to execute when middle-clicked on the module using mousewheel.
|
||||
|
||||
*on-click-right*: ++
|
||||
typeof: string ++
|
||||
Command to execute when you right clicked on the module.
|
||||
|
||||
*on-update*: ++
|
||||
typeof: string ++
|
||||
Command to execute when the module is updated.
|
||||
|
||||
*on-scroll-up*: ++
|
||||
typeof: string ++
|
||||
Command to execute when scrolling up on the module.
|
||||
|
||||
*on-scroll-down*: ++
|
||||
typeof: string ++
|
||||
Command to execute when scrolling down on the module.
|
||||
|
||||
*smooth-scrolling-threshold*: ++
|
||||
typeof: double ++
|
||||
Threshold to be used when scrolling.
|
||||
|
||||
*tooltip*: ++
|
||||
typeof: bool ++
|
||||
default: true ++
|
||||
Option to disable tooltip on hover.
|
||||
|
||||
# FORMAT REPLACEMENTS
|
||||
|
||||
*{status}*: status (*activated* or *deactivated*)
|
||||
|
||||
*{icon}*: Icon, as defined in *format-icons*
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
```
|
||||
"inhibitor": {
|
||||
"what": "handle-lid-switch",
|
||||
"format": "{icon}",
|
||||
"format-icons": {
|
||||
"activated": "",
|
||||
"deactivated": ""
|
||||
}
|
||||
}
|
||||
```
|
82
man/waybar-keyboard-state.5.scd
Normal file
82
man/waybar-keyboard-state.5.scd
Normal file
@ -0,0 +1,82 @@
|
||||
waybar-keyboard-state(5)
|
||||
|
||||
# NAME
|
||||
|
||||
waybar - keyboard-state module
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
The *keyboard-state* module displays the state of number lock, caps lock, and scroll lock.
|
||||
|
||||
You must be a member of the input group to use this module.
|
||||
|
||||
# CONFIGURATION
|
||||
|
||||
*interval*: ++
|
||||
typeof: integer ++
|
||||
default: 1 ++
|
||||
The interval, in seconds, to poll the keyboard state.
|
||||
|
||||
*format*: ++
|
||||
typeof: string|object ++
|
||||
default: {name} {icon} ++
|
||||
The format, how information should be displayed. If a string, the same format is used for all keyboard states. If an object, the fields "numlock", "capslock", and "scrolllock" each specify the format for the corresponding state. Any unspecified states use the default format.
|
||||
|
||||
*format-icons*: ++
|
||||
typeof: object ++
|
||||
default: {"locked": "locked", "unlocked": "unlocked"} ++
|
||||
Based on the keyboard state, the corresponding icon gets selected. The same set of icons is used for number, caps, and scroll lock, but the icon is selected from the set independently for each. See *icons*.
|
||||
|
||||
*numlock*: ++
|
||||
typeof: bool ++
|
||||
default: false ++
|
||||
Display the number lock state.
|
||||
|
||||
*capslock*: ++
|
||||
typeof: bool ++
|
||||
default: false ++
|
||||
Display the caps lock state.
|
||||
|
||||
*scrolllock*: ++
|
||||
typeof: bool ++
|
||||
default: false ++
|
||||
Display the scroll lock state.
|
||||
|
||||
*device-path*: ++
|
||||
typeof: string ++
|
||||
default: chooses first valid input device ++
|
||||
Which libevdev input device to show the state of. Libevdev devices can be found in /dev/input. The device should support number lock, caps lock, and scroll lock events.
|
||||
|
||||
# FORMAT REPLACEMENTS
|
||||
|
||||
*{name}*: Caps, Num, or Scroll.
|
||||
|
||||
*{icon}*: Icon, as defined in *format-icons*.
|
||||
|
||||
# ICONS
|
||||
|
||||
The following *format-icons* can be set.
|
||||
|
||||
- *locked*: Will be shown when the keyboard state is locked. Default "locked".
|
||||
- *unlocked*: Will be shown when the keyboard state is not locked. Default "unlocked"
|
||||
|
||||
# EXAMPLE:
|
||||
|
||||
```
|
||||
"keyboard-state": {
|
||||
"numlock": true,
|
||||
"capslock": true,
|
||||
"format": "{name} {icon}",
|
||||
"format-icons": {
|
||||
"locked": "",
|
||||
"unlocked": ""
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
# STYLE
|
||||
|
||||
- *#keyboard-state*
|
||||
- *#keyboard-state label*
|
||||
- *#keyboard-state label.locked*
|
||||
|
@ -22,6 +22,11 @@ Addressed by *memory*
|
||||
default: {percentage}% ++
|
||||
The format, how information should be displayed.
|
||||
|
||||
*format-icons*: ++
|
||||
typeof: array/object ++
|
||||
Based on the current percentage, the corresponding icon gets selected. ++
|
||||
The order is *low* to *high*. Or by the state if it is an object.
|
||||
|
||||
*rotate*: ++
|
||||
typeof: integer ++
|
||||
Positive value to rotate the text label.
|
||||
@ -79,12 +84,20 @@ Addressed by *memory*
|
||||
|
||||
*{percentage}*: Percentage of memory in use.
|
||||
|
||||
*{swapPercentage}*: Percentage of swap in use.
|
||||
|
||||
*{total}*: Amount of total memory available in GiB.
|
||||
|
||||
*{swapTotal}*: Amount of total swap available in GiB.
|
||||
|
||||
*{used}*: Amount of used memory in GiB.
|
||||
|
||||
*{swapUsed}*: Amount of used swap in GiB.
|
||||
|
||||
*{avail}*: Amount of available memory in GiB.
|
||||
|
||||
*{swapAvail}*: Amount of available swap in GiB.
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
```
|
||||
|
@ -172,6 +172,8 @@ Addressed by *mpd*
|
||||
|
||||
*{date}*: The date of the current song
|
||||
|
||||
*{volume}*: The current volume in percent
|
||||
|
||||
*{elapsedTime}*: The current position of the current song. To format as a date/time (see example configuration)
|
||||
|
||||
*{totalTime}*: The length of the current song. To format as a date/time (see example configuration)
|
||||
|
@ -131,6 +131,8 @@ Addressed by *network*
|
||||
|
||||
*{ipaddr}*: The first IP of the interface.
|
||||
|
||||
*{gwaddr}*: The default gateway for the interface
|
||||
|
||||
*{netmask}*: The subnetmask corresponding to the IP.
|
||||
|
||||
*{cidr}*: The subnetmask corresponding to the IP in CIDR notation.
|
||||
|
@ -109,6 +109,9 @@ Additionally you can control the volume by scrolling *up* or *down* while the cu
|
||||
# ICONS:
|
||||
|
||||
The following strings for *format-icons* are supported.
|
||||
|
||||
- the device name
|
||||
|
||||
If they are found in the current PulseAudio port name, the corresponding icons will be selected.
|
||||
|
||||
- *default* (Shown, when no other port is found)
|
||||
@ -131,6 +134,7 @@ If they are found in the current PulseAudio port name, the corresponding icons w
|
||||
"format-bluetooth": "{volume}% {icon}",
|
||||
"format-muted": "",
|
||||
"format-icons": {
|
||||
"alsa_output.pci-0000_00_1f.3.analog-stereo": "",
|
||||
"headphones": "",
|
||||
"handsfree": "",
|
||||
"headset": "",
|
||||
|
@ -15,12 +15,17 @@ Addressed by *river/tags*
|
||||
*num-tags*: ++
|
||||
typeof: uint ++
|
||||
default: 9 ++
|
||||
The number of tags that should be displayed.
|
||||
The number of tags that should be displayed. Max 32.
|
||||
|
||||
*tag-labels*: ++
|
||||
typeof: array ++
|
||||
The label to display for each tag.
|
||||
|
||||
*disable-click*: ++
|
||||
typeof: bool ++
|
||||
default: false ++
|
||||
If set to false, you can left click to set focused tag. Right click to toggle tag focus. If set to true this behaviour is disabled.
|
||||
|
||||
# EXAMPLE
|
||||
|
||||
```
|
||||
@ -34,8 +39,10 @@ Addressed by *river/tags*
|
||||
- *#tags button*
|
||||
- *#tags button.occupied*
|
||||
- *#tags button.focused*
|
||||
- *#tags button.urgent*
|
||||
|
||||
Note that a tag can be both occupied and focused at the same time.
|
||||
Note that occupied/focused/urgent status may overlap. That is, a tag may be
|
||||
both occupied and focused at the same time.
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
|
@ -15,63 +15,39 @@ Addressed by *sway/language*
|
||||
*format*: ++
|
||||
typeof: string ++
|
||||
default: {} ++
|
||||
The format, how information should be displayed. On {} data gets inserted.
|
||||
The format, how layout should be displayed.
|
||||
|
||||
*rotate*: ++
|
||||
typeof: integer ++
|
||||
Positive value to rotate the text label.
|
||||
|
||||
*max-length*: ++
|
||||
typeof: integer ++
|
||||
The maximum length in character the module should display.
|
||||
|
||||
*min-length*: ++
|
||||
typeof: integer ++
|
||||
The minimum length in characters the module should take up.
|
||||
|
||||
*align*: ++
|
||||
typeof: float ++
|
||||
The alignment of the text, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text.
|
||||
|
||||
*on-click*: ++
|
||||
*tooltip-format*: ++
|
||||
typeof: string ++
|
||||
Command to execute when clicked on the module.
|
||||
|
||||
*on-click-middle*: ++
|
||||
typeof: string ++
|
||||
Command to execute when middle-clicked on the module using mousewheel.
|
||||
|
||||
*on-click-right*: ++
|
||||
typeof: string ++
|
||||
Command to execute when you right clicked on the module.
|
||||
|
||||
*on-update*: ++
|
||||
typeof: string ++
|
||||
Command to execute when the module is updated.
|
||||
|
||||
*on-scroll-up*: ++
|
||||
typeof: string ++
|
||||
Command to execute when scrolling up on the module.
|
||||
|
||||
*on-scroll-down*: ++
|
||||
typeof: string ++
|
||||
Command to execute when scrolling down on the module.
|
||||
|
||||
*smooth-scrolling-threshold*: ++
|
||||
typeof: double ++
|
||||
Threshold to be used when scrolling.
|
||||
default: {} ++
|
||||
The format, how layout should be displayed in tooltip.
|
||||
|
||||
*tooltip*: ++
|
||||
typeof: bool ++
|
||||
default: true ++
|
||||
Option to disable tooltip on hover.
|
||||
|
||||
# FORMAT REPLACEMENTS
|
||||
|
||||
*{short}*: Short name of layout (e.g. "us"). Equals to {}.
|
||||
|
||||
*{shortDescription}*: Short description of layout (e.g. "en").
|
||||
|
||||
*{long}*: Long name of layout (e.g. "English (Dvorak)").
|
||||
|
||||
*{variant}*: Variant of layout (e.g. "dvorak").
|
||||
|
||||
*{flag}*: Country flag of layout.
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
```
|
||||
"sway/language": {
|
||||
"format": "{}",
|
||||
"max-length": 50
|
||||
},
|
||||
|
||||
"sway/language": {
|
||||
"format": "{short} {variant}",
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -69,7 +69,7 @@ Addressed by *sway/mode*
|
||||
# EXAMPLES
|
||||
|
||||
```
|
||||
"sway/window": {
|
||||
"sway/mode": {
|
||||
"format": " {}",
|
||||
"max-length": 50
|
||||
}
|
||||
|
@ -66,12 +66,37 @@ Addressed by *sway/window*
|
||||
default: true ++
|
||||
Option to disable tooltip on hover.
|
||||
|
||||
*rewrite*: ++
|
||||
typeof: object ++
|
||||
Rules to rewrite window title. See *rewrite rules*.
|
||||
|
||||
*icon*: ++
|
||||
typeof: bool ++
|
||||
default: false ++
|
||||
Option to hide the application icon.
|
||||
|
||||
# REWRITE RULES
|
||||
|
||||
*rewrite* is an object where keys are regular expressions and values are
|
||||
rewrite rules if the expression matches. Rules may contain references to
|
||||
captures of the expression.
|
||||
|
||||
Regular expression and replacement follow ECMA-script rules.
|
||||
|
||||
If no expression matches, the title is left unchanged.
|
||||
|
||||
Invalid expressions (e.g., mismatched parentheses) are skipped.
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
```
|
||||
"sway/window": {
|
||||
"format": "{}",
|
||||
"max-length": 50
|
||||
"max-length": 50,
|
||||
"rewrite": {
|
||||
"(.*) - Mozilla Firefox": "🌎 $1",
|
||||
"(.*) - zsh": "> [$1]"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -69,10 +69,6 @@ Addressed by *sway/workspaces*
|
||||
typeof: string ++
|
||||
Command to execute when the module is updated.
|
||||
|
||||
*numeric-first*: ++
|
||||
typeof: bool ++
|
||||
Whether to put workspaces starting with numbers before workspaces that do not start with a number.
|
||||
|
||||
*disable-auto-back-and-forth*: ++
|
||||
typeof: bool ++
|
||||
Whether to disable *workspace_auto_back_and_forth* when clicking on workspaces. If this is set to *true*, clicking on a workspace you are already on won't do anything, even if *workspace_auto_back_and_forth* is enabled in the Sway configuration.
|
||||
@ -120,7 +116,6 @@ n.b.: the list of outputs can be obtained from command line using *swaymsg -t ge
|
||||
"sway/workspaces": {
|
||||
"disable-scroll": true,
|
||||
"all-outputs": true,
|
||||
"numeric-first": false,
|
||||
"format": "{name}: {icon}",
|
||||
"format-icons": {
|
||||
"1": "",
|
||||
|
@ -16,10 +16,23 @@ Addressed by *tray*
|
||||
typeof: integer ++
|
||||
Defines the size of the tray icons.
|
||||
|
||||
*show-passive-items*: ++
|
||||
typeof: bool ++
|
||||
default: false ++
|
||||
Defines visibility of the tray icons with *Passive* status.
|
||||
|
||||
*smooth-scrolling-threshold*: ++
|
||||
typeof: double ++
|
||||
Threshold to be used when scrolling.
|
||||
|
||||
*spacing*: ++
|
||||
typeof: integer ++
|
||||
Defines the spacing between the tray icons.
|
||||
|
||||
*reverse-direction*: ++
|
||||
typeof: bool ++
|
||||
Defines if new app icons should be added in a reverse order
|
||||
|
||||
*on-update*: ++
|
||||
typeof: string ++
|
||||
Command to execute when the module is updated.
|
||||
@ -37,3 +50,6 @@ Addressed by *tray*
|
||||
# STYLE
|
||||
|
||||
- *#tray*
|
||||
- *#tray > .passive*
|
||||
- *#tray > .active*
|
||||
- *#tray > .needs-attention*
|
||||
|
@ -68,10 +68,20 @@ Addressed by *wlr/taskbar*
|
||||
typeof: string ++
|
||||
Command to execute when the module is updated.
|
||||
|
||||
*ignore-list*: ++
|
||||
typeof: array ++
|
||||
List of app_id/titles to be invisible.
|
||||
|
||||
*app_ids-mapping*: ++
|
||||
typeof: object ++
|
||||
Dictionary of app_id to be replaced with
|
||||
|
||||
# FORMAT REPLACEMENTS
|
||||
|
||||
*{icon}*: The icon of the application.
|
||||
|
||||
*{title}*: The application name as in desktop file if appropriate desktop fils found, otherwise same as {app_id}
|
||||
|
||||
*{title}*: The title of the application.
|
||||
|
||||
*{app_id}*: The app_id (== application name) of the application.
|
||||
@ -83,10 +93,15 @@ Addressed by *wlr/taskbar*
|
||||
# CLICK ACTIONS
|
||||
|
||||
*activate*: Bring the application into foreground.
|
||||
|
||||
*minimize*: Toggle application's minimized state.
|
||||
|
||||
*minimize-raise*: Bring the application into foreground or toggle its minimized state.
|
||||
|
||||
*maximize*: Toggle application's maximized state.
|
||||
|
||||
*fullscreen*: Toggle application's fullscreen state.
|
||||
|
||||
*close*: Close the application.
|
||||
|
||||
# EXAMPLES
|
||||
@ -98,7 +113,13 @@ Addressed by *wlr/taskbar*
|
||||
"icon-theme": "Numix-Circle",
|
||||
"tooltip-format": "{title}",
|
||||
"on-click": "activate",
|
||||
"on-click-middle": "close"
|
||||
"on-click-middle": "close",
|
||||
"ignore-list": [
|
||||
"Alacritty"
|
||||
],
|
||||
"app_ids-mapping": {
|
||||
"firefoxdeveloperedition": "firefox-developer-edition"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
88
man/waybar-wlr-workspaces.5.scd
Normal file
88
man/waybar-wlr-workspaces.5.scd
Normal file
@ -0,0 +1,88 @@
|
||||
waybar-wlr-workspaces(5)
|
||||
|
||||
# NAME
|
||||
|
||||
waybar - wlr workspaces module
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
The *workspaces* module displays the currently used workspaces in wayland compositor.
|
||||
|
||||
# CONFIGURATION
|
||||
|
||||
Addressed by *wlr/workspaces*
|
||||
|
||||
*format*: ++
|
||||
typeof: string ++
|
||||
default: {name} ++
|
||||
The format, how information should be displayed.
|
||||
|
||||
*format-icons*: ++
|
||||
typeof: array ++
|
||||
Based on the workspace name and state, the corresponding icon gets selected. See *icons*.
|
||||
|
||||
*sort-by-name*: ++
|
||||
typeof: bool ++
|
||||
default: true ++
|
||||
Should workspaces be sorted by name.
|
||||
|
||||
*sort-by-coordinates*: ++
|
||||
typeof: bool ++
|
||||
default: true ++
|
||||
Should workspaces be sorted by coordinates.
|
||||
Note that if both *sort-by-name* and *sort-by-coordinates* are true sort by name will be first.
|
||||
If both are false - sort by id will be performed.
|
||||
|
||||
*all-outputs*: ++
|
||||
typeof: bool ++
|
||||
default: false ++
|
||||
If set to false workspaces group will be shown only in assigned output. Otherwise all workspace groups are shown.
|
||||
|
||||
*active-only*: ++
|
||||
typeof: bool ++
|
||||
default: false ++
|
||||
If set to true only active or urgent workspaces will be shown.
|
||||
|
||||
# FORMAT REPLACEMENTS
|
||||
|
||||
*{name}*: Name of workspace assigned by compositor
|
||||
|
||||
*{icon}*: Icon, as defined in *format-icons*.
|
||||
|
||||
# CLICK ACTIONS
|
||||
|
||||
*activate*: Switch to workspace.
|
||||
|
||||
*close*: Close the workspace.
|
||||
|
||||
# ICONS
|
||||
|
||||
Additional to workspace name matching, the following *format-icons* can be set.
|
||||
|
||||
- *default*: Will be shown, when no string match is found.
|
||||
- *focused*: Will be shown, when workspace is focused
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
```
|
||||
"wlr/workspaces": {
|
||||
"format": "{name}: {icon}",
|
||||
"format-icons": {
|
||||
"1": "",
|
||||
"2": "",
|
||||
"3": "",
|
||||
"4": "",
|
||||
"5": "",
|
||||
"focused": "",
|
||||
"default": ""
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
# Style
|
||||
|
||||
- *#workspaces*
|
||||
- *#workspaces button*
|
||||
- *#workspaces button.active*
|
||||
- *#workspaces button.urgent*
|
||||
- *#workspaces button.hidden*
|
@ -64,16 +64,58 @@ Also a minimal example configuration can be found on the at the bottom of this m
|
||||
typeof: integer ++
|
||||
Margins value without units.
|
||||
|
||||
*spacing* ++
|
||||
typeof: integer ++
|
||||
Size of gaps in between of the different modules.
|
||||
|
||||
*name* ++
|
||||
typeof: string ++
|
||||
Optional name added as a CSS class, for styling multiple waybars.
|
||||
|
||||
*mode* ++
|
||||
typeof: string ++
|
||||
Selects one of the preconfigured display modes. This is an equivalent of the sway-bar(5) *mode* command and supports the same values: *dock*, *hide*, *invisible*, *overlay*. ++
|
||||
Note: *hide* and *invisible* modes may be not as useful without Sway IPC.
|
||||
|
||||
*exclusive* ++
|
||||
typeof: bool ++
|
||||
default: *true* ++
|
||||
Option to request an exclusive zone from the compositor. Disable this to allow drawing application windows underneath or on top of the bar.
|
||||
|
||||
*fixed-center* ++
|
||||
typeof: bool ++
|
||||
default: *true*
|
||||
Prefer fixed center position for the `modules-center` block. The center block will stay in the middle of the bar whenever possible. It can still be pushed around if other blocks need more space.
|
||||
When false, the center block is centered in the space between the left and right block.
|
||||
|
||||
*passthrough* ++
|
||||
typeof: bool ++
|
||||
default: *false* ++
|
||||
Option to pass any pointer events to the window under the bar.
|
||||
Intended to be used with either *top* or *overlay* layers and without exclusive zone.
|
||||
|
||||
*gtk-layer-shell* ++
|
||||
typeof: bool ++
|
||||
default: true ++
|
||||
Option to disable the use of gtk-layer-shell for popups.
|
||||
Only functional if compiled with gtk-layer-shell support.
|
||||
|
||||
*ipc* ++
|
||||
typeof: bool ++
|
||||
default: false ++
|
||||
Option to subscribe to the Sway IPC bar configuration and visibility events and control waybar with *swaymsg bar* commands. ++
|
||||
Requires *bar_id* value from sway configuration to be either passed with the *-b* commandline argument or specified with the *id* option.
|
||||
|
||||
*id* ++
|
||||
typeof: string ++
|
||||
*bar_id* for the Sway IPC. Use this if you need to override the value passed with the *-b bar_id* commandline argument for the specific bar instance.
|
||||
|
||||
*include* ++
|
||||
typeof: string|array ++
|
||||
Paths to additional configuration files.
|
||||
Each file can contain a single object with any of the bar configuration options. In case of duplicate options, the first defined value takes precedence, i.e. including file -> first included file -> etc. Nested includes are permitted, but make sure to avoid circular imports.
|
||||
For a multi-bar config, the include directive affects only current bar configuration object.
|
||||
|
||||
# MODULE FORMAT
|
||||
|
||||
You can use PangoMarkupFormat (See https://developer.gnome.org/pango/stable/PangoMarkupFormat.html#PangoMarkupFormat).
|
||||
@ -182,6 +224,28 @@ 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.
|
||||
|
||||
## Grouping modules
|
||||
|
||||
Module groups allow stacking modules in the direction orthogonal to the bar direction. 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.
|
||||
|
||||
A module group is defined by specifying a module named "group/some-group-name". The group must also be configured with a list of contained modules. Example:
|
||||
|
||||
```
|
||||
{
|
||||
"modules-right": ["group/hardware", "clock"],
|
||||
|
||||
"group/hardware": {
|
||||
"modules": [
|
||||
"cpu",
|
||||
"memory",
|
||||
"battery"
|
||||
]
|
||||
},
|
||||
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
# SUPPORTED MODULES
|
||||
|
||||
- *waybar-backlight(5)*
|
||||
@ -192,6 +256,7 @@ Valid options for the "rotate" property are: 0, 90, 180 and 270.
|
||||
- *waybar-custom(5)*
|
||||
- *waybar-disk(5)*
|
||||
- *waybar-idle-inhibitor(5)*
|
||||
- *waybar-keyboard-state(5)*
|
||||
- *waybar-memory(5)*
|
||||
- *waybar-mpd(5)*
|
||||
- *waybar-network(5)*
|
||||
@ -202,5 +267,6 @@ Valid options for the "rotate" property are: 0, 90, 180 and 270.
|
||||
- *waybar-sway-window(5)*
|
||||
- *waybar-sway-workspaces(5)*
|
||||
- *waybar-wlr-taskbar(5)*
|
||||
- *waybar-wlr-workspaces(5)*
|
||||
- *waybar-temperature(5)*
|
||||
- *waybar-tray(5)*
|
||||
|
50
meson.build
50
meson.build
@ -1,6 +1,6 @@
|
||||
project(
|
||||
'waybar', 'cpp', 'c',
|
||||
version: '0.9.6',
|
||||
version: '0.9.11',
|
||||
license: 'MIT',
|
||||
meson_version: '>= 0.49.0',
|
||||
default_options : [
|
||||
@ -79,14 +79,14 @@ is_netbsd = host_machine.system() == 'netbsd'
|
||||
is_openbsd = host_machine.system() == 'openbsd'
|
||||
|
||||
thread_dep = dependency('threads')
|
||||
fmt = dependency('fmt', version : ['>=5.3.0'], fallback : ['fmt', 'fmt_dep'])
|
||||
spdlog = dependency('spdlog', version : ['>=1.8.0'], fallback : ['spdlog', 'spdlog_dep'], default_options : ['external_fmt=true'])
|
||||
fmt = dependency('fmt', version : ['>=7.0.0'], fallback : ['fmt', 'fmt_dep'])
|
||||
spdlog = dependency('spdlog', version : ['>=1.8.5'], fallback : ['spdlog', 'spdlog_dep'], default_options : ['external_fmt=true'])
|
||||
wayland_client = dependency('wayland-client')
|
||||
wayland_cursor = dependency('wayland-cursor')
|
||||
wayland_protos = dependency('wayland-protocols')
|
||||
gtkmm = dependency('gtkmm-3.0', version : ['>=3.22.0'])
|
||||
dbusmenu_gtk = dependency('dbusmenu-gtk3-0.4', required: get_option('dbusmenu-gtk'))
|
||||
giounix = dependency('gio-unix-2.0', required: get_option('dbusmenu-gtk'))
|
||||
giounix = dependency('gio-unix-2.0', required: (get_option('dbusmenu-gtk').enabled() or get_option('logind').enabled()))
|
||||
jsoncpp = dependency('jsoncpp')
|
||||
sigcpp = dependency('sigc++-2.0')
|
||||
libepoll = dependency('epoll-shim', required: false)
|
||||
@ -94,7 +94,9 @@ libnl = dependency('libnl-3.0', required: get_option('libnl'))
|
||||
libnlgen = dependency('libnl-genl-3.0', required: get_option('libnl'))
|
||||
libpulse = dependency('libpulse', required: get_option('pulseaudio'))
|
||||
libudev = dependency('libudev', required: get_option('libudev'))
|
||||
libevdev = dependency('libevdev', required: get_option('libevdev'))
|
||||
libmpdclient = dependency('libmpdclient', required: get_option('mpd'))
|
||||
xkbregistry = dependency('xkbregistry')
|
||||
|
||||
libsndio = compiler.find_library('sndio', required: get_option('sndio'))
|
||||
if libsndio.found()
|
||||
@ -140,6 +142,7 @@ src_files = files(
|
||||
'src/factory.cpp',
|
||||
'src/AModule.cpp',
|
||||
'src/ALabel.cpp',
|
||||
'src/AIconLabel.cpp',
|
||||
'src/modules/custom.cpp',
|
||||
'src/modules/disk.cpp',
|
||||
'src/modules/idle_inhibitor.cpp',
|
||||
@ -147,6 +150,8 @@ src_files = files(
|
||||
'src/main.cpp',
|
||||
'src/bar.cpp',
|
||||
'src/client.cpp',
|
||||
'src/config.cpp',
|
||||
'src/group.cpp',
|
||||
'src/util/ustring_clen.cpp'
|
||||
)
|
||||
|
||||
@ -174,6 +179,7 @@ endif
|
||||
add_project_arguments('-DHAVE_SWAY', language: 'cpp')
|
||||
src_files += [
|
||||
'src/modules/sway/ipc/client.cpp',
|
||||
'src/modules/sway/bar.cpp',
|
||||
'src/modules/sway/mode.cpp',
|
||||
'src/modules/sway/language.cpp',
|
||||
'src/modules/sway/window.cpp',
|
||||
@ -183,6 +189,8 @@ src_files += [
|
||||
if true
|
||||
add_project_arguments('-DHAVE_WLR', language: 'cpp')
|
||||
src_files += 'src/modules/wlr/taskbar.cpp'
|
||||
src_files += 'src/modules/wlr/workspace_manager.cpp'
|
||||
src_files += 'src/modules/wlr/workspace_manager_binding.cpp'
|
||||
endif
|
||||
|
||||
if true
|
||||
@ -215,6 +223,11 @@ if libudev.found() and (is_linux or libepoll.found())
|
||||
src_files += 'src/modules/backlight.cpp'
|
||||
endif
|
||||
|
||||
if libevdev.found() and (is_linux or libepoll.found())
|
||||
add_project_arguments('-DHAVE_LIBEVDEV', language: 'cpp')
|
||||
src_files += 'src/modules/keyboard_state.cpp'
|
||||
endif
|
||||
|
||||
if libmpdclient.found()
|
||||
add_project_arguments('-DHAVE_LIBMPDCLIENT', language: 'cpp')
|
||||
src_files += 'src/modules/mpd/mpd.cpp'
|
||||
@ -230,6 +243,11 @@ if libsndio.found()
|
||||
src_files += 'src/modules/sndio.cpp'
|
||||
endif
|
||||
|
||||
if (giounix.found() and not get_option('logind').disabled())
|
||||
add_project_arguments('-DHAVE_GIO_UNIX', language: 'cpp')
|
||||
src_files += 'src/modules/inhibitor.cpp'
|
||||
endif
|
||||
|
||||
if get_option('rfkill').enabled()
|
||||
if is_linux
|
||||
add_project_arguments('-DWANT_RFKILL', language: 'cpp')
|
||||
@ -247,6 +265,10 @@ else
|
||||
src_files += 'src/modules/simpleclock.cpp'
|
||||
endif
|
||||
|
||||
if get_option('experimental')
|
||||
add_project_arguments('-DUSE_EXPERIMENTAL', language: 'cpp')
|
||||
endif
|
||||
|
||||
subdir('protocol')
|
||||
|
||||
executable(
|
||||
@ -270,9 +292,11 @@ executable(
|
||||
libudev,
|
||||
libepoll,
|
||||
libmpdclient,
|
||||
libevdev,
|
||||
gtk_layer_shell,
|
||||
libsndio,
|
||||
tz_dep
|
||||
tz_dep,
|
||||
xkbregistry
|
||||
],
|
||||
include_directories: [include_directories('include')],
|
||||
install: true,
|
||||
@ -310,6 +334,7 @@ if scdoc.found()
|
||||
'waybar-custom.5.scd',
|
||||
'waybar-disk.5.scd',
|
||||
'waybar-idle-inhibitor.5.scd',
|
||||
'waybar-keyboard-state.5.scd',
|
||||
'waybar-memory.5.scd',
|
||||
'waybar-mpd.5.scd',
|
||||
'waybar-network.5.scd',
|
||||
@ -323,10 +348,15 @@ if scdoc.found()
|
||||
'waybar-tray.5.scd',
|
||||
'waybar-states.5.scd',
|
||||
'waybar-wlr-taskbar.5.scd',
|
||||
'waybar-wlr-workspaces.5.scd',
|
||||
'waybar-bluetooth.5.scd',
|
||||
'waybar-sndio.5.scd',
|
||||
]
|
||||
|
||||
if (giounix.found() and not get_option('logind').disabled())
|
||||
man_files += 'waybar-inhibitor.5.scd'
|
||||
endif
|
||||
|
||||
foreach file : man_files
|
||||
path = '@0@'.format(file)
|
||||
basename = path.split('/')[-1]
|
||||
@ -349,6 +379,15 @@ if scdoc.found()
|
||||
endforeach
|
||||
endif
|
||||
|
||||
catch2 = dependency(
|
||||
'catch2',
|
||||
fallback: ['catch2', 'catch2_dep'],
|
||||
required: get_option('tests'),
|
||||
)
|
||||
if catch2.found()
|
||||
subdir('test')
|
||||
endif
|
||||
|
||||
clangtidy = find_program('clang-tidy', required: false)
|
||||
|
||||
if clangtidy.found()
|
||||
@ -360,3 +399,4 @@ if clangtidy.found()
|
||||
'-p', meson.build_root()
|
||||
] + src_files)
|
||||
endif
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
option('libcxx', type : 'boolean', value : false, description : 'Build with Clang\'s libc++ instead of libstdc++ on Linux.')
|
||||
option('libnl', type: 'feature', value: 'auto', description: 'Enable libnl support for network related features')
|
||||
option('libudev', type: 'feature', value: 'auto', description: 'Enable libudev support for udev related features')
|
||||
option('libevdev', type: 'feature', value: 'auto', description: 'Enable libevdev support for evdev related features')
|
||||
option('pulseaudio', type: 'feature', value: 'auto', description: 'Enable support for pulseaudio')
|
||||
option('systemd', type: 'feature', value: 'auto', description: 'Install systemd user service unit')
|
||||
option('dbusmenu-gtk', type: 'feature', value: 'auto', description: 'Enable support for tray')
|
||||
@ -9,3 +10,6 @@ option('mpd', type: 'feature', value: 'auto', description: 'Enable support for t
|
||||
option('gtk-layer-shell', type: 'feature', value: 'auto', description: 'Use gtk-layer-shell library for popups support')
|
||||
option('rfkill', type: 'feature', value: 'auto', description: 'Enable support for RFKILL')
|
||||
option('sndio', type: 'feature', value: 'auto', description: 'Enable support for sndio')
|
||||
option('logind', type: 'feature', value: 'auto', description: 'Enable support for logind')
|
||||
option('tests', type: 'feature', value: 'auto', description: 'Enable tests')
|
||||
option('experimental', type : 'boolean', value : false, description: 'Enable experimental features')
|
||||
|
306
protocol/ext-workspace-unstable-v1.xml
Normal file
306
protocol/ext-workspace-unstable-v1.xml
Normal file
@ -0,0 +1,306 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<protocol name="ext_workspace_unstable_v1">
|
||||
<copyright>
|
||||
Copyright © 2019 Christopher Billington
|
||||
Copyright © 2020 Ilia Bozhinov
|
||||
|
||||
Permission to use, copy, modify, distribute, and sell this
|
||||
software and its documentation for any purpose is hereby granted
|
||||
without fee, provided that the above copyright notice appear in
|
||||
all copies and that both that copyright notice and this permission
|
||||
notice appear in supporting documentation, and that the name of
|
||||
the copyright holders not be used in advertising or publicity
|
||||
pertaining to distribution of the software without specific,
|
||||
written prior permission. The copyright holders make no
|
||||
representations about the suitability of this software for any
|
||||
purpose. It is provided "as is" without express or implied
|
||||
warranty.
|
||||
|
||||
THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
|
||||
SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
|
||||
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
|
||||
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
||||
THIS SOFTWARE.
|
||||
</copyright>
|
||||
|
||||
<interface name="zext_workspace_manager_v1" version="1">
|
||||
<description summary="list and control workspaces">
|
||||
Workspaces, also called virtual desktops, are groups of surfaces. A
|
||||
compositor with a concept of workspaces may only show some such groups of
|
||||
surfaces (those of 'active' workspaces) at a time. 'Activating' a
|
||||
workspace is a request for the compositor to display that workspace's
|
||||
surfaces as normal, whereas the compositor may hide or otherwise
|
||||
de-emphasise surfaces that are associated only with 'inactive' workspaces.
|
||||
Workspaces are grouped by which sets of outputs they correspond to, and
|
||||
may contain surfaces only from those outputs. In this way, it is possible
|
||||
for each output to have its own set of workspaces, or for all outputs (or
|
||||
any other arbitrary grouping) to share workspaces. Compositors may
|
||||
optionally conceptually arrange each group of workspaces in an
|
||||
N-dimensional grid.
|
||||
|
||||
The purpose of this protocol is to enable the creation of taskbars and
|
||||
docks by providing them with a list of workspaces and their properties,
|
||||
and allowing them to activate and deactivate workspaces.
|
||||
|
||||
After a client binds the zext_workspace_manager_v1, each workspace will be
|
||||
sent via the workspace event.
|
||||
</description>
|
||||
|
||||
<event name="workspace_group">
|
||||
<description summary="a workspace group has been created">
|
||||
This event is emitted whenever a new workspace group has been created.
|
||||
|
||||
All initial details of the workspace group (workspaces, outputs) will be
|
||||
sent immediately after this event via the corresponding events in
|
||||
zext_workspace_group_handle_v1.
|
||||
</description>
|
||||
<arg name="workspace_group" type="new_id" interface="zext_workspace_group_handle_v1"/>
|
||||
</event>
|
||||
|
||||
<request name="commit">
|
||||
<description summary="all requests about the workspaces have been sent">
|
||||
The client must send this request after it has finished sending other
|
||||
requests. The compositor must process a series of requests preceding a
|
||||
commit request atomically.
|
||||
|
||||
This allows changes to the workspace properties to be seen as atomic,
|
||||
even if they happen via multiple events, and even if they involve
|
||||
multiple zext_workspace_handle_v1 objects, for example, deactivating one
|
||||
workspace and activating another.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<event name="done">
|
||||
<description summary="all information about the workspace groups has been sent">
|
||||
This event is sent after all changes in all workspace groups have been
|
||||
sent.
|
||||
|
||||
This allows changes to one or more zext_workspace_group_handle_v1
|
||||
properties to be seen as atomic, even if they happen via multiple
|
||||
events. In particular, an output moving from one workspace group to
|
||||
another sends an output_enter event and an output_leave event to the two
|
||||
zext_workspace_group_handle_v1 objects in question. The compositor sends
|
||||
the done event only after updating the output information in both
|
||||
workspace groups.
|
||||
</description>
|
||||
</event>
|
||||
|
||||
<event name="finished">
|
||||
<description summary="the compositor has finished with the workspace_manager">
|
||||
This event indicates that the compositor is done sending events to the
|
||||
zext_workspace_manager_v1. The server will destroy the object
|
||||
immediately after sending this request, so it will become invalid and
|
||||
the client should free any resources associated with it.
|
||||
</description>
|
||||
</event>
|
||||
|
||||
<request name="stop">
|
||||
<description summary="stop sending events">
|
||||
Indicates the client no longer wishes to receive events for new
|
||||
workspace groups. However the compositor may emit further workspace
|
||||
events, until the finished event is emitted.
|
||||
|
||||
The client must not send any more requests after this one.
|
||||
</description>
|
||||
</request>
|
||||
</interface>
|
||||
|
||||
<interface name="zext_workspace_group_handle_v1" version="1">
|
||||
<description summary="a workspace group assigned to a set of outputs">
|
||||
A zext_workspace_group_handle_v1 object represents a a workspace group
|
||||
that is assigned a set of outputs and contains a number of workspaces.
|
||||
|
||||
The set of outputs assigned to the workspace group is conveyed to the client via
|
||||
output_enter and output_leave events, and its workspaces are conveyed with
|
||||
workspace events.
|
||||
|
||||
For example, a compositor which has a set of workspaces for each output may
|
||||
advertise a workspace group (and its workspaces) per output, whereas a compositor
|
||||
where a workspace spans all outputs may advertise a single workspace group for all
|
||||
outputs.
|
||||
</description>
|
||||
|
||||
<event name="output_enter">
|
||||
<description summary="output assigned to workspace group">
|
||||
This event is emitted whenever an output is assigned to the workspace
|
||||
group.
|
||||
</description>
|
||||
<arg name="output" type="object" interface="wl_output"/>
|
||||
</event>
|
||||
|
||||
<event name="output_leave">
|
||||
<description summary="output removed from workspace group">
|
||||
This event is emitted whenever an output is removed from the workspace
|
||||
group.
|
||||
</description>
|
||||
<arg name="output" type="object" interface="wl_output"/>
|
||||
</event>
|
||||
|
||||
<event name="workspace">
|
||||
<description summary="workspace added to workspace group">
|
||||
This event is emitted whenever a new workspace has been created.
|
||||
|
||||
All initial details of the workspace (name, coordinates, state) will
|
||||
be sent immediately after this event via the corresponding events in
|
||||
zext_workspace_handle_v1.
|
||||
</description>
|
||||
<arg name="workspace" type="new_id" interface="zext_workspace_handle_v1"/>
|
||||
</event>
|
||||
|
||||
<event name="remove">
|
||||
<description summary="this workspace group has been destroyed">
|
||||
This event means the zext_workspace_group_handle_v1 has been destroyed.
|
||||
It is guaranteed there won't be any more events for this
|
||||
zext_workspace_group_handle_v1. The zext_workspace_group_handle_v1 becomes
|
||||
inert so any requests will be ignored except the destroy request.
|
||||
|
||||
The compositor must remove all workspaces belonging to a workspace group
|
||||
before removing the workspace group.
|
||||
</description>
|
||||
</event>
|
||||
|
||||
<request name="create_workspace">
|
||||
<description summary="create a new workspace">
|
||||
Request that the compositor create a new workspace with the given name.
|
||||
|
||||
There is no guarantee that the compositor will create a new workspace,
|
||||
or that the created workspace will have the provided name.
|
||||
</description>
|
||||
<arg name="workspace" type="string"/>
|
||||
</request>
|
||||
|
||||
<request name="destroy" type="destructor">
|
||||
<description summary="destroy the zext_workspace_handle_v1 object">
|
||||
Destroys the zext_workspace_handle_v1 object.
|
||||
|
||||
This request should be called either when the client does not want to
|
||||
use the workspace object any more or after the remove event to finalize
|
||||
the destruction of the object.
|
||||
</description>
|
||||
</request>
|
||||
</interface>
|
||||
|
||||
<interface name="zext_workspace_handle_v1" version="1">
|
||||
<description summary="a workspace handing a group of surfaces">
|
||||
A zext_workspace_handle_v1 object represents a a workspace that handles a
|
||||
group of surfaces.
|
||||
|
||||
Each workspace has a name, conveyed to the client with the name event; a
|
||||
list of states, conveyed to the client with the state event; and
|
||||
optionally a set of coordinates, conveyed to the client with the
|
||||
coordinates event. The client may request that the compositor activate or
|
||||
deactivate the workspace.
|
||||
|
||||
Each workspace can belong to only a single workspace group.
|
||||
Depepending on the compositor policy, there might be workspaces with
|
||||
the same name in different workspace groups, but these workspaces are still
|
||||
separate (e.g. one of them might be active while the other is not).
|
||||
</description>
|
||||
|
||||
<event name="name">
|
||||
<description summary="workspace name changed">
|
||||
This event is emitted immediately after the zext_workspace_handle_v1 is
|
||||
created and whenever the name of the workspace changes.
|
||||
</description>
|
||||
<arg name="name" type="string"/>
|
||||
</event>
|
||||
|
||||
<event name="coordinates">
|
||||
<description summary="workspace coordinates changed">
|
||||
This event is used to organize workspaces into an N-dimensional grid
|
||||
within a workspace group, and if supported, is emitted immediately after
|
||||
the zext_workspace_handle_v1 is created and whenever the coordinates of
|
||||
the workspace change. Compositors may not send this event if they do not
|
||||
conceptually arrange workspaces in this way. If compositors simply
|
||||
number workspaces, without any geometric interpretation, they may send
|
||||
1D coordinates, which clients should not interpret as implying any
|
||||
geometry. Sending an empty array means that the compositor no longer
|
||||
orders the workspace geometrically.
|
||||
|
||||
Coordinates have an arbitrary number of dimensions N with an uint32
|
||||
position along each dimension. By convention if N > 1, the first
|
||||
dimension is X, the second Y, the third Z, and so on. The compositor may
|
||||
chose to utilize these events for a more novel workspace layout
|
||||
convention, however. No guarantee is made about the grid being filled or
|
||||
bounded; there may be a workspace at coordinate 1 and another at
|
||||
coordinate 1000 and none in between. Within a workspace group, however,
|
||||
workspaces must have unique coordinates of equal dimensionality.
|
||||
</description>
|
||||
<arg name="coordinates" type="array"/>
|
||||
</event>
|
||||
|
||||
<event name="state">
|
||||
<description summary="the state of the workspace changed">
|
||||
This event is emitted immediately after the zext_workspace_handle_v1 is
|
||||
created and each time the workspace state changes, either because of a
|
||||
compositor action or because of a request in this protocol.
|
||||
</description>
|
||||
<arg name="state" type="array"/>
|
||||
</event>
|
||||
|
||||
<enum name="state">
|
||||
<description summary="types of states on the workspace">
|
||||
The different states that a workspace can have.
|
||||
</description>
|
||||
|
||||
<entry name="active" value="0" summary="the workspace is active"/>
|
||||
<entry name="urgent" value="1" summary="the workspace requests attention"/>
|
||||
<entry name="hidden" value="2">
|
||||
<description summary="the workspace is not visible">
|
||||
The workspace is not visible in its workspace group, and clients
|
||||
attempting to visualize the compositor workspace state should not
|
||||
display such workspaces.
|
||||
</description>
|
||||
</entry>
|
||||
</enum>
|
||||
|
||||
<event name="remove">
|
||||
<description summary="this workspace has been destroyed">
|
||||
This event means the zext_workspace_handle_v1 has been destroyed. It is
|
||||
guaranteed there won't be any more events for this
|
||||
zext_workspace_handle_v1. The zext_workspace_handle_v1 becomes inert so
|
||||
any requests will be ignored except the destroy request.
|
||||
</description>
|
||||
</event>
|
||||
|
||||
<request name="destroy" type="destructor">
|
||||
<description summary="destroy the zext_workspace_handle_v1 object">
|
||||
Destroys the zext_workspace_handle_v1 object.
|
||||
|
||||
This request should be called either when the client does not want to
|
||||
use the workspace object any more or after the remove event to finalize
|
||||
the destruction of the object.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<request name="activate">
|
||||
<description summary="activate the workspace">
|
||||
Request that this workspace be activated.
|
||||
|
||||
There is no guarantee the workspace will be actually activated, and
|
||||
behaviour may be compositor-dependent. For example, activating a
|
||||
workspace may or may not deactivate all other workspaces in the same
|
||||
group.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<request name="deactivate">
|
||||
<description summary="activate the workspace">
|
||||
Request that this workspace be deactivated.
|
||||
|
||||
There is no guarantee the workspace will be actually deactivated.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<request name="remove">
|
||||
<description summary="remove the workspace">
|
||||
Request that this workspace be removed.
|
||||
|
||||
There is no guarantee the workspace will be actually removed.
|
||||
</description>
|
||||
</request>
|
||||
</interface>
|
||||
</protocol>
|
@ -27,7 +27,9 @@ client_protocols = [
|
||||
[wl_protocol_dir, 'unstable/idle-inhibit/idle-inhibit-unstable-v1.xml'],
|
||||
['wlr-layer-shell-unstable-v1.xml'],
|
||||
['wlr-foreign-toplevel-management-unstable-v1.xml'],
|
||||
['ext-workspace-unstable-v1.xml'],
|
||||
['river-status-unstable-v1.xml'],
|
||||
['river-control-unstable-v1.xml'],
|
||||
]
|
||||
|
||||
client_protos_src = []
|
||||
|
85
protocol/river-control-unstable-v1.xml
Normal file
85
protocol/river-control-unstable-v1.xml
Normal file
@ -0,0 +1,85 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<protocol name="river_control_unstable_v1">
|
||||
<copyright>
|
||||
Copyright 2020 The River Developers
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
</copyright>
|
||||
|
||||
<interface name="zriver_control_v1" version="1">
|
||||
<description summary="run compositor commands">
|
||||
This interface allows clients to run compositor commands and receive a
|
||||
success/failure response with output or a failure message respectively.
|
||||
|
||||
Each command is built up in a series of add_argument requests and
|
||||
executed with a run_command request. The first argument is the command
|
||||
to be run.
|
||||
|
||||
A complete list of commands should be made available in the man page of
|
||||
the compositor.
|
||||
</description>
|
||||
|
||||
<request name="destroy" type="destructor">
|
||||
<description summary="destroy the river_control object">
|
||||
This request indicates that the client will not use the
|
||||
river_control object any more. Objects that have been created
|
||||
through this instance are not affected.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<request name="add_argument">
|
||||
<description summary="add an argument to the current command">
|
||||
Arguments are stored by the server in the order they were sent until
|
||||
the run_command request is made.
|
||||
</description>
|
||||
<arg name="argument" type="string" summary="the argument to add"/>
|
||||
</request>
|
||||
|
||||
<request name="run_command">
|
||||
<description summary="run the current command">
|
||||
Execute the command built up using the add_argument request for the
|
||||
given seat.
|
||||
</description>
|
||||
<arg name="seat" type="object" interface="wl_seat"/>
|
||||
<arg name="callback" type="new_id" interface="zriver_command_callback_v1"
|
||||
summary="callback object"/>
|
||||
</request>
|
||||
</interface>
|
||||
|
||||
<interface name="zriver_command_callback_v1" version="1">
|
||||
<description summary="callback object">
|
||||
This object is created by the run_command request. Exactly one of the
|
||||
success or failure events will be sent. This object will be destroyed
|
||||
by the compositor after one of the events is sent.
|
||||
</description>
|
||||
|
||||
<event name="success">
|
||||
<description summary="command successful">
|
||||
Sent when the command has been successfully received and executed by
|
||||
the compositor. Some commands may produce output, in which case the
|
||||
output argument will be a non-empty string.
|
||||
</description>
|
||||
<arg name="output" type="string" summary="the output of the command"/>
|
||||
</event>
|
||||
|
||||
<event name="failure">
|
||||
<description summary="command failed">
|
||||
Sent when the command could not be carried out. This could be due to
|
||||
sending a non-existent command, no command, not enough arguments, too
|
||||
many arguments, invalid arguments, etc.
|
||||
</description>
|
||||
<arg name="failure_message" type="string"
|
||||
summary="a message explaining why failure occurred"/>
|
||||
</event>
|
||||
</interface>
|
||||
</protocol>
|
@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<protocol name="river_status_unstable_v1">
|
||||
<copyright>
|
||||
Copyright 2020 Isaac Freund
|
||||
Copyright 2020 The River Developers
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
@ -16,7 +16,7 @@
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
</copyright>
|
||||
|
||||
<interface name="zriver_status_manager_v1" version="1">
|
||||
<interface name="zriver_status_manager_v1" version="2">
|
||||
<description summary="manage river status objects">
|
||||
A global factory for objects that receive status information specific
|
||||
to river. It could be used to implement, for example, a status bar.
|
||||
@ -47,7 +47,7 @@
|
||||
</request>
|
||||
</interface>
|
||||
|
||||
<interface name="zriver_output_status_v1" version="1">
|
||||
<interface name="zriver_output_status_v1" version="2">
|
||||
<description summary="track output tags and focus">
|
||||
This interface allows clients to receive information about the current
|
||||
windowing state of an output.
|
||||
@ -75,12 +75,21 @@
|
||||
</description>
|
||||
<arg name="tags" type="array" summary="array of 32-bit bitfields"/>
|
||||
</event>
|
||||
|
||||
<event name="urgent_tags" since="2">
|
||||
<description summary="tags of the output with an urgent view">
|
||||
Sent once on binding the interface and again whenever the set of
|
||||
tags with at least one urgent view changes.
|
||||
</description>
|
||||
<arg name="tags" type="uint" summary="32-bit bitfield"/>
|
||||
</event>
|
||||
</interface>
|
||||
|
||||
<interface name="zriver_seat_status_v1" version="1">
|
||||
<description summary="track seat focus">
|
||||
This interface allows clients to receive information about the current
|
||||
focus of a seat.
|
||||
focus of a seat. Note that (un)focused_output events will only be sent
|
||||
if the client has bound the relevant wl_output globals.
|
||||
</description>
|
||||
|
||||
<request name="destroy" type="destructor">
|
||||
|
@ -3,10 +3,11 @@
|
||||
// "position": "bottom", // Waybar position (top|bottom|left|right)
|
||||
"height": 30, // Waybar height (to be removed for auto height)
|
||||
// "width": 1280, // Waybar width
|
||||
"spacing": 4, // Gaps between modules (4px)
|
||||
// Choose the order of the modules
|
||||
"modules-left": ["sway/workspaces", "sway/mode", "custom/media"],
|
||||
"modules-center": ["sway/window"],
|
||||
"modules-right": ["mpd", "idle_inhibitor", "pulseaudio", "network", "cpu", "memory", "temperature", "backlight", "sway/language", "battery", "battery#bat2", "clock", "tray"],
|
||||
"modules-right": ["mpd", "idle_inhibitor", "pulseaudio", "network", "cpu", "memory", "temperature", "backlight", "keyboard-state", "sway/language", "battery", "battery#bat2", "clock", "tray"],
|
||||
// Modules configuration
|
||||
// "sway/workspaces": {
|
||||
// "disable-scroll": true,
|
||||
@ -23,11 +24,20 @@
|
||||
// "default": ""
|
||||
// }
|
||||
// },
|
||||
"keyboard-state": {
|
||||
"numlock": true,
|
||||
"capslock": true,
|
||||
"format": "{name} {icon}",
|
||||
"format-icons": {
|
||||
"locked": "",
|
||||
"unlocked": ""
|
||||
}
|
||||
},
|
||||
"sway/mode": {
|
||||
"format": "<span style=\"italic\">{}</span>"
|
||||
},
|
||||
"mpd": {
|
||||
"format": "{stateIcon} {consumeIcon}{randomIcon}{repeatIcon}{singleIcon}{artist} - {album} - {title} ({elapsedTime:%M:%S}/{totalTime:%M:%S}) ⸨{songPosition}|{queueLength}⸩ ",
|
||||
"format": "{stateIcon} {consumeIcon}{randomIcon}{repeatIcon}{singleIcon}{artist} - {album} - {title} ({elapsedTime:%M:%S}/{totalTime:%M:%S}) ⸨{songPosition}|{queueLength}⸩ {volume}% ",
|
||||
"format-disconnected": "Disconnected ",
|
||||
"format-stopped": "{consumeIcon}{randomIcon}{repeatIcon}{singleIcon}Stopped ",
|
||||
"unknown-tag": "N/A",
|
||||
@ -108,7 +118,8 @@
|
||||
"network": {
|
||||
// "interface": "wlp2*", // (Optional) To force the use of this interface
|
||||
"format-wifi": "{essid} ({signalStrength}%) ",
|
||||
"format-ethernet": "{ifname}: {ipaddr}/{cidr} ",
|
||||
"format-ethernet": "{ipaddr}/{cidr} ",
|
||||
"tooltip-format": "{ifname} via {gwaddr} ",
|
||||
"format-linked": "{ifname} (No IP) ",
|
||||
"format-disconnected": "Disconnected ⚠",
|
||||
"format-alt": "{ifname}: {ipaddr}/{cidr}"
|
||||
@ -145,3 +156,4 @@
|
||||
// "exec": "$HOME/.config/waybar/mediaplayer.py --player spotify 2> /dev/null" // Filter player based on name
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -79,7 +79,7 @@ def signal_handler(sig, frame):
|
||||
def parse_arguments():
|
||||
parser = argparse.ArgumentParser()
|
||||
|
||||
# Increase verbosity with every occurence of -v
|
||||
# Increase verbosity with every occurrence of -v
|
||||
parser.add_argument('-v', '--verbose', action='count', default=0)
|
||||
|
||||
# Define for which player we're listening
|
||||
@ -110,6 +110,7 @@ def main():
|
||||
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
signal.signal(signal.SIGTERM, signal_handler)
|
||||
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
||||
|
||||
for player in manager.props.player_names:
|
||||
if arguments.player is not None and arguments.player != player.name:
|
||||
|
@ -1,10 +1,7 @@
|
||||
* {
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
/* `otf-font-awesome` is required to be installed for icons */
|
||||
font-family: Roboto, Helvetica, Arial, sans-serif;
|
||||
font-family: FontAwesome, Roboto, Helvetica, Arial, sans-serif;
|
||||
font-size: 13px;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
window#waybar {
|
||||
@ -43,6 +40,9 @@ window#waybar.chromium {
|
||||
color: #ffffff;
|
||||
/* Use box-shadow instead of border so the text isn't offset */
|
||||
box-shadow: inset 0 -3px transparent;
|
||||
/* Avoid rounded borders under each workspace name */
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
/* https://github.com/Alexays/Waybar/wiki/FAQ#the-workspace-buttons-have-a-strange-hover-effect */
|
||||
@ -80,7 +80,6 @@ window#waybar.chromium {
|
||||
#idle_inhibitor,
|
||||
#mpd {
|
||||
padding: 0 10px;
|
||||
margin: 0 4px;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
@ -195,6 +194,15 @@ label:focus {
|
||||
background-color: #2980b9;
|
||||
}
|
||||
|
||||
#tray > .passive {
|
||||
-gtk-icon-effect: dim;
|
||||
}
|
||||
|
||||
#tray > .needs-attention {
|
||||
-gtk-icon-effect: highlight;
|
||||
background-color: #eb4d4b;
|
||||
}
|
||||
|
||||
#idle_inhibitor {
|
||||
background-color: #2d3436;
|
||||
}
|
||||
@ -228,3 +236,19 @@ label:focus {
|
||||
margin: 0 5px;
|
||||
min-width: 16px;
|
||||
}
|
||||
|
||||
#keyboard-state {
|
||||
background: #97e1ad;
|
||||
color: #000000;
|
||||
padding: 0 0px;
|
||||
margin: 0 5px;
|
||||
min-width: 16px;
|
||||
}
|
||||
|
||||
#keyboard-state > label {
|
||||
padding: 0 5px;
|
||||
}
|
||||
|
||||
#keyboard-state > label.locked {
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ Description=Highly customizable Wayland bar for Sway and Wlroots based composito
|
||||
Documentation=https://github.com/Alexays/Waybar/wiki/
|
||||
PartOf=graphical-session.target
|
||||
After=graphical-session.target
|
||||
Requisite=graphical-session.target
|
||||
|
||||
[Service]
|
||||
ExecStart=@prefix@/bin/waybar
|
||||
|
28
src/AIconLabel.cpp
Normal file
28
src/AIconLabel.cpp
Normal file
@ -0,0 +1,28 @@
|
||||
#include "AIconLabel.hpp"
|
||||
|
||||
#include <gdkmm/pixbuf.h>
|
||||
|
||||
namespace waybar {
|
||||
|
||||
AIconLabel::AIconLabel(const Json::Value &config, const std::string &name, const std::string &id,
|
||||
const std::string &format, uint16_t interval, bool ellipsize,
|
||||
bool enable_click, bool enable_scroll)
|
||||
: ALabel(config, name, id, format, interval, ellipsize, enable_click, enable_scroll) {
|
||||
event_box_.remove();
|
||||
box_.set_orientation(Gtk::Orientation::ORIENTATION_HORIZONTAL);
|
||||
box_.set_spacing(8);
|
||||
box_.add(image_);
|
||||
box_.add(label_);
|
||||
event_box_.add(box_);
|
||||
}
|
||||
|
||||
auto AIconLabel::update() -> void {
|
||||
image_.set_visible(image_.get_visible() && iconEnabled());
|
||||
ALabel::update();
|
||||
}
|
||||
|
||||
bool AIconLabel::iconEnabled() const {
|
||||
return config_["icon"].isBool() ? config_["icon"].asBool() : false;
|
||||
}
|
||||
|
||||
} // namespace waybar
|
@ -67,8 +67,10 @@ std::string ALabel::getIcon(uint16_t percentage, const std::string& alt, uint16_
|
||||
}
|
||||
if (format_icons.isArray()) {
|
||||
auto size = format_icons.size();
|
||||
auto idx = std::clamp(percentage / ((max == 0 ? 100 : max) / size), 0U, size - 1);
|
||||
format_icons = format_icons[idx];
|
||||
if (size) {
|
||||
auto idx = std::clamp(percentage / ((max == 0 ? 100 : max) / size), 0U, size - 1);
|
||||
format_icons = format_icons[idx];
|
||||
}
|
||||
}
|
||||
if (format_icons.isString()) {
|
||||
return format_icons.asString();
|
||||
@ -76,7 +78,7 @@ std::string ALabel::getIcon(uint16_t percentage, const std::string& alt, uint16_
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string ALabel::getIcon(uint16_t percentage, std::vector<std::string>& alts, uint16_t max) {
|
||||
std::string ALabel::getIcon(uint16_t percentage, const std::vector<std::string>& alts, uint16_t max) {
|
||||
auto format_icons = config_["format-icons"];
|
||||
if (format_icons.isObject()) {
|
||||
std::string _alt = "default";
|
||||
@ -90,8 +92,10 @@ std::string ALabel::getIcon(uint16_t percentage, std::vector<std::string>& alts,
|
||||
}
|
||||
if (format_icons.isArray()) {
|
||||
auto size = format_icons.size();
|
||||
auto idx = std::clamp(percentage / ((max == 0 ? 100 : max) / size), 0U, size - 1);
|
||||
format_icons = format_icons[idx];
|
||||
if (size) {
|
||||
auto idx = std::clamp(percentage / ((max == 0 ? 100 : max) / size), 0U, size - 1);
|
||||
format_icons = format_icons[idx];
|
||||
}
|
||||
}
|
||||
if (format_icons.isString()) {
|
||||
return format_icons.asString();
|
||||
|
@ -6,7 +6,9 @@ namespace waybar {
|
||||
|
||||
AModule::AModule(const Json::Value& config, const std::string& name, const std::string& id,
|
||||
bool enable_click, bool enable_scroll)
|
||||
: name_(std::move(name)), config_(std::move(config)) {
|
||||
: name_(std::move(name)), config_(std::move(config))
|
||||
, distance_scrolled_y_(0.0)
|
||||
, distance_scrolled_x_(0.0) {
|
||||
// configure events' user commands
|
||||
if (config_["on-click"].isString() || config_["on-click-middle"].isString() ||
|
||||
config_["on-click-backward"].isString() || config_["on-click-forward"].isString() ||
|
||||
|
277
src/bar.cpp
277
src/bar.cpp
@ -9,20 +9,103 @@
|
||||
#include "bar.hpp"
|
||||
#include "client.hpp"
|
||||
#include "factory.hpp"
|
||||
#include "group.hpp"
|
||||
#include "wlr-layer-shell-unstable-v1-client-protocol.h"
|
||||
|
||||
#ifdef HAVE_SWAY
|
||||
#include "modules/sway/bar.hpp"
|
||||
#endif
|
||||
|
||||
namespace waybar {
|
||||
static constexpr const char* MIN_HEIGHT_MSG =
|
||||
"Requested height: {} exceeds the minimum height: {} required by the modules";
|
||||
"Requested height: {} is less than the minimum height: {} required by the modules";
|
||||
|
||||
static constexpr const char* MIN_WIDTH_MSG =
|
||||
"Requested width: {} exceeds the minimum width: {} required by the modules";
|
||||
"Requested width: {} is less than the minimum width: {} required by the modules";
|
||||
|
||||
static constexpr const char* BAR_SIZE_MSG = "Bar configured (width: {}, height: {}) for output: {}";
|
||||
|
||||
static constexpr const char* SIZE_DEFINED =
|
||||
"{} size is defined in the config file so it will stay like that";
|
||||
|
||||
const Bar::bar_mode_map Bar::PRESET_MODES = { //
|
||||
{"default",
|
||||
{// Special mode to hold the global bar configuration
|
||||
.layer = bar_layer::BOTTOM,
|
||||
.exclusive = true,
|
||||
.passthrough = false,
|
||||
.visible = true}},
|
||||
{"dock",
|
||||
{// Modes supported by the sway config; see man sway-bar(5)
|
||||
.layer = bar_layer::BOTTOM,
|
||||
.exclusive = true,
|
||||
.passthrough = false,
|
||||
.visible = true}},
|
||||
{"hide",
|
||||
{//
|
||||
.layer = bar_layer::TOP,
|
||||
.exclusive = false,
|
||||
.passthrough = false,
|
||||
.visible = true}},
|
||||
{"invisible",
|
||||
{//
|
||||
.layer = bar_layer::BOTTOM,
|
||||
.exclusive = false,
|
||||
.passthrough = true,
|
||||
.visible = false}},
|
||||
{"overlay",
|
||||
{//
|
||||
.layer = bar_layer::TOP,
|
||||
.exclusive = false,
|
||||
.passthrough = true,
|
||||
.visible = true}}};
|
||||
|
||||
const std::string_view Bar::MODE_DEFAULT = "default";
|
||||
const std::string_view Bar::MODE_INVISIBLE = "invisible";
|
||||
const std::string_view DEFAULT_BAR_ID = "bar-0";
|
||||
|
||||
/* Deserializer for enum bar_layer */
|
||||
void from_json(const Json::Value& j, bar_layer& l) {
|
||||
if (j == "bottom") {
|
||||
l = bar_layer::BOTTOM;
|
||||
} else if (j == "top") {
|
||||
l = bar_layer::TOP;
|
||||
} else if (j == "overlay") {
|
||||
l = bar_layer::OVERLAY;
|
||||
}
|
||||
}
|
||||
|
||||
/* Deserializer for struct bar_mode */
|
||||
void from_json(const Json::Value& j, bar_mode& m) {
|
||||
if (j.isObject()) {
|
||||
if (auto v = j["layer"]; v.isString()) {
|
||||
from_json(v, m.layer);
|
||||
}
|
||||
if (auto v = j["exclusive"]; v.isBool()) {
|
||||
m.exclusive = v.asBool();
|
||||
}
|
||||
if (auto v = j["passthrough"]; v.isBool()) {
|
||||
m.passthrough = v.asBool();
|
||||
}
|
||||
if (auto v = j["visible"]; v.isBool()) {
|
||||
m.visible = v.asBool();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Deserializer for JSON Object -> map<string compatible type, Value>
|
||||
* Assumes that all the values in the object are deserializable to the same type.
|
||||
*/
|
||||
template <typename Key, typename Value,
|
||||
typename = std::enable_if_t<std::is_convertible<std::string_view, Key>::value>>
|
||||
void from_json(const Json::Value& j, std::map<Key, Value>& m) {
|
||||
if (j.isObject()) {
|
||||
for (auto it = j.begin(); it != j.end(); ++it) {
|
||||
from_json(*it, m[it.key().asString()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef HAVE_GTK_LAYER_SHELL
|
||||
struct GLSSurfaceImpl : public BarSurface, public sigc::trackable {
|
||||
GLSSurfaceImpl(Gtk::Window& window, struct waybar_output& output) : window_{window} {
|
||||
@ -33,6 +116,7 @@ struct GLSSurfaceImpl : public BarSurface, public sigc::trackable {
|
||||
gtk_layer_set_monitor(window_.gobj(), output.monitor->gobj());
|
||||
gtk_layer_set_namespace(window_.gobj(), "waybar");
|
||||
|
||||
window.signal_map_event().connect_notify(sigc::mem_fun(*this, &GLSSurfaceImpl::onMap));
|
||||
window.signal_configure_event().connect_notify(
|
||||
sigc::mem_fun(*this, &GLSSurfaceImpl::onConfigure));
|
||||
}
|
||||
@ -62,6 +146,18 @@ struct GLSSurfaceImpl : public BarSurface, public sigc::trackable {
|
||||
gtk_layer_set_layer(window_.gobj(), layer);
|
||||
}
|
||||
|
||||
void setPassThrough(bool enable) override {
|
||||
passthrough_ = enable;
|
||||
auto gdk_window = window_.get_window();
|
||||
if (gdk_window) {
|
||||
Cairo::RefPtr<Cairo::Region> region;
|
||||
if (enable) {
|
||||
region = Cairo::Region::create();
|
||||
}
|
||||
gdk_window->input_shape_combine_region(region, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void setPosition(const std::string_view& position) override {
|
||||
auto unanchored = GTK_LAYER_SHELL_EDGE_BOTTOM;
|
||||
vertical_ = false;
|
||||
@ -93,8 +189,11 @@ struct GLSSurfaceImpl : public BarSurface, public sigc::trackable {
|
||||
std::string output_name_;
|
||||
uint32_t width_;
|
||||
uint32_t height_;
|
||||
bool passthrough_ = false;
|
||||
bool vertical_ = false;
|
||||
|
||||
void onMap(GdkEventAny* ev) { setPassThrough(passthrough_); }
|
||||
|
||||
void onConfigure(GdkEventConfigure* ev) {
|
||||
/*
|
||||
* GTK wants new size for the window.
|
||||
@ -182,6 +281,20 @@ struct RawSurfaceImpl : public BarSurface, public sigc::trackable {
|
||||
}
|
||||
}
|
||||
|
||||
void setPassThrough(bool enable) override {
|
||||
passthrough_ = enable;
|
||||
/* GTK overwrites any region changes applied directly to the wl_surface,
|
||||
* thus the same GTK region API as in the GLS impl has to be used. */
|
||||
auto gdk_window = window_.get_window();
|
||||
if (gdk_window) {
|
||||
Cairo::RefPtr<Cairo::Region> region;
|
||||
if (enable) {
|
||||
region = Cairo::Region::create();
|
||||
}
|
||||
gdk_window->input_shape_combine_region(region, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void setPosition(const std::string_view& position) override {
|
||||
anchor_ = HORIZONTAL_ANCHOR | ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP;
|
||||
if (position == "bottom") {
|
||||
@ -230,6 +343,7 @@ struct RawSurfaceImpl : public BarSurface, public sigc::trackable {
|
||||
uint32_t height_ = 0;
|
||||
uint8_t anchor_ = HORIZONTAL_ANCHOR | ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP;
|
||||
bool exclusive_zone_ = true;
|
||||
bool passthrough_ = false;
|
||||
struct bar_margins margins_;
|
||||
|
||||
zwlr_layer_shell_v1_layer layer_ = ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM;
|
||||
@ -262,6 +376,7 @@ struct RawSurfaceImpl : public BarSurface, public sigc::trackable {
|
||||
|
||||
setSurfaceSize(width_, height_);
|
||||
setExclusiveZone(exclusive_zone_);
|
||||
setPassThrough(passthrough_);
|
||||
|
||||
commit();
|
||||
wl_display_roundtrip(client->wl_display);
|
||||
@ -359,7 +474,6 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config)
|
||||
: output(w_output),
|
||||
config(w_config),
|
||||
window{Gtk::WindowType::WINDOW_TOPLEVEL},
|
||||
layer_{bar_layer::BOTTOM},
|
||||
left_(Gtk::ORIENTATION_HORIZONTAL, 0),
|
||||
center_(Gtk::ORIENTATION_HORIZONTAL, 0),
|
||||
right_(Gtk::ORIENTATION_HORIZONTAL, 0),
|
||||
@ -371,12 +485,6 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config)
|
||||
window.get_style_context()->add_class(config["name"].asString());
|
||||
window.get_style_context()->add_class(config["position"].asString());
|
||||
|
||||
if (config["layer"] == "top") {
|
||||
layer_ = bar_layer::TOP;
|
||||
} else if (config["layer"] == "overlay") {
|
||||
layer_ = bar_layer::OVERLAY;
|
||||
}
|
||||
|
||||
auto position = config["position"].asString();
|
||||
|
||||
if (position == "right" || position == "left") {
|
||||
@ -391,6 +499,13 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config)
|
||||
center_.get_style_context()->add_class("modules-center");
|
||||
right_.get_style_context()->add_class("modules-right");
|
||||
|
||||
if (config["spacing"].isInt()) {
|
||||
int spacing = config["spacing"].asInt();
|
||||
left_.set_spacing(spacing);
|
||||
center_.set_spacing(spacing);
|
||||
right_.set_spacing(spacing);
|
||||
}
|
||||
|
||||
uint32_t height = config["height"].isUInt() ? config["height"].asUInt() : 0;
|
||||
uint32_t width = config["width"].isUInt() ? config["width"].asUInt() : 0;
|
||||
|
||||
@ -451,14 +566,43 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config)
|
||||
surface_impl_ = std::make_unique<RawSurfaceImpl>(window, *output);
|
||||
}
|
||||
|
||||
surface_impl_->setLayer(layer_);
|
||||
surface_impl_->setExclusiveZone(true);
|
||||
surface_impl_->setMargins(margins_);
|
||||
surface_impl_->setPosition(position);
|
||||
surface_impl_->setSize(width, height);
|
||||
|
||||
/* Read custom modes if available */
|
||||
if (auto modes = config.get("modes", {}); modes.isObject()) {
|
||||
from_json(modes, configured_modes);
|
||||
}
|
||||
|
||||
/* Update "default" mode with the global bar options */
|
||||
from_json(config, configured_modes[MODE_DEFAULT]);
|
||||
|
||||
if (auto mode = config.get("mode", {}); mode.isString()) {
|
||||
setMode(config["mode"].asString());
|
||||
} else {
|
||||
setMode(MODE_DEFAULT);
|
||||
}
|
||||
|
||||
window.signal_map_event().connect_notify(sigc::mem_fun(*this, &Bar::onMap));
|
||||
|
||||
#if HAVE_SWAY
|
||||
if (auto ipc = config["ipc"]; ipc.isBool() && ipc.asBool()) {
|
||||
bar_id = Client::inst()->bar_id;
|
||||
if (auto id = config["id"]; id.isString()) {
|
||||
bar_id = id.asString();
|
||||
}
|
||||
if (bar_id.empty()) {
|
||||
bar_id = DEFAULT_BAR_ID;
|
||||
}
|
||||
try {
|
||||
_ipc_client = std::make_unique<BarIpcClient>(*this);
|
||||
} catch (const std::exception& exc) {
|
||||
spdlog::warn("Failed to open bar ipc connection: {}", exc.what());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
setupWidgets();
|
||||
window.show_all();
|
||||
|
||||
@ -473,6 +617,44 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config)
|
||||
}
|
||||
}
|
||||
|
||||
/* Need to define it here because of forward declared members */
|
||||
waybar::Bar::~Bar() = default;
|
||||
|
||||
void waybar::Bar::setMode(const std::string_view& mode) {
|
||||
using namespace std::literals::string_literals;
|
||||
|
||||
auto style = window.get_style_context();
|
||||
/* remove styles added by previous setMode calls */
|
||||
style->remove_class("mode-"s + last_mode_);
|
||||
|
||||
auto it = configured_modes.find(mode);
|
||||
if (it != configured_modes.end()) {
|
||||
last_mode_ = mode;
|
||||
style->add_class("mode-"s + last_mode_);
|
||||
setMode(it->second);
|
||||
} else {
|
||||
spdlog::warn("Unknown mode \"{}\" requested", mode);
|
||||
last_mode_ = MODE_DEFAULT;
|
||||
style->add_class("mode-"s + last_mode_);
|
||||
setMode(configured_modes.at(MODE_DEFAULT));
|
||||
}
|
||||
}
|
||||
|
||||
void waybar::Bar::setMode(const struct bar_mode& mode) {
|
||||
surface_impl_->setLayer(mode.layer);
|
||||
surface_impl_->setExclusiveZone(mode.exclusive);
|
||||
surface_impl_->setPassThrough(mode.passthrough);
|
||||
|
||||
if (mode.visible) {
|
||||
window.get_style_context()->remove_class("hidden");
|
||||
window.set_opacity(1);
|
||||
} else {
|
||||
window.get_style_context()->add_class("hidden");
|
||||
window.set_opacity(0);
|
||||
}
|
||||
surface_impl_->commit();
|
||||
}
|
||||
|
||||
void waybar::Bar::onMap(GdkEventAny*) {
|
||||
/*
|
||||
* Obtain a pointer to the custom layer surface for modules that require it (idle_inhibitor).
|
||||
@ -483,17 +665,7 @@ void waybar::Bar::onMap(GdkEventAny*) {
|
||||
|
||||
void waybar::Bar::setVisible(bool value) {
|
||||
visible = value;
|
||||
if (!visible) {
|
||||
window.get_style_context()->add_class("hidden");
|
||||
window.set_opacity(0);
|
||||
surface_impl_->setLayer(bar_layer::BOTTOM);
|
||||
} else {
|
||||
window.get_style_context()->remove_class("hidden");
|
||||
window.set_opacity(1);
|
||||
surface_impl_->setLayer(layer_);
|
||||
}
|
||||
surface_impl_->setExclusiveZone(visible);
|
||||
surface_impl_->commit();
|
||||
setMode(visible ? MODE_DEFAULT : MODE_INVISIBLE);
|
||||
}
|
||||
|
||||
void waybar::Bar::toggle() { setVisible(!visible); }
|
||||
@ -539,19 +711,7 @@ void waybar::Bar::setupAltFormatKeyForModuleList(const char* module_list_name) {
|
||||
}
|
||||
|
||||
void waybar::Bar::handleSignal(int signal) {
|
||||
for (auto& module : modules_left_) {
|
||||
auto* custom = dynamic_cast<waybar::modules::Custom*>(module.get());
|
||||
if (custom != nullptr) {
|
||||
custom->refresh(signal);
|
||||
}
|
||||
}
|
||||
for (auto& module : modules_center_) {
|
||||
auto* custom = dynamic_cast<waybar::modules::Custom*>(module.get());
|
||||
if (custom != nullptr) {
|
||||
custom->refresh(signal);
|
||||
}
|
||||
}
|
||||
for (auto& module : modules_right_) {
|
||||
for (auto& module : modules_all_) {
|
||||
auto* custom = dynamic_cast<waybar::modules::Custom*>(module.get());
|
||||
if (custom != nullptr) {
|
||||
custom->refresh(signal);
|
||||
@ -559,21 +719,38 @@ void waybar::Bar::handleSignal(int signal) {
|
||||
}
|
||||
}
|
||||
|
||||
void waybar::Bar::getModules(const Factory& factory, const std::string& pos) {
|
||||
if (config[pos].isArray()) {
|
||||
for (const auto& name : config[pos]) {
|
||||
void waybar::Bar::getModules(const Factory& factory, const std::string& pos, Gtk::Box* group = nullptr) {
|
||||
auto module_list = group ? config[pos]["modules"] : config[pos];
|
||||
if (module_list.isArray()) {
|
||||
for (const auto& name : module_list) {
|
||||
try {
|
||||
auto module = factory.makeModule(name.asString());
|
||||
if (pos == "modules-left") {
|
||||
modules_left_.emplace_back(module);
|
||||
auto ref = name.asString();
|
||||
AModule* module;
|
||||
|
||||
if (ref.compare(0, 6, "group/") == 0 && ref.size() > 6) {
|
||||
auto group_module = new waybar::Group(ref, *this, config[ref]);
|
||||
getModules(factory, ref, &group_module->box);
|
||||
module = group_module;
|
||||
} else {
|
||||
module = factory.makeModule(ref);
|
||||
}
|
||||
if (pos == "modules-center") {
|
||||
modules_center_.emplace_back(module);
|
||||
|
||||
std::shared_ptr<AModule> module_sp(module);
|
||||
modules_all_.emplace_back(module_sp);
|
||||
if (group) {
|
||||
group->pack_start(*module, false, false);
|
||||
} else {
|
||||
if (pos == "modules-left") {
|
||||
modules_left_.emplace_back(module_sp);
|
||||
}
|
||||
if (pos == "modules-center") {
|
||||
modules_center_.emplace_back(module_sp);
|
||||
}
|
||||
if (pos == "modules-right") {
|
||||
modules_right_.emplace_back(module_sp);
|
||||
}
|
||||
}
|
||||
if (pos == "modules-right") {
|
||||
modules_right_.emplace_back(module);
|
||||
}
|
||||
module->dp.connect([module, &name] {
|
||||
module->dp.connect([module, name] {
|
||||
try {
|
||||
module->update();
|
||||
} catch (const std::exception& e) {
|
||||
@ -590,7 +767,11 @@ void waybar::Bar::getModules(const Factory& factory, const std::string& pos) {
|
||||
auto waybar::Bar::setupWidgets() -> void {
|
||||
window.add(box_);
|
||||
box_.pack_start(left_, false, false);
|
||||
box_.set_center_widget(center_);
|
||||
if (config["fixed-center"].isBool() ? config["fixed-center"].asBool() : true) {
|
||||
box_.set_center_widget(center_);
|
||||
} else {
|
||||
box_.pack_start(center_, true, false);
|
||||
}
|
||||
box_.pack_end(right_, false, false);
|
||||
|
||||
// Convert to button code for every module that is used.
|
||||
|
109
src/client.cpp
109
src/client.cpp
@ -3,12 +3,10 @@
|
||||
#include <fmt/ostream.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
#include "idle-inhibit-unstable-v1-client-protocol.h"
|
||||
#include "util/clara.hpp"
|
||||
#include "util/json.hpp"
|
||||
#include "wlr-layer-shell-unstable-v1-client-protocol.h"
|
||||
|
||||
waybar::Client *waybar::Client::inst() {
|
||||
@ -16,23 +14,6 @@ waybar::Client *waybar::Client::inst() {
|
||||
return c;
|
||||
}
|
||||
|
||||
const std::string waybar::Client::getValidPath(const std::vector<std::string> &paths) const {
|
||||
wordexp_t p;
|
||||
|
||||
for (const std::string &path : paths) {
|
||||
if (wordexp(path.c_str(), &p, 0) == 0) {
|
||||
if (access(*p.we_wordv, F_OK) == 0) {
|
||||
std::string result = *p.we_wordv;
|
||||
wordfree(&p);
|
||||
return result;
|
||||
}
|
||||
wordfree(&p);
|
||||
}
|
||||
}
|
||||
|
||||
return std::string();
|
||||
}
|
||||
|
||||
void waybar::Client::handleGlobal(void *data, struct wl_registry *registry, uint32_t name,
|
||||
const char *interface, uint32_t version) {
|
||||
auto client = static_cast<Client *>(data);
|
||||
@ -70,29 +51,6 @@ void waybar::Client::handleOutput(struct waybar_output &output) {
|
||||
zxdg_output_v1_add_listener(output.xdg_output.get(), &xdgOutputListener, &output);
|
||||
}
|
||||
|
||||
bool waybar::Client::isValidOutput(const Json::Value &config, struct waybar_output &output) {
|
||||
if (config["output"].isArray()) {
|
||||
for (auto const &output_conf : config["output"]) {
|
||||
if (output_conf.isString() &&
|
||||
(output_conf.asString() == output.name || output_conf.asString() == output.identifier)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} else if (config["output"].isString()) {
|
||||
auto config_output = config["output"].asString();
|
||||
if (!config_output.empty()) {
|
||||
if (config_output.substr(0, 1) == "!") {
|
||||
return config_output.substr(1) != output.name &&
|
||||
config_output.substr(1) != output.identifier;
|
||||
}
|
||||
return config_output == output.name || config_output == output.identifier;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
struct waybar::waybar_output &waybar::Client::getOutput(void *addr) {
|
||||
auto it = std::find_if(
|
||||
outputs_.begin(), outputs_.end(), [&addr](const auto &output) { return &output == addr; });
|
||||
@ -103,17 +61,7 @@ struct waybar::waybar_output &waybar::Client::getOutput(void *addr) {
|
||||
}
|
||||
|
||||
std::vector<Json::Value> waybar::Client::getOutputConfigs(struct waybar_output &output) {
|
||||
std::vector<Json::Value> configs;
|
||||
if (config_.isArray()) {
|
||||
for (auto const &config : config_) {
|
||||
if (config.isObject() && isValidOutput(config, output)) {
|
||||
configs.push_back(config);
|
||||
}
|
||||
}
|
||||
} else if (isValidOutput(config_, output)) {
|
||||
configs.push_back(config_);
|
||||
}
|
||||
return configs;
|
||||
return config.getOutputConfigs(output.name, output.identifier);
|
||||
}
|
||||
|
||||
void waybar::Client::handleOutputDone(void *data, struct zxdg_output_v1 * /*xdg_output*/) {
|
||||
@ -203,42 +151,14 @@ void waybar::Client::handleDeferredMonitorRemoval(Glib::RefPtr<Gdk::Monitor> mon
|
||||
outputs_.remove_if([&monitor](const auto &output) { return output.monitor == monitor; });
|
||||
}
|
||||
|
||||
std::tuple<const std::string, const std::string> waybar::Client::getConfigs(
|
||||
const std::string &config, const std::string &style) const {
|
||||
auto config_file = config.empty() ? getValidPath({
|
||||
"$XDG_CONFIG_HOME/waybar/config",
|
||||
"$HOME/.config/waybar/config",
|
||||
"$HOME/waybar/config",
|
||||
"/etc/xdg/waybar/config",
|
||||
SYSCONFDIR "/xdg/waybar/config",
|
||||
"./resources/config",
|
||||
})
|
||||
: config;
|
||||
auto css_file = style.empty() ? getValidPath({
|
||||
"$XDG_CONFIG_HOME/waybar/style.css",
|
||||
"$HOME/.config/waybar/style.css",
|
||||
"$HOME/waybar/style.css",
|
||||
"/etc/xdg/waybar/style.css",
|
||||
SYSCONFDIR "/xdg/waybar/style.css",
|
||||
"./resources/style.css",
|
||||
})
|
||||
: style;
|
||||
if (css_file.empty() || config_file.empty()) {
|
||||
throw std::runtime_error("Missing required resources files");
|
||||
const std::string waybar::Client::getStyle(const std::string &style) {
|
||||
auto css_file = style.empty() ? Config::findConfigPath({"style.css"}) : style;
|
||||
if (!css_file) {
|
||||
throw std::runtime_error("Missing required resource files");
|
||||
}
|
||||
spdlog::info("Resources files: {}, {}", config_file, css_file);
|
||||
return {config_file, css_file};
|
||||
}
|
||||
|
||||
auto waybar::Client::setupConfig(const std::string &config_file) -> void {
|
||||
std::ifstream file(config_file);
|
||||
if (!file.is_open()) {
|
||||
throw std::runtime_error("Can't open config file");
|
||||
}
|
||||
std::string str((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
|
||||
util::JsonParser parser;
|
||||
config_ = parser.parse(str);
|
||||
}
|
||||
spdlog::info("Using CSS file {}", css_file.value());
|
||||
return css_file.value();
|
||||
};
|
||||
|
||||
auto waybar::Client::setupCss(const std::string &css_file) -> void {
|
||||
css_provider_ = Gtk::CssProvider::create();
|
||||
@ -277,14 +197,13 @@ void waybar::Client::bindInterfaces() {
|
||||
int waybar::Client::main(int argc, char *argv[]) {
|
||||
bool show_help = false;
|
||||
bool show_version = false;
|
||||
std::string config;
|
||||
std::string style;
|
||||
std::string bar_id;
|
||||
std::string config_opt;
|
||||
std::string style_opt;
|
||||
std::string log_level;
|
||||
auto cli = clara::detail::Help(show_help) |
|
||||
clara::detail::Opt(show_version)["-v"]["--version"]("Show version") |
|
||||
clara::detail::Opt(config, "config")["-c"]["--config"]("Config path") |
|
||||
clara::detail::Opt(style, "style")["-s"]["--style"]("Style path") |
|
||||
clara::detail::Opt(config_opt, "config")["-c"]["--config"]("Config path") |
|
||||
clara::detail::Opt(style_opt, "style")["-s"]["--style"]("Style path") |
|
||||
clara::detail::Opt(
|
||||
log_level,
|
||||
"trace|debug|info|warning|error|critical|off")["-l"]["--log-level"]("Log level") |
|
||||
@ -315,8 +234,8 @@ int waybar::Client::main(int argc, char *argv[]) {
|
||||
throw std::runtime_error("Bar need to run under Wayland");
|
||||
}
|
||||
wl_display = gdk_wayland_display_get_wl_display(gdk_display->gobj());
|
||||
auto [config_file, css_file] = getConfigs(config, style);
|
||||
setupConfig(config_file);
|
||||
config.load(config_opt);
|
||||
auto css_file = getStyle(style_opt);
|
||||
setupCss(css_file);
|
||||
bindInterfaces();
|
||||
gtk_app->hold();
|
||||
|
153
src/config.cpp
Normal file
153
src/config.cpp
Normal file
@ -0,0 +1,153 @@
|
||||
#include "config.hpp"
|
||||
|
||||
#include <fmt/ostream.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <unistd.h>
|
||||
#include <wordexp.h>
|
||||
|
||||
#include <fstream>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "util/json.hpp"
|
||||
|
||||
namespace waybar {
|
||||
|
||||
const std::vector<std::string> Config::CONFIG_DIRS = {
|
||||
"$XDG_CONFIG_HOME/waybar/",
|
||||
"$HOME/.config/waybar/",
|
||||
"$HOME/waybar/",
|
||||
"/etc/xdg/waybar/",
|
||||
SYSCONFDIR "/xdg/waybar/",
|
||||
"./resources/",
|
||||
};
|
||||
|
||||
std::optional<std::string> tryExpandPath(const std::string &path) {
|
||||
wordexp_t p;
|
||||
if (wordexp(path.c_str(), &p, 0) == 0) {
|
||||
if (access(*p.we_wordv, F_OK) == 0) {
|
||||
std::string result = *p.we_wordv;
|
||||
wordfree(&p);
|
||||
return result;
|
||||
}
|
||||
wordfree(&p);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<std::string> Config::findConfigPath(const std::vector<std::string> &names,
|
||||
const std::vector<std::string> &dirs) {
|
||||
std::vector<std::string> paths;
|
||||
for (const auto &dir : dirs) {
|
||||
for (const auto &name : names) {
|
||||
if (auto res = tryExpandPath(dir + name); res) {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void Config::setupConfig(Json::Value &dst, const std::string &config_file, int depth) {
|
||||
if (depth > 100) {
|
||||
throw std::runtime_error("Aborting due to likely recursive include in config files");
|
||||
}
|
||||
std::ifstream file(config_file);
|
||||
if (!file.is_open()) {
|
||||
throw std::runtime_error("Can't open config file");
|
||||
}
|
||||
std::string str((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
|
||||
util::JsonParser parser;
|
||||
Json::Value tmp_config = parser.parse(str);
|
||||
if (tmp_config.isArray()) {
|
||||
for (auto &config_part : tmp_config) {
|
||||
resolveConfigIncludes(config_part, depth);
|
||||
}
|
||||
} else {
|
||||
resolveConfigIncludes(tmp_config, depth);
|
||||
}
|
||||
mergeConfig(dst, tmp_config);
|
||||
}
|
||||
|
||||
void Config::resolveConfigIncludes(Json::Value &config, int depth) {
|
||||
Json::Value includes = config["include"];
|
||||
if (includes.isArray()) {
|
||||
for (const auto &include : includes) {
|
||||
spdlog::info("Including resource file: {}", include.asString());
|
||||
setupConfig(config, tryExpandPath(include.asString()).value_or(""), ++depth);
|
||||
}
|
||||
} else if (includes.isString()) {
|
||||
spdlog::info("Including resource file: {}", includes.asString());
|
||||
setupConfig(config, tryExpandPath(includes.asString()).value_or(""), ++depth);
|
||||
}
|
||||
}
|
||||
|
||||
void Config::mergeConfig(Json::Value &a_config_, Json::Value &b_config_) {
|
||||
if (!a_config_) {
|
||||
// For the first config
|
||||
a_config_ = b_config_;
|
||||
} else if (a_config_.isObject() && b_config_.isObject()) {
|
||||
for (const auto &key : b_config_.getMemberNames()) {
|
||||
// [] creates key with default value. Use `get` to avoid that.
|
||||
if (a_config_.get(key, Json::Value::nullSingleton()).isObject() &&
|
||||
b_config_[key].isObject()) {
|
||||
mergeConfig(a_config_[key], b_config_[key]);
|
||||
} else if (!a_config_.isMember(key)) {
|
||||
// do not allow overriding value set by top or previously included config
|
||||
a_config_[key] = b_config_[key];
|
||||
} else {
|
||||
spdlog::trace("Option {} is already set; ignoring value {}", key, b_config_[key]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
spdlog::error("Cannot merge config, conflicting or invalid JSON types");
|
||||
}
|
||||
}
|
||||
bool isValidOutput(const Json::Value &config, const std::string &name,
|
||||
const std::string &identifier) {
|
||||
if (config["output"].isArray()) {
|
||||
for (auto const &output_conf : config["output"]) {
|
||||
if (output_conf.isString() &&
|
||||
(output_conf.asString() == name || output_conf.asString() == identifier)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} else if (config["output"].isString()) {
|
||||
auto config_output = config["output"].asString();
|
||||
if (!config_output.empty()) {
|
||||
if (config_output.substr(0, 1) == "!") {
|
||||
return config_output.substr(1) != name && config_output.substr(1) != identifier;
|
||||
}
|
||||
return config_output == name || config_output == identifier;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Config::load(const std::string &config) {
|
||||
auto file = config.empty() ? findConfigPath({"config", "config.jsonc"}) : config;
|
||||
if (!file) {
|
||||
throw std::runtime_error("Missing required resource files");
|
||||
}
|
||||
config_file_ = file.value();
|
||||
spdlog::info("Using configuration file {}", config_file_);
|
||||
setupConfig(config_, config_file_, 0);
|
||||
}
|
||||
|
||||
std::vector<Json::Value> Config::getOutputConfigs(const std::string &name,
|
||||
const std::string &identifier) {
|
||||
std::vector<Json::Value> configs;
|
||||
if (config_.isArray()) {
|
||||
for (auto const &config : config_) {
|
||||
if (config.isObject() && isValidOutput(config, name, identifier)) {
|
||||
configs.push_back(config);
|
||||
}
|
||||
}
|
||||
} else if (isValidOutput(config_, name, identifier)) {
|
||||
configs.push_back(config_);
|
||||
}
|
||||
return configs;
|
||||
}
|
||||
|
||||
} // namespace waybar
|
@ -30,6 +30,11 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
|
||||
if (ref == "wlr/taskbar") {
|
||||
return new waybar::modules::wlr::Taskbar(id, bar_, config_[name]);
|
||||
}
|
||||
#ifdef USE_EXPERIMENTAL
|
||||
if (ref == "wlr/workspaces") {
|
||||
return new waybar::modules::wlr::WorkspaceManager(id, bar_, config_[name]);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
#ifdef HAVE_RIVER
|
||||
if (ref == "river/tags") {
|
||||
@ -70,6 +75,11 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
|
||||
return new waybar::modules::Backlight(id, config_[name]);
|
||||
}
|
||||
#endif
|
||||
#ifdef HAVE_LIBEVDEV
|
||||
if (ref == "keyboard-state") {
|
||||
return new waybar::modules::KeyboardState(id, bar_, config_[name]);
|
||||
}
|
||||
#endif
|
||||
#ifdef HAVE_LIBPULSE
|
||||
if (ref == "pulseaudio") {
|
||||
return new waybar::modules::Pulseaudio(id, config_[name]);
|
||||
@ -84,6 +94,11 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
|
||||
if (ref == "sndio") {
|
||||
return new waybar::modules::Sndio(id, config_[name]);
|
||||
}
|
||||
#endif
|
||||
#ifdef HAVE_GIO_UNIX
|
||||
if (ref == "inhibitor") {
|
||||
return new waybar::modules::Inhibitor(id, bar_, config_[name]);
|
||||
}
|
||||
#endif
|
||||
if (ref == "temperature") {
|
||||
return new waybar::modules::Temperature(id, config_[name]);
|
||||
|
19
src/group.cpp
Normal file
19
src/group.cpp
Normal file
@ -0,0 +1,19 @@
|
||||
#include "group.hpp"
|
||||
#include <fmt/format.h>
|
||||
#include <util/command.hpp>
|
||||
|
||||
namespace waybar {
|
||||
|
||||
Group::Group(const std::string& name, const Bar& bar, const Json::Value& config)
|
||||
: AModule(config, name, "", false, false),
|
||||
box{bar.vertical ? Gtk::ORIENTATION_HORIZONTAL : Gtk::ORIENTATION_VERTICAL, 0}
|
||||
{
|
||||
}
|
||||
|
||||
auto Group::update() -> void {
|
||||
// noop
|
||||
}
|
||||
|
||||
Group::operator Gtk::Widget&() { return box; }
|
||||
|
||||
} // namespace waybar
|
@ -173,7 +173,7 @@ auto waybar::modules::Backlight::update() -> void {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto percent = best->get_max() == 0 ? 100 : best->get_actual() * 100 / best->get_max();
|
||||
const uint8_t percent = best->get_max() == 0 ? 100 : round(best->get_actual() * 100.0f / best->get_max());
|
||||
label_.set_markup(fmt::format(
|
||||
format_, fmt::arg("percent", std::to_string(percent)), fmt::arg("icon", getIcon(percent))));
|
||||
getState(percent);
|
||||
|
@ -161,15 +161,38 @@ const std::tuple<uint8_t, float, std::string, float> waybar::modules::Battery::g
|
||||
uint32_t energy_now;
|
||||
uint32_t energy_full_design;
|
||||
std::string _status;
|
||||
std::ifstream(bat / "status") >> _status;
|
||||
auto rate_path = fs::exists(bat / "current_now") ? "current_now" : "power_now";
|
||||
std::ifstream(bat / rate_path) >> power_now;
|
||||
auto now_path = fs::exists(bat / "charge_now") ? "charge_now" : "energy_now";
|
||||
std::ifstream(bat / now_path) >> energy_now;
|
||||
auto full_path = fs::exists(bat / "charge_full") ? "charge_full" : "energy_full";
|
||||
std::ifstream(bat / full_path) >> energy_full;
|
||||
auto full_design_path = fs::exists(bat / "charge_full_design") ? "charge_full_design" : "energy_full_design";
|
||||
std::ifstream(bat / full_design_path) >> energy_full_design;
|
||||
std::getline(std::ifstream(bat / "status"), _status);
|
||||
|
||||
// Some battery will report current and charge in μA/μAh.
|
||||
// Scale these by the voltage to get μW/μWh.
|
||||
if (fs::exists(bat / "current_now")) {
|
||||
uint32_t voltage_now;
|
||||
uint32_t current_now;
|
||||
uint32_t charge_now;
|
||||
uint32_t charge_full;
|
||||
uint32_t charge_full_design;
|
||||
std::ifstream(bat / "voltage_now") >> voltage_now;
|
||||
std::ifstream(bat / "current_now") >> current_now;
|
||||
std::ifstream(bat / "charge_full") >> charge_full;
|
||||
std::ifstream(bat / "charge_full_design") >> charge_full_design;
|
||||
if (fs::exists(bat / "charge_now"))
|
||||
std::ifstream(bat / "charge_now") >> charge_now;
|
||||
else {
|
||||
// charge_now is missing on some systems, estimate using capacity.
|
||||
uint32_t capacity;
|
||||
std::ifstream(bat / "capacity") >> capacity;
|
||||
charge_now = (capacity * charge_full) / 100;
|
||||
}
|
||||
power_now = ((uint64_t)current_now * (uint64_t)voltage_now) / 1000000;
|
||||
energy_now = ((uint64_t)charge_now * (uint64_t)voltage_now) / 1000000;
|
||||
energy_full = ((uint64_t)charge_full * (uint64_t)voltage_now) / 1000000;
|
||||
energy_full_design = ((uint64_t)charge_full_design * (uint64_t)voltage_now) / 1000000;
|
||||
} else {
|
||||
std::ifstream(bat / "power_now") >> power_now;
|
||||
std::ifstream(bat / "energy_now") >> energy_now;
|
||||
std::ifstream(bat / "energy_full") >> energy_full;
|
||||
std::ifstream(bat / "energy_full_design") >> energy_full_design;
|
||||
}
|
||||
|
||||
// Show the "smallest" status among all batteries
|
||||
if (status_gt(status, _status)) {
|
||||
@ -216,7 +239,7 @@ const std::tuple<uint8_t, float, std::string, float> waybar::modules::Battery::g
|
||||
capacity = 100.f;
|
||||
}
|
||||
uint8_t cap = round(capacity);
|
||||
if (cap == 100) {
|
||||
if (cap == 100 && status == "Charging") {
|
||||
// If we've reached 100% just mark as full as some batteries can stay
|
||||
// stuck reporting they're still charging but not yet done
|
||||
status = "Full";
|
||||
|
@ -16,7 +16,7 @@ auto waybar::modules::Bluetooth::update() -> void {
|
||||
if (tooltipEnabled()) {
|
||||
if (config_["tooltip-format"].isString()) {
|
||||
auto tooltip_format = config_["tooltip-format"].asString();
|
||||
auto tooltip_text = fmt::format(tooltip_format, status);
|
||||
auto tooltip_text = fmt::format(tooltip_format, status, fmt::arg("status", status));
|
||||
label_.set_tooltip_text(tooltip_text);
|
||||
} else {
|
||||
label_.set_tooltip_text(status);
|
||||
|
@ -1,24 +1,73 @@
|
||||
#include "modules/clock.hpp"
|
||||
|
||||
#include <time.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
#if FMT_VERSION < 60000
|
||||
#include <fmt/time.h>
|
||||
#else
|
||||
#include <fmt/chrono.h>
|
||||
#endif
|
||||
|
||||
#include <ctime>
|
||||
#include <sstream>
|
||||
#include <type_traits>
|
||||
|
||||
#include "util/ustring_clen.hpp"
|
||||
#include "util/waybar_time.hpp"
|
||||
#ifdef HAVE_LANGINFO_1STDAY
|
||||
#include <langinfo.h>
|
||||
#include <locale.h>
|
||||
#endif
|
||||
|
||||
using waybar::modules::waybar_time;
|
||||
using waybar::waybar_time;
|
||||
|
||||
waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
|
||||
: ALabel(config, "clock", id, "{:%H:%M}", 60, false, false, true), fixed_time_zone_(false) {
|
||||
if (config_["timezone"].isString()) {
|
||||
spdlog::warn("As using a timezone, some format args may be missing as the date library havn't got a release since 2018.");
|
||||
time_zone_ = date::locate_zone(config_["timezone"].asString());
|
||||
fixed_time_zone_ = true;
|
||||
: ALabel(config, "clock", id, "{:%H:%M}", 60, false, false, true),
|
||||
current_time_zone_idx_(0),
|
||||
is_calendar_in_tooltip_(false),
|
||||
is_timezoned_list_in_tooltip_(false)
|
||||
{
|
||||
if (config_["timezones"].isArray() && !config_["timezones"].empty()) {
|
||||
for (const auto& zone_name: config_["timezones"]) {
|
||||
if (!zone_name.isString() || zone_name.asString().empty()) {
|
||||
time_zones_.push_back(nullptr);
|
||||
continue;
|
||||
}
|
||||
time_zones_.push_back(
|
||||
date::locate_zone(
|
||||
zone_name.asString()
|
||||
)
|
||||
);
|
||||
}
|
||||
} else if (config_["timezone"].isString() && !config_["timezone"].asString().empty()) {
|
||||
time_zones_.push_back(
|
||||
date::locate_zone(
|
||||
config_["timezone"].asString()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// If all timezones are parsed and no one is good, add nullptr to the timezones vector, to mark that local time should be shown.
|
||||
if (!time_zones_.size()) {
|
||||
time_zones_.push_back(nullptr);
|
||||
}
|
||||
|
||||
if (!is_timezone_fixed()) {
|
||||
spdlog::warn("As using a timezone, some format args may be missing as the date library haven't got a release since 2018.");
|
||||
}
|
||||
|
||||
// Check if a particular placeholder is present in the tooltip format, to know what to calculate on update.
|
||||
if (config_["tooltip-format"].isString()) {
|
||||
std::string trimmed_format = config_["tooltip-format"].asString();
|
||||
trimmed_format.erase(std::remove_if(trimmed_format.begin(),
|
||||
trimmed_format.end(),
|
||||
[](unsigned char x){return std::isspace(x);}),
|
||||
trimmed_format.end());
|
||||
if (trimmed_format.find("{" + kCalendarPlaceholder + "}") != std::string::npos) {
|
||||
is_calendar_in_tooltip_ = true;
|
||||
}
|
||||
if (trimmed_format.find("{" + KTimezonedTimeListPlaceholder + "}") != std::string::npos) {
|
||||
is_timezoned_list_in_tooltip_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (config_["locale"].isString()) {
|
||||
@ -36,38 +85,46 @@ waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
|
||||
};
|
||||
}
|
||||
|
||||
const date::time_zone* waybar::modules::Clock::current_timezone() {
|
||||
return time_zones_[current_time_zone_idx_] ? time_zones_[current_time_zone_idx_] : date::current_zone();
|
||||
}
|
||||
|
||||
bool waybar::modules::Clock::is_timezone_fixed() {
|
||||
return time_zones_[current_time_zone_idx_] != nullptr;
|
||||
}
|
||||
|
||||
auto waybar::modules::Clock::update() -> void {
|
||||
if (!fixed_time_zone_) {
|
||||
// Time zone can change. Be sure to pick that.
|
||||
time_zone_ = date::current_zone();
|
||||
}
|
||||
|
||||
auto now = std::chrono::system_clock::now();
|
||||
auto time_zone = current_timezone();
|
||||
auto now = std::chrono::system_clock::now();
|
||||
waybar_time wtime = {locale_,
|
||||
date::make_zoned(time_zone_, date::floor<std::chrono::seconds>(now))};
|
||||
|
||||
std::string text;
|
||||
if (!fixed_time_zone_) {
|
||||
date::make_zoned(time_zone, date::floor<std::chrono::seconds>(now))};
|
||||
std::string text = "";
|
||||
if (!is_timezone_fixed()) {
|
||||
// As date dep is not fully compatible, prefer fmt
|
||||
tzset();
|
||||
auto localtime = fmt::localtime(std::chrono::system_clock::to_time_t(now));
|
||||
text = fmt::format(format_, localtime);
|
||||
label_.set_markup(text);
|
||||
text = fmt::format(locale_, format_, localtime);
|
||||
} else {
|
||||
text = fmt::format(format_, wtime);
|
||||
label_.set_markup(text);
|
||||
}
|
||||
label_.set_markup(text);
|
||||
|
||||
if (tooltipEnabled()) {
|
||||
if (config_["tooltip-format"].isString()) {
|
||||
const auto calendar = calendar_text(wtime);
|
||||
auto tooltip_format = config_["tooltip-format"].asString();
|
||||
auto tooltip_text = fmt::format(tooltip_format, wtime, fmt::arg("calendar", calendar));
|
||||
label_.set_tooltip_markup(tooltip_text);
|
||||
} else {
|
||||
std::string calendar_lines = "";
|
||||
std::string timezoned_time_lines = "";
|
||||
if (is_calendar_in_tooltip_) {
|
||||
calendar_lines = calendar_text(wtime);
|
||||
}
|
||||
if (is_timezoned_list_in_tooltip_) {
|
||||
timezoned_time_lines = timezones_text(&now);
|
||||
}
|
||||
auto tooltip_format = config_["tooltip-format"].asString();
|
||||
text = fmt::format(tooltip_format, wtime, fmt::arg(kCalendarPlaceholder.c_str(), calendar_lines), fmt::arg(KTimezonedTimeListPlaceholder.c_str(), timezoned_time_lines));
|
||||
label_.set_tooltip_markup(text);
|
||||
}
|
||||
}
|
||||
|
||||
// Call parent update
|
||||
ALabel::update();
|
||||
}
|
||||
@ -82,22 +139,16 @@ bool waybar::modules::Clock::handleScroll(GdkEventScroll *e) {
|
||||
if (dir != SCROLL_DIR::UP && dir != SCROLL_DIR::DOWN) {
|
||||
return true;
|
||||
}
|
||||
if (!config_["timezones"].isArray() || config_["timezones"].empty()) {
|
||||
if (time_zones_.size() == 1) {
|
||||
return true;
|
||||
}
|
||||
auto nr_zones = config_["timezones"].size();
|
||||
|
||||
auto nr_zones = time_zones_.size();
|
||||
if (dir == SCROLL_DIR::UP) {
|
||||
size_t new_idx = time_zone_idx_ + 1;
|
||||
time_zone_idx_ = new_idx == nr_zones ? 0 : new_idx;
|
||||
size_t new_idx = current_time_zone_idx_ + 1;
|
||||
current_time_zone_idx_ = new_idx == nr_zones ? 0 : new_idx;
|
||||
} else {
|
||||
time_zone_idx_ = time_zone_idx_ == 0 ? nr_zones - 1 : time_zone_idx_ - 1;
|
||||
}
|
||||
auto zone_name = config_["timezones"][time_zone_idx_];
|
||||
if (!zone_name.isString() || zone_name.empty()) {
|
||||
fixed_time_zone_ = false;
|
||||
} else {
|
||||
time_zone_ = date::locate_zone(zone_name.asString());
|
||||
fixed_time_zone_ = true;
|
||||
current_time_zone_idx_ = current_time_zone_idx_ == 0 ? nr_zones - 1 : current_time_zone_idx_ - 1;
|
||||
}
|
||||
|
||||
update();
|
||||
@ -168,6 +219,26 @@ auto waybar::modules::Clock::weekdays_header(const date::weekday& first_dow, std
|
||||
os << "\n";
|
||||
}
|
||||
|
||||
auto waybar::modules::Clock::timezones_text(std::chrono::_V2::system_clock::time_point *now) -> std::string {
|
||||
if (time_zones_.size() == 1) {
|
||||
return "";
|
||||
}
|
||||
std::stringstream os;
|
||||
waybar_time wtime;
|
||||
for (size_t time_zone_idx = 0; time_zone_idx < time_zones_.size(); ++time_zone_idx) {
|
||||
if (static_cast<int>(time_zone_idx) == current_time_zone_idx_) {
|
||||
continue;
|
||||
}
|
||||
const date::time_zone* timezone = time_zones_[time_zone_idx];
|
||||
if (!timezone) {
|
||||
timezone = date::current_zone();
|
||||
}
|
||||
wtime = {locale_, date::make_zoned(timezone, date::floor<std::chrono::seconds>(*now))};
|
||||
os << fmt::format(format_, wtime) << "\n";
|
||||
}
|
||||
return os.str();
|
||||
}
|
||||
|
||||
#ifdef HAVE_LANGINFO_1STDAY
|
||||
template <auto fn>
|
||||
using deleter_from_fn = std::integral_constant<decltype(fn), fn>;
|
||||
@ -191,11 +262,3 @@ auto waybar::modules::Clock::first_day_of_week() -> date::weekday {
|
||||
#endif
|
||||
return date::Sunday;
|
||||
}
|
||||
|
||||
template <>
|
||||
struct fmt::formatter<waybar_time> : fmt::formatter<std::tm> {
|
||||
template <typename FormatContext>
|
||||
auto format(const waybar_time& t, FormatContext& ctx) {
|
||||
return format_to(ctx.out(), "{}", date::format(t.locale, fmt::to_string(tm_format), t.ztime));
|
||||
}
|
||||
};
|
||||
|
@ -1,5 +1,14 @@
|
||||
#include "modules/cpu.hpp"
|
||||
|
||||
// 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
|
||||
|
||||
waybar::modules::Cpu::Cpu(const std::string& id, const Json::Value& config)
|
||||
: ALabel(config, "cpu", id, "{usage}%", 10) {
|
||||
thread_ = [this] {
|
||||
@ -17,7 +26,8 @@ auto waybar::modules::Cpu::update() -> void {
|
||||
label_.set_tooltip_text(tooltip);
|
||||
}
|
||||
auto format = format_;
|
||||
auto state = getState(cpu_usage);
|
||||
auto total_usage = cpu_usage.empty() ? 0 : cpu_usage[0];
|
||||
auto state = getState(total_usage);
|
||||
if (!state.empty() && config_["format-" + state].isString()) {
|
||||
format = config_["format-" + state].asString();
|
||||
}
|
||||
@ -26,12 +36,23 @@ auto waybar::modules::Cpu::update() -> void {
|
||||
event_box_.hide();
|
||||
} else {
|
||||
event_box_.show();
|
||||
label_.set_markup(fmt::format(format,
|
||||
fmt::arg("load", cpu_load),
|
||||
fmt::arg("usage", cpu_usage),
|
||||
fmt::arg("max_frequency", max_frequency),
|
||||
fmt::arg("min_frequency", min_frequency),
|
||||
fmt::arg("avg_frequency", avg_frequency)));
|
||||
auto icons = std::vector<std::string>{state};
|
||||
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||
store.push_back(fmt::arg("load", cpu_load));
|
||||
store.push_back(fmt::arg("load", cpu_load));
|
||||
store.push_back(fmt::arg("usage", total_usage));
|
||||
store.push_back(fmt::arg("icon", getIcon(total_usage, icons)));
|
||||
store.push_back(fmt::arg("max_frequency", max_frequency));
|
||||
store.push_back(fmt::arg("min_frequency", min_frequency));
|
||||
store.push_back(fmt::arg("avg_frequency", avg_frequency));
|
||||
for (size_t i = 1; i < cpu_usage.size(); ++i) {
|
||||
auto core_i = i - 1;
|
||||
auto core_format = fmt::format("usage{}", core_i);
|
||||
store.push_back(fmt::arg(core_format.c_str(), cpu_usage[i]));
|
||||
auto icon_format = fmt::format("icon{}", core_i);
|
||||
store.push_back(fmt::arg(icon_format.c_str(), getIcon(cpu_usage[i], icons)));
|
||||
}
|
||||
label_.set_markup(fmt::vformat(format, store));
|
||||
}
|
||||
|
||||
// Call parent update
|
||||
@ -41,19 +62,19 @@ auto waybar::modules::Cpu::update() -> void {
|
||||
double waybar::modules::Cpu::getCpuLoad() {
|
||||
double load[1];
|
||||
if (getloadavg(load, 1) != -1) {
|
||||
return load[0];
|
||||
return std::ceil(load[0] * 100.0) / 100.0;
|
||||
}
|
||||
throw std::runtime_error("Can't get Cpu load");
|
||||
}
|
||||
|
||||
std::tuple<uint16_t, std::string> waybar::modules::Cpu::getCpuUsage() {
|
||||
std::tuple<std::vector<uint16_t>, std::string> waybar::modules::Cpu::getCpuUsage() {
|
||||
if (prev_times_.empty()) {
|
||||
prev_times_ = parseCpuinfo();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
}
|
||||
std::vector<std::tuple<size_t, size_t>> curr_times = parseCpuinfo();
|
||||
std::string tooltip;
|
||||
uint16_t usage = 0;
|
||||
std::vector<uint16_t> usage;
|
||||
for (size_t i = 0; i < curr_times.size(); ++i) {
|
||||
auto [curr_idle, curr_total] = curr_times[i];
|
||||
auto [prev_idle, prev_total] = prev_times_[i];
|
||||
@ -61,11 +82,11 @@ std::tuple<uint16_t, std::string> waybar::modules::Cpu::getCpuUsage() {
|
||||
const float delta_total = curr_total - prev_total;
|
||||
uint16_t tmp = 100 * (1 - delta_idle / delta_total);
|
||||
if (i == 0) {
|
||||
usage = tmp;
|
||||
tooltip = fmt::format("Total: {}%", tmp);
|
||||
} else {
|
||||
tooltip = tooltip + fmt::format("\nCore{}: {}%", i - 1, tmp);
|
||||
}
|
||||
usage.push_back(tmp);
|
||||
}
|
||||
prev_times_ = curr_times;
|
||||
return {usage, tooltip};
|
||||
|
@ -1,3 +1,4 @@
|
||||
#include <filesystem>
|
||||
#include "modules/cpu.hpp"
|
||||
|
||||
std::vector<std::tuple<size_t, size_t>> waybar::modules::Cpu::parseCpuinfo() {
|
||||
@ -46,5 +47,31 @@ std::vector<float> waybar::modules::Cpu::parseCpuFrequencies() {
|
||||
frequencies.push_back(frequency);
|
||||
}
|
||||
info.close();
|
||||
|
||||
if (frequencies.size() <= 0) {
|
||||
std::string cpufreq_dir = "/sys/devices/system/cpu/cpufreq";
|
||||
if (std::filesystem::exists(cpufreq_dir)) {
|
||||
std::vector<std::string> frequency_files = {
|
||||
"/cpuinfo_min_freq",
|
||||
"/cpuinfo_max_freq"
|
||||
};
|
||||
for (auto& p: std::filesystem::directory_iterator(cpufreq_dir)) {
|
||||
for (auto freq_file: frequency_files) {
|
||||
std::string freq_file_path = p.path().string() + freq_file;
|
||||
if (std::filesystem::exists(freq_file_path)) {
|
||||
std::string freq_value;
|
||||
std::ifstream freq(freq_file_path);
|
||||
if (freq.is_open()) {
|
||||
getline(freq, freq_value);
|
||||
float frequency = std::strtol(freq_value.c_str(), nullptr, 10);
|
||||
frequencies.push_back(frequency / 1000);
|
||||
freq.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return frequencies;
|
||||
}
|
||||
|
@ -45,9 +45,9 @@ auto waybar::modules::Disk::update() -> void {
|
||||
}
|
||||
|
||||
auto free = pow_format(stats.f_bavail * stats.f_frsize, "B", true);
|
||||
auto used = pow_format((stats.f_blocks - stats.f_bavail) * stats.f_frsize, "B", true);
|
||||
auto used = pow_format((stats.f_blocks - stats.f_bfree) * stats.f_frsize, "B", true);
|
||||
auto total = pow_format(stats.f_blocks * stats.f_frsize, "B", true);
|
||||
auto percentage_used = (stats.f_blocks - stats.f_bavail) * 100 / stats.f_blocks;
|
||||
auto percentage_used = (stats.f_blocks - stats.f_bfree) * 100 / stats.f_blocks;
|
||||
|
||||
auto format = format_;
|
||||
auto state = getState(percentage_used);
|
||||
|
175
src/modules/inhibitor.cpp
Normal file
175
src/modules/inhibitor.cpp
Normal file
@ -0,0 +1,175 @@
|
||||
#include "modules/inhibitor.hpp"
|
||||
|
||||
#include <gio/gio.h>
|
||||
#include <gio/gunixfdlist.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
namespace {
|
||||
|
||||
using DBus = std::unique_ptr<GDBusConnection, void(*)(GDBusConnection*)>;
|
||||
|
||||
auto dbus() -> DBus {
|
||||
GError *error = nullptr;
|
||||
GDBusConnection* connection =
|
||||
g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &error);
|
||||
|
||||
if (error) {
|
||||
spdlog::error("g_bus_get_sync() failed: {}", error->message);
|
||||
g_error_free(error);
|
||||
connection = nullptr;
|
||||
}
|
||||
|
||||
auto destructor = [](GDBusConnection* connection) {
|
||||
GError *error = nullptr;
|
||||
g_dbus_connection_close_sync(connection, nullptr, &error);
|
||||
if (error) {
|
||||
spdlog::error(
|
||||
"g_bus_connection_close_sync failed(): {}",
|
||||
error->message);
|
||||
g_error_free(error);
|
||||
}
|
||||
};
|
||||
|
||||
return DBus{connection, destructor};
|
||||
}
|
||||
|
||||
auto getLocks(const DBus& bus, const std::string& inhibitors) -> int {
|
||||
GError *error = nullptr;
|
||||
GUnixFDList* fd_list;
|
||||
int handle;
|
||||
|
||||
auto reply = g_dbus_connection_call_with_unix_fd_list_sync(bus.get(),
|
||||
"org.freedesktop.login1",
|
||||
"/org/freedesktop/login1",
|
||||
"org.freedesktop.login1.Manager",
|
||||
"Inhibit",
|
||||
g_variant_new(
|
||||
"(ssss)",
|
||||
inhibitors.c_str(),
|
||||
"waybar",
|
||||
"Asked by user",
|
||||
"block"),
|
||||
G_VARIANT_TYPE("(h)"),
|
||||
G_DBUS_CALL_FLAGS_NONE,
|
||||
-1,
|
||||
nullptr,
|
||||
&fd_list,
|
||||
nullptr,
|
||||
&error);
|
||||
if (error) {
|
||||
spdlog::error(
|
||||
"g_dbus_connection_call_with_unix_fd_list_sync() failed: {}",
|
||||
error->message);
|
||||
g_error_free(error);
|
||||
handle = -1;
|
||||
} else {
|
||||
gint index;
|
||||
g_variant_get(reply, "(h)", &index);
|
||||
g_variant_unref(reply);
|
||||
handle = g_unix_fd_list_get(fd_list, index, nullptr);
|
||||
g_object_unref(fd_list);
|
||||
}
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
auto checkInhibitor(const std::string& inhibitor) -> const std::string& {
|
||||
static const auto inhibitors = std::array{
|
||||
"idle",
|
||||
"shutdown",
|
||||
"sleep",
|
||||
"handle-power-key",
|
||||
"handle-suspend-key",
|
||||
"handle-hibernate-key",
|
||||
"handle-lid-switch"
|
||||
};
|
||||
|
||||
if (std::find(inhibitors.begin(), inhibitors.end(), inhibitor)
|
||||
== inhibitors.end()) {
|
||||
throw std::runtime_error("invalid logind inhibitor " + inhibitor);
|
||||
}
|
||||
|
||||
return inhibitor;
|
||||
}
|
||||
|
||||
auto getInhibitors(const Json::Value& config) -> std::string {
|
||||
std::string inhibitors = "idle";
|
||||
|
||||
if (config["what"].empty()) {
|
||||
return inhibitors;
|
||||
}
|
||||
|
||||
if (config["what"].isString()) {
|
||||
return checkInhibitor(config["what"].asString());
|
||||
}
|
||||
|
||||
if (config["what"].isArray()) {
|
||||
inhibitors = checkInhibitor(config["what"][0].asString());
|
||||
for (decltype(config["what"].size()) i = 1; i < config["what"].size(); ++i) {
|
||||
inhibitors += ":" + checkInhibitor(config["what"][i].asString());
|
||||
}
|
||||
return inhibitors;
|
||||
}
|
||||
|
||||
return inhibitors;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace waybar::modules {
|
||||
|
||||
Inhibitor::Inhibitor(const std::string& id, const Bar& bar,
|
||||
const Json::Value& config)
|
||||
: ALabel(config, "inhibitor", id, "{status}", true),
|
||||
dbus_(::dbus()),
|
||||
inhibitors_(::getInhibitors(config)) {
|
||||
event_box_.add_events(Gdk::BUTTON_PRESS_MASK);
|
||||
event_box_.signal_button_press_event().connect(
|
||||
sigc::mem_fun(*this, &Inhibitor::handleToggle));
|
||||
dp.emit();
|
||||
}
|
||||
|
||||
Inhibitor::~Inhibitor() {
|
||||
if (handle_ != -1) {
|
||||
::close(handle_);
|
||||
}
|
||||
}
|
||||
|
||||
auto Inhibitor::activated() -> bool {
|
||||
return handle_ != -1;
|
||||
}
|
||||
|
||||
auto Inhibitor::update() -> void {
|
||||
std::string status_text = activated() ? "activated" : "deactivated";
|
||||
|
||||
label_.get_style_context()->remove_class(
|
||||
activated() ? "deactivated" : "activated");
|
||||
label_.set_markup(
|
||||
fmt::format(format_, fmt::arg("status", status_text),
|
||||
fmt::arg("icon", getIcon(0, status_text))));
|
||||
label_.get_style_context()->add_class(status_text);
|
||||
|
||||
if (tooltipEnabled()) {
|
||||
label_.set_tooltip_text(status_text);
|
||||
}
|
||||
|
||||
return ALabel::update();
|
||||
}
|
||||
|
||||
auto Inhibitor::handleToggle(GdkEventButton* const& e) -> bool {
|
||||
if (e->button == 1) {
|
||||
if (activated()) {
|
||||
::close(handle_);
|
||||
handle_ = -1;
|
||||
} else {
|
||||
handle_ = ::getLocks(dbus_, inhibitors_);
|
||||
if (handle_ == -1) {
|
||||
spdlog::error("cannot get inhibitor locks");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ALabel::handleToggle(e);
|
||||
}
|
||||
|
||||
} // waybar::modules
|
205
src/modules/keyboard_state.cpp
Normal file
205
src/modules/keyboard_state.cpp
Normal file
@ -0,0 +1,205 @@
|
||||
#include "modules/keyboard_state.hpp"
|
||||
#include <errno.h>
|
||||
#include <filesystem>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <string.h>
|
||||
|
||||
extern "C" {
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
}
|
||||
|
||||
class errno_error : public std::runtime_error {
|
||||
public:
|
||||
int code;
|
||||
errno_error(int code, const std::string& msg)
|
||||
: std::runtime_error(getErrorMsg(code, msg.c_str())),
|
||||
code(code) {}
|
||||
errno_error(int code, const char* msg)
|
||||
: std::runtime_error(getErrorMsg(code, msg)),
|
||||
code(code) {}
|
||||
private:
|
||||
static auto getErrorMsg(int err, const char* msg) -> std::string {
|
||||
std::string error_msg{msg};
|
||||
error_msg += ": ";
|
||||
|
||||
#if (__GLIBC__ >= 2) && (__GLIBC_MINOR__ >= 32)
|
||||
// strerrorname_np gets the error code's name; it's nice to have, but it's a recent GNU extension
|
||||
const auto errno_name = strerrorname_np(err);
|
||||
error_msg += errno_name;
|
||||
error_msg += " ";
|
||||
#endif
|
||||
|
||||
const auto errno_str = strerror(err);
|
||||
error_msg += errno_str;
|
||||
|
||||
return error_msg;
|
||||
}
|
||||
};
|
||||
|
||||
auto openFile(const std::string& path, int flags) -> int {
|
||||
int fd = open(path.c_str(), flags);
|
||||
if (fd < 0) {
|
||||
if (errno == EACCES) {
|
||||
throw errno_error(errno, "Can't open " + path + " (are you in the input group?)");
|
||||
} else {
|
||||
throw errno_error(errno, "Can't open " + path);
|
||||
}
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
|
||||
auto closeFile(int fd) -> void {
|
||||
int res = close(fd);
|
||||
if (res < 0) {
|
||||
throw errno_error(errno, "Can't close file");
|
||||
}
|
||||
}
|
||||
|
||||
auto openDevice(int fd) -> libevdev* {
|
||||
libevdev* dev;
|
||||
int err = libevdev_new_from_fd(fd, &dev);
|
||||
if (err < 0) {
|
||||
throw errno_error(-err, "Can't create libevdev device");
|
||||
}
|
||||
return dev;
|
||||
}
|
||||
|
||||
auto supportsLockStates(const libevdev* dev) -> bool {
|
||||
return libevdev_has_event_type(dev, EV_LED)
|
||||
&& libevdev_has_event_code(dev, EV_LED, LED_NUML)
|
||||
&& libevdev_has_event_code(dev, EV_LED, LED_CAPSL)
|
||||
&& libevdev_has_event_code(dev, EV_LED, LED_SCROLLL);
|
||||
}
|
||||
|
||||
waybar::modules::KeyboardState::KeyboardState(const std::string& id, const Bar& bar, const Json::Value& config)
|
||||
: AModule(config, "keyboard-state", id, false, !config["disable-scroll"].asBool()),
|
||||
box_(bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0),
|
||||
numlock_label_(""),
|
||||
capslock_label_(""),
|
||||
numlock_format_(config_["format"].isString() ? config_["format"].asString()
|
||||
: config_["format"]["numlock"].isString() ? config_["format"]["numlock"].asString()
|
||||
: "{name} {icon}"),
|
||||
capslock_format_(config_["format"].isString() ? config_["format"].asString()
|
||||
: config_["format"]["capslock"].isString() ? config_["format"]["capslock"].asString()
|
||||
: "{name} {icon}"),
|
||||
scrolllock_format_(config_["format"].isString() ? config_["format"].asString()
|
||||
: config_["format"]["scrolllock"].isString() ? config_["format"]["scrolllock"].asString()
|
||||
: "{name} {icon}"),
|
||||
interval_(std::chrono::seconds(config_["interval"].isUInt() ? config_["interval"].asUInt() : 1)),
|
||||
icon_locked_(config_["format-icons"]["locked"].isString()
|
||||
? config_["format-icons"]["locked"].asString()
|
||||
: "locked"),
|
||||
icon_unlocked_(config_["format-icons"]["unlocked"].isString()
|
||||
? config_["format-icons"]["unlocked"].asString()
|
||||
: "unlocked"),
|
||||
fd_(0),
|
||||
dev_(nullptr) {
|
||||
box_.set_name("keyboard-state");
|
||||
if (config_["numlock"].asBool()) {
|
||||
box_.pack_end(numlock_label_, false, false, 0);
|
||||
}
|
||||
if (config_["capslock"].asBool()) {
|
||||
box_.pack_end(capslock_label_, false, false, 0);
|
||||
}
|
||||
if (config_["scrolllock"].asBool()) {
|
||||
box_.pack_end(scrolllock_label_, false, false, 0);
|
||||
}
|
||||
if (!id.empty()) {
|
||||
box_.get_style_context()->add_class(id);
|
||||
}
|
||||
event_box_.add(box_);
|
||||
|
||||
if (config_["device-path"].isString()) {
|
||||
std::string dev_path = config_["device-path"].asString();
|
||||
fd_ = openFile(dev_path, O_NONBLOCK | O_CLOEXEC | O_RDONLY);
|
||||
dev_ = openDevice(fd_);
|
||||
} else {
|
||||
DIR* dev_dir = opendir("/dev/input");
|
||||
if (dev_dir == nullptr) {
|
||||
throw errno_error(errno, "Failed to open /dev/input");
|
||||
}
|
||||
dirent *ep;
|
||||
while ((ep = readdir(dev_dir))) {
|
||||
if (ep->d_type != DT_CHR) continue;
|
||||
std::string dev_path = std::string("/dev/input/") + ep->d_name;
|
||||
int fd = openFile(dev_path.c_str(), O_NONBLOCK | O_CLOEXEC | O_RDONLY);
|
||||
try {
|
||||
auto dev = openDevice(fd);
|
||||
if (supportsLockStates(dev)) {
|
||||
spdlog::info("Found device {} at '{}'", libevdev_get_name(dev), dev_path);
|
||||
fd_ = fd;
|
||||
dev_ = dev;
|
||||
break;
|
||||
}
|
||||
} catch (const errno_error& e) {
|
||||
// ENOTTY just means the device isn't an evdev device, skip it
|
||||
if (e.code != ENOTTY) {
|
||||
spdlog::warn(e.what());
|
||||
}
|
||||
}
|
||||
closeFile(fd);
|
||||
}
|
||||
if (dev_ == nullptr) {
|
||||
throw errno_error(errno, "Failed to find keyboard device");
|
||||
}
|
||||
}
|
||||
|
||||
thread_ = [this] {
|
||||
dp.emit();
|
||||
thread_.sleep_for(interval_);
|
||||
};
|
||||
}
|
||||
|
||||
waybar::modules::KeyboardState::~KeyboardState() {
|
||||
libevdev_free(dev_);
|
||||
try {
|
||||
closeFile(fd_);
|
||||
} catch (const std::runtime_error& e) {
|
||||
spdlog::warn(e.what());
|
||||
}
|
||||
}
|
||||
|
||||
auto waybar::modules::KeyboardState::update() -> void {
|
||||
int err = LIBEVDEV_READ_STATUS_SUCCESS;
|
||||
while (err == LIBEVDEV_READ_STATUS_SUCCESS) {
|
||||
input_event ev;
|
||||
err = libevdev_next_event(dev_, LIBEVDEV_READ_FLAG_NORMAL, &ev);
|
||||
while (err == LIBEVDEV_READ_STATUS_SYNC) {
|
||||
err = libevdev_next_event(dev_, LIBEVDEV_READ_FLAG_SYNC, &ev);
|
||||
}
|
||||
}
|
||||
if (-err != EAGAIN) {
|
||||
throw errno_error(-err, "Failed to sync evdev device");
|
||||
}
|
||||
|
||||
int numl = libevdev_get_event_value(dev_, EV_LED, LED_NUML);
|
||||
int capsl = libevdev_get_event_value(dev_, EV_LED, LED_CAPSL);
|
||||
int scrolll = libevdev_get_event_value(dev_, EV_LED, LED_SCROLLL);
|
||||
|
||||
struct {
|
||||
bool state;
|
||||
Gtk::Label& label;
|
||||
const std::string& format;
|
||||
const char* name;
|
||||
} label_states[] = {
|
||||
{(bool) numl, numlock_label_, numlock_format_, "Num"},
|
||||
{(bool) capsl, capslock_label_, capslock_format_, "Caps"},
|
||||
{(bool) scrolll, scrolllock_label_, scrolllock_format_, "Scroll"},
|
||||
};
|
||||
for (auto& label_state : label_states) {
|
||||
std::string text;
|
||||
text = fmt::format(label_state.format,
|
||||
fmt::arg("icon", label_state.state ? icon_locked_ : icon_unlocked_),
|
||||
fmt::arg("name", label_state.name));
|
||||
label_state.label.set_markup(text);
|
||||
if (label_state.state) {
|
||||
label_state.label.get_style_context()->add_class("locked");
|
||||
} else {
|
||||
label_state.label.get_style_context()->remove_class("locked");
|
||||
}
|
||||
}
|
||||
|
||||
AModule::update();
|
||||
}
|
@ -12,21 +12,36 @@ auto waybar::modules::Memory::update() -> void {
|
||||
parseMeminfo();
|
||||
|
||||
unsigned long memtotal = meminfo_["MemTotal"];
|
||||
unsigned long swaptotal = 0;
|
||||
if (meminfo_.count("SwapTotal")) {
|
||||
swaptotal = meminfo_["SwapTotal"];
|
||||
}
|
||||
unsigned long memfree;
|
||||
unsigned long swapfree = 0;
|
||||
if (meminfo_.count("SwapFree")) {
|
||||
swapfree = meminfo_["SwapFree"];
|
||||
}
|
||||
if (meminfo_.count("MemAvailable")) {
|
||||
// New kernels (3.4+) have an accurate available memory field.
|
||||
memfree = meminfo_["MemAvailable"];
|
||||
memfree = meminfo_["MemAvailable"] + meminfo_["zfs_size"];
|
||||
} else {
|
||||
// Old kernel; give a best-effort approximation of available memory.
|
||||
memfree = meminfo_["MemFree"] + meminfo_["Buffers"] + meminfo_["Cached"] +
|
||||
meminfo_["SReclaimable"] - meminfo_["Shmem"];
|
||||
meminfo_["SReclaimable"] - meminfo_["Shmem"] + meminfo_["zfs_size"];
|
||||
}
|
||||
|
||||
if (memtotal > 0 && memfree >= 0) {
|
||||
auto total_ram_gigabytes = memtotal / std::pow(1024, 2);
|
||||
auto total_swap_gigabytes = swaptotal / std::pow(1024, 2);
|
||||
int used_ram_percentage = 100 * (memtotal - memfree) / memtotal;
|
||||
int used_swap_percentage = 0;
|
||||
if (swaptotal && swapfree) {
|
||||
used_swap_percentage = 100 * (swaptotal - swapfree) / swaptotal;
|
||||
}
|
||||
auto used_ram_gigabytes = (memtotal - memfree) / std::pow(1024, 2);
|
||||
auto used_swap_gigabytes = (swaptotal - swapfree) / std::pow(1024, 2);
|
||||
auto available_ram_gigabytes = memfree / std::pow(1024, 2);
|
||||
auto available_swap_gigabytes = swapfree / std::pow(1024, 2);
|
||||
|
||||
auto format = format_;
|
||||
auto state = getState(used_ram_percentage);
|
||||
@ -38,12 +53,18 @@ auto waybar::modules::Memory::update() -> void {
|
||||
event_box_.hide();
|
||||
} else {
|
||||
event_box_.show();
|
||||
auto icons = std::vector<std::string>{state};
|
||||
label_.set_markup(fmt::format(format,
|
||||
used_ram_percentage,
|
||||
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("swapPercentage", used_swap_percentage),
|
||||
fmt::arg("used", used_ram_gigabytes),
|
||||
fmt::arg("avail", available_ram_gigabytes)));
|
||||
fmt::arg("swapUsed", used_swap_gigabytes),
|
||||
fmt::arg("avail", available_ram_gigabytes),
|
||||
fmt::arg("swapAvail", available_swap_gigabytes)));
|
||||
}
|
||||
|
||||
if (tooltipEnabled()) {
|
||||
@ -52,9 +73,13 @@ auto waybar::modules::Memory::update() -> void {
|
||||
label_.set_tooltip_text(fmt::format(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("swapPercentage", used_swap_percentage),
|
||||
fmt::arg("used", used_ram_gigabytes),
|
||||
fmt::arg("avail", available_ram_gigabytes)));
|
||||
fmt::arg("swapUsed", used_swap_gigabytes),
|
||||
fmt::arg("avail", available_ram_gigabytes),
|
||||
fmt::arg("swapAvail", available_swap_gigabytes)));
|
||||
} else {
|
||||
label_.set_tooltip_text(fmt::format("{:.{}f}GiB used", used_ram_gigabytes, 1));
|
||||
}
|
||||
|
@ -1,8 +1,29 @@
|
||||
#include "modules/memory.hpp"
|
||||
|
||||
static unsigned zfsArcSize() {
|
||||
std::ifstream zfs_arc_stats{"/proc/spl/kstat/zfs/arcstats"};
|
||||
|
||||
if (zfs_arc_stats.is_open()) {
|
||||
std::string name;
|
||||
std::string type;
|
||||
unsigned long data{0};
|
||||
|
||||
std::string line;
|
||||
while (std::getline(zfs_arc_stats, line)) {
|
||||
std::stringstream(line) >> name >> type >> data;
|
||||
|
||||
if (name == "size") {
|
||||
return data / 1024; // convert to kB
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void waybar::modules::Memory::parseMeminfo() {
|
||||
const std::string data_dir_ = "/proc/meminfo";
|
||||
std::ifstream info(data_dir_);
|
||||
std::ifstream info(data_dir_);
|
||||
if (!info.is_open()) {
|
||||
throw std::runtime_error("Can't open " + data_dir_);
|
||||
}
|
||||
@ -17,4 +38,6 @@ void waybar::modules::Memory::parseMeminfo() {
|
||||
int64_t value = std::stol(line.substr(posDelim + 1));
|
||||
meminfo_[name] = value;
|
||||
}
|
||||
|
||||
meminfo_["zfs_size"] = zfsArcSize();
|
||||
}
|
||||
|
@ -100,7 +100,7 @@ void waybar::modules::MPD::setLabel() {
|
||||
auto format = format_;
|
||||
Glib::ustring artist, album_artist, album, title;
|
||||
std::string date;
|
||||
int song_pos = 0, queue_length = 0;
|
||||
int song_pos = 0, queue_length = 0, volume = 0;
|
||||
std::chrono::seconds elapsedTime, totalTime;
|
||||
|
||||
std::string stateIcon = "";
|
||||
@ -129,7 +129,11 @@ void waybar::modules::MPD::setLabel() {
|
||||
album = getTag(MPD_TAG_ALBUM);
|
||||
title = getTag(MPD_TAG_TITLE);
|
||||
date = getTag(MPD_TAG_DATE);
|
||||
song_pos = mpd_status_get_song_pos(status_.get());
|
||||
song_pos = mpd_status_get_song_pos(status_.get()) + 1;
|
||||
volume = mpd_status_get_volume(status_.get());
|
||||
if (volume < 0) {
|
||||
volume = 0;
|
||||
}
|
||||
queue_length = mpd_status_get_queue_length(status_.get());
|
||||
elapsedTime = std::chrono::seconds(mpd_status_get_elapsed_time(status_.get()));
|
||||
totalTime = std::chrono::seconds(mpd_status_get_total_time(status_.get()));
|
||||
@ -156,6 +160,7 @@ void waybar::modules::MPD::setLabel() {
|
||||
fmt::arg("album", Glib::Markup::escape_text(album).raw()),
|
||||
fmt::arg("title", Glib::Markup::escape_text(title).raw()),
|
||||
fmt::arg("date", Glib::Markup::escape_text(date).raw()),
|
||||
fmt::arg("volume", volume),
|
||||
fmt::arg("elapsedTime", elapsedTime),
|
||||
fmt::arg("totalTime", totalTime),
|
||||
fmt::arg("songPosition", song_pos),
|
||||
@ -180,6 +185,7 @@ void waybar::modules::MPD::setLabel() {
|
||||
fmt::arg("album", album.raw()),
|
||||
fmt::arg("title", title.raw()),
|
||||
fmt::arg("date", date),
|
||||
fmt::arg("volume", volume),
|
||||
fmt::arg("elapsedTime", elapsedTime),
|
||||
fmt::arg("totalTime", totalTime),
|
||||
fmt::arg("songPosition", song_pos),
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -54,7 +54,9 @@ void waybar::modules::Pulseaudio::contextStateCb(pa_context *c, void *data) {
|
||||
c,
|
||||
static_cast<enum pa_subscription_mask>(static_cast<int>(PA_SUBSCRIPTION_MASK_SERVER) |
|
||||
static_cast<int>(PA_SUBSCRIPTION_MASK_SINK) |
|
||||
static_cast<int>(PA_SUBSCRIPTION_MASK_SOURCE)),
|
||||
static_cast<int>(PA_SUBSCRIPTION_MASK_SINK_INPUT) |
|
||||
static_cast<int>(PA_SUBSCRIPTION_MASK_SOURCE) |
|
||||
static_cast<int>(PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT)),
|
||||
nullptr,
|
||||
nullptr);
|
||||
break;
|
||||
@ -79,6 +81,13 @@ bool waybar::modules::Pulseaudio::handleScroll(GdkEventScroll *e) {
|
||||
if (dir == SCROLL_DIR::NONE) {
|
||||
return true;
|
||||
}
|
||||
if (config_["reverse-scrolling"].asInt() == 1){
|
||||
if (dir == SCROLL_DIR::UP) {
|
||||
dir = SCROLL_DIR::DOWN;
|
||||
} else if (dir == SCROLL_DIR::DOWN) {
|
||||
dir = SCROLL_DIR::UP;
|
||||
}
|
||||
}
|
||||
double volume_tick = static_cast<double>(PA_VOLUME_NORM) / 100;
|
||||
pa_volume_t change = volume_tick;
|
||||
pa_cvolume pa_volume = pa_volume_;
|
||||
@ -114,8 +123,12 @@ void waybar::modules::Pulseaudio::subscribeCb(pa_context * conte
|
||||
pa_context_get_server_info(context, serverInfoCb, data);
|
||||
} else if (facility == PA_SUBSCRIPTION_EVENT_SINK) {
|
||||
pa_context_get_sink_info_by_index(context, idx, sinkInfoCb, data);
|
||||
} else if (facility == PA_SUBSCRIPTION_EVENT_SINK_INPUT) {
|
||||
pa_context_get_sink_info_list(context, sinkInfoCb, data);
|
||||
} else if (facility == PA_SUBSCRIPTION_EVENT_SOURCE) {
|
||||
pa_context_get_source_info_by_index(context, idx, sourceInfoCb, data);
|
||||
} else if (facility == PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT) {
|
||||
pa_context_get_source_info_list(context, sourceInfoCb, data);
|
||||
}
|
||||
}
|
||||
|
||||
@ -151,8 +164,24 @@ void waybar::modules::Pulseaudio::sourceInfoCb(pa_context * /*context*/, const p
|
||||
*/
|
||||
void waybar::modules::Pulseaudio::sinkInfoCb(pa_context * /*context*/, const pa_sink_info *i,
|
||||
int /*eol*/, void *data) {
|
||||
if (i == nullptr)
|
||||
return;
|
||||
|
||||
auto pa = static_cast<waybar::modules::Pulseaudio *>(data);
|
||||
if (i != nullptr && pa->default_sink_name_ == i->name) {
|
||||
if (pa->current_sink_name_ == i->name) {
|
||||
if (i->state != PA_SINK_RUNNING) {
|
||||
pa->current_sink_running_ = false;
|
||||
} else {
|
||||
pa->current_sink_running_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!pa->current_sink_running_ && i->state == PA_SINK_RUNNING) {
|
||||
pa->current_sink_name_ = i->name;
|
||||
pa->current_sink_running_ = true;
|
||||
}
|
||||
|
||||
if (pa->current_sink_name_ == i->name) {
|
||||
pa->pa_volume_ = i->volume;
|
||||
float volume = static_cast<float>(pa_cvolume_avg(&(pa->pa_volume_))) / float{PA_VOLUME_NORM};
|
||||
pa->sink_idx_ = i->index;
|
||||
@ -175,11 +204,11 @@ void waybar::modules::Pulseaudio::sinkInfoCb(pa_context * /*context*/, const pa_
|
||||
void waybar::modules::Pulseaudio::serverInfoCb(pa_context *context, const pa_server_info *i,
|
||||
void *data) {
|
||||
auto pa = static_cast<waybar::modules::Pulseaudio *>(data);
|
||||
pa->default_sink_name_ = i->default_sink_name;
|
||||
pa->current_sink_name_ = i->default_sink_name;
|
||||
pa->default_source_name_ = i->default_source_name;
|
||||
|
||||
pa_context_get_sink_info_by_name(context, i->default_sink_name, sinkInfoCb, data);
|
||||
pa_context_get_source_info_by_name(context, i->default_source_name, sourceInfoCb, data);
|
||||
pa_context_get_sink_info_list(context, sinkInfoCb, data);
|
||||
pa_context_get_source_info_list(context, sourceInfoCb, data);
|
||||
}
|
||||
|
||||
static const std::array<std::string, 9> ports = {
|
||||
@ -194,22 +223,26 @@ static const std::array<std::string, 9> ports = {
|
||||
"phone",
|
||||
};
|
||||
|
||||
const std::string waybar::modules::Pulseaudio::getPortIcon() const {
|
||||
const std::vector<std::string> waybar::modules::Pulseaudio::getPulseIcon() const {
|
||||
std::vector<std::string> res = {current_sink_name_, default_source_name_};
|
||||
std::string nameLC = port_name_ + form_factor_;
|
||||
std::transform(nameLC.begin(), nameLC.end(), nameLC.begin(), ::tolower);
|
||||
for (auto const &port : ports) {
|
||||
if (nameLC.find(port) != std::string::npos) {
|
||||
return port;
|
||||
res.push_back(port);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
return port_name_;
|
||||
return res;
|
||||
}
|
||||
|
||||
auto waybar::modules::Pulseaudio::update() -> void {
|
||||
auto format = format_;
|
||||
std::string tooltip_format;
|
||||
if (!alt_) {
|
||||
std::string format_name = "format";
|
||||
if (monitor_.find("a2dp_sink") != std::string::npos) {
|
||||
if (monitor_.find("a2dp_sink") != std::string::npos || // PulseAudio
|
||||
monitor_.find("a2dp-sink") != std::string::npos) { // PipeWire
|
||||
format_name = format_name + "-bluetooth";
|
||||
label_.get_style_context()->add_class("bluetooth");
|
||||
} else {
|
||||
@ -222,28 +255,53 @@ auto waybar::modules::Pulseaudio::update() -> void {
|
||||
}
|
||||
format_name = format_name + "-muted";
|
||||
label_.get_style_context()->add_class("muted");
|
||||
label_.get_style_context()->add_class("sink-muted");
|
||||
} else {
|
||||
label_.get_style_context()->remove_class("muted");
|
||||
label_.get_style_context()->remove_class("sink-muted");
|
||||
}
|
||||
format =
|
||||
config_[format_name].isString() ? config_[format_name].asString() : format;
|
||||
}
|
||||
// TODO: find a better way to split source/sink
|
||||
std::string format_source = "{volume}%";
|
||||
if (source_muted_ && config_["format-source-muted"].isString()) {
|
||||
format_source = config_["format-source-muted"].asString();
|
||||
} else if (!source_muted_ && config_["format-source"].isString()) {
|
||||
format_source = config_["format-source"].asString();
|
||||
if (source_muted_) {
|
||||
label_.get_style_context()->add_class("source-muted");
|
||||
if (config_["format-source-muted"].isString()) {
|
||||
format_source = config_["format-source-muted"].asString();
|
||||
}
|
||||
} else {
|
||||
label_.get_style_context()->remove_class("source-muted");
|
||||
if (config_["format-source-muted"].isString()) {
|
||||
format_source = config_["format-source"].asString();
|
||||
}
|
||||
}
|
||||
format_source = fmt::format(format_source, fmt::arg("volume", source_volume_));
|
||||
label_.set_markup(fmt::format(format,
|
||||
fmt::arg("desc", desc_),
|
||||
fmt::arg("volume", volume_),
|
||||
fmt::arg("format_source", format_source),
|
||||
fmt::arg("icon", getIcon(volume_, getPortIcon()))));
|
||||
fmt::arg("source_volume", source_volume_),
|
||||
fmt::arg("source_desc", source_desc_),
|
||||
fmt::arg("icon", getIcon(volume_, getPulseIcon()))));
|
||||
getState(volume_);
|
||||
|
||||
if (tooltipEnabled()) {
|
||||
label_.set_tooltip_text(desc_);
|
||||
if (tooltip_format.empty() && config_["tooltip-format"].isString()) {
|
||||
tooltip_format = config_["tooltip-format"].asString();
|
||||
}
|
||||
if (!tooltip_format.empty()) {
|
||||
label_.set_tooltip_text(fmt::format(
|
||||
tooltip_format,
|
||||
fmt::arg("desc", desc_),
|
||||
fmt::arg("volume", volume_),
|
||||
fmt::arg("format_source", format_source),
|
||||
fmt::arg("source_volume", source_volume_),
|
||||
fmt::arg("source_desc", source_desc_),
|
||||
fmt::arg("icon", getIcon(volume_, getPulseIcon()))));
|
||||
} else {
|
||||
label_.set_tooltip_text(desc_);
|
||||
}
|
||||
}
|
||||
|
||||
// Call parent update
|
||||
|
@ -7,7 +7,6 @@
|
||||
|
||||
#include "client.hpp"
|
||||
#include "modules/river/tags.hpp"
|
||||
#include "river-status-unstable-v1-client-protocol.h"
|
||||
#include "xdg-output-unstable-v1-client-protocol.h"
|
||||
|
||||
namespace waybar::modules::river {
|
||||
@ -22,29 +21,71 @@ static void listen_view_tags(void *data, struct zriver_output_status_v1 *zriver_
|
||||
static_cast<Tags *>(data)->handle_view_tags(tags);
|
||||
}
|
||||
|
||||
static void listen_urgent_tags(void *data, struct zriver_output_status_v1 *zriver_output_status_v1,
|
||||
uint32_t tags) {
|
||||
static_cast<Tags *>(data)->handle_urgent_tags(tags);
|
||||
}
|
||||
|
||||
static const zriver_output_status_v1_listener output_status_listener_impl{
|
||||
.focused_tags = listen_focused_tags,
|
||||
.view_tags = listen_view_tags,
|
||||
.urgent_tags = listen_urgent_tags,
|
||||
};
|
||||
|
||||
static void listen_command_success(void *data,
|
||||
struct zriver_command_callback_v1 *zriver_command_callback_v1,
|
||||
const char *output) {
|
||||
// Do nothing but keep listener to avoid crashing when command was successful
|
||||
}
|
||||
|
||||
static void listen_command_failure(void *data,
|
||||
struct zriver_command_callback_v1 *zriver_command_callback_v1,
|
||||
const char *output) {
|
||||
spdlog::error("failure when selecting/toggling tags {}", output);
|
||||
}
|
||||
|
||||
static const zriver_command_callback_v1_listener command_callback_listener_impl {
|
||||
.success = listen_command_success,
|
||||
.failure = listen_command_failure,
|
||||
};
|
||||
|
||||
static void handle_global(void *data, struct wl_registry *registry, uint32_t name,
|
||||
const char *interface, uint32_t version) {
|
||||
if (std::strcmp(interface, zriver_status_manager_v1_interface.name) == 0) {
|
||||
version = std::min<uint32_t>(version, 2);
|
||||
if (version < ZRIVER_OUTPUT_STATUS_V1_URGENT_TAGS_SINCE_VERSION) {
|
||||
spdlog::warn("river server does not support urgent tags");
|
||||
}
|
||||
static_cast<Tags *>(data)->status_manager_ = static_cast<struct zriver_status_manager_v1 *>(
|
||||
wl_registry_bind(registry, name, &zriver_status_manager_v1_interface, version));
|
||||
}
|
||||
|
||||
if (std::strcmp(interface, zriver_control_v1_interface.name) == 0) {
|
||||
version = std::min<uint32_t>(version, 1);
|
||||
static_cast<Tags *>(data)->control_ = static_cast<struct zriver_control_v1 *>(
|
||||
wl_registry_bind(registry, name, &zriver_control_v1_interface, version));
|
||||
}
|
||||
|
||||
if (std::strcmp(interface, wl_seat_interface.name) == 0) {
|
||||
version = std::min<uint32_t>(version, 1);
|
||||
static_cast<Tags *>(data)->seat_ = static_cast<struct wl_seat *>(
|
||||
wl_registry_bind(registry, name, &wl_seat_interface, version));
|
||||
}
|
||||
}
|
||||
|
||||
static void handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) {
|
||||
/* Ignore event */
|
||||
}
|
||||
|
||||
|
||||
static const wl_registry_listener registry_listener_impl = {.global = handle_global,
|
||||
.global_remove = handle_global_remove};
|
||||
|
||||
Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &config)
|
||||
: waybar::AModule(config, "tags", id, false, false),
|
||||
status_manager_{nullptr},
|
||||
control_{nullptr},
|
||||
seat_{nullptr},
|
||||
bar_(bar),
|
||||
box_{bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0},
|
||||
output_status_{nullptr} {
|
||||
@ -58,14 +99,23 @@ Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &con
|
||||
return;
|
||||
}
|
||||
|
||||
if (!control_) {
|
||||
spdlog::error("river_control_v1 not advertised");
|
||||
}
|
||||
|
||||
if (!seat_) {
|
||||
spdlog::error("wl_seat not advertised");
|
||||
}
|
||||
|
||||
box_.set_name("tags");
|
||||
if (!id.empty()) {
|
||||
box_.get_style_context()->add_class(id);
|
||||
}
|
||||
event_box_.add(box_);
|
||||
|
||||
// Default to 9 tags
|
||||
const uint32_t num_tags = config["num-tags"].isUInt() ? config_["num-tags"].asUInt() : 9;
|
||||
// Default to 9 tags, cap at 32
|
||||
const uint32_t num_tags =
|
||||
config["num-tags"].isUInt() ? std::min<uint32_t>(32, config_["num-tags"].asUInt()) : 9;
|
||||
|
||||
std::vector<std::string> tag_labels(num_tags);
|
||||
for (uint32_t tag = 0; tag < num_tags; ++tag) {
|
||||
@ -78,11 +128,17 @@ Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &con
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t i = 1;
|
||||
for (const auto &tag_label : tag_labels) {
|
||||
Gtk::Button &button = buttons_.emplace_back(tag_label);
|
||||
button.set_relief(Gtk::RELIEF_NONE);
|
||||
box_.pack_start(button, false, false, 0);
|
||||
if (!config_["disable-click"].asBool()) {
|
||||
button.signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &Tags::handle_primary_clicked), i));
|
||||
button.signal_button_press_event().connect(sigc::bind(sigc::mem_fun(*this, &Tags::handle_button_press), i));
|
||||
}
|
||||
button.show();
|
||||
i <<= 1;
|
||||
}
|
||||
|
||||
struct wl_output *output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj());
|
||||
@ -96,6 +152,31 @@ Tags::~Tags() {
|
||||
if (output_status_) {
|
||||
zriver_output_status_v1_destroy(output_status_);
|
||||
}
|
||||
|
||||
if (control_) {
|
||||
zriver_control_v1_destroy(control_);
|
||||
}
|
||||
}
|
||||
|
||||
void Tags::handle_primary_clicked(uint32_t tag) {
|
||||
// Send river command to select tag on left mouse click
|
||||
zriver_command_callback_v1 *callback;
|
||||
zriver_control_v1_add_argument(control_, "set-focused-tags");
|
||||
zriver_control_v1_add_argument(control_, std::to_string(tag).c_str());
|
||||
callback = zriver_control_v1_run_command(control_, seat_);
|
||||
zriver_command_callback_v1_add_listener(callback, &command_callback_listener_impl, nullptr);
|
||||
}
|
||||
|
||||
bool Tags::handle_button_press(GdkEventButton *event_button, uint32_t tag) {
|
||||
if (event_button->type == GDK_BUTTON_PRESS && event_button->button == 3) {
|
||||
// Send river command to toggle tag on right mouse click
|
||||
zriver_command_callback_v1 *callback;
|
||||
zriver_control_v1_add_argument(control_, "toggle-focused-tags");
|
||||
zriver_control_v1_add_argument(control_, std::to_string(tag).c_str());
|
||||
callback = zriver_control_v1_run_command(control_, seat_);
|
||||
zriver_command_callback_v1_add_listener(callback, &command_callback_listener_impl, nullptr);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Tags::handle_focused_tags(uint32_t tags) {
|
||||
@ -129,4 +210,16 @@ void Tags::handle_view_tags(struct wl_array *view_tags) {
|
||||
}
|
||||
}
|
||||
|
||||
void Tags::handle_urgent_tags(uint32_t tags) {
|
||||
uint32_t i = 0;
|
||||
for (auto &button : buttons_) {
|
||||
if ((1 << i) & tags) {
|
||||
button.get_style_context()->add_class("urgent");
|
||||
} else {
|
||||
button.get_style_context()->remove_class("urgent");
|
||||
}
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
} /* namespace waybar::modules::river */
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
namespace waybar::modules::SNI {
|
||||
|
||||
Host::Host(const std::size_t id, const Json::Value& config,
|
||||
Host::Host(const std::size_t id, const Json::Value& config, const Bar& bar,
|
||||
const std::function<void(std::unique_ptr<Item>&)>& on_add,
|
||||
const std::function<void(std::unique_ptr<Item>&)>& on_remove)
|
||||
: bus_name_("org.kde.StatusNotifierHost-" + std::to_string(getpid()) + "-" +
|
||||
@ -13,6 +13,7 @@ Host::Host(const std::size_t id, const Json::Value& config,
|
||||
bus_name_id_(Gio::DBus::own_name(Gio::DBus::BusType::BUS_TYPE_SESSION, bus_name_,
|
||||
sigc::mem_fun(*this, &Host::busAcquired))),
|
||||
config_(config),
|
||||
bar_(bar),
|
||||
on_add_(on_add),
|
||||
on_remove_(on_remove) {}
|
||||
|
||||
@ -136,7 +137,7 @@ void Host::addRegisteredItem(std::string service) {
|
||||
return bus_name == item->bus_name && object_path == item->object_path;
|
||||
});
|
||||
if (it == items_.end()) {
|
||||
items_.emplace_back(new Item(bus_name, object_path, config_));
|
||||
items_.emplace_back(new Item(bus_name, object_path, config_, bar_));
|
||||
on_add_(items_.back());
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,14 @@
|
||||
#include "modules/sni/item.hpp"
|
||||
#include <glibmm/main.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <fstream>
|
||||
|
||||
template <>
|
||||
struct fmt::formatter<Glib::ustring> : formatter<std::string> {
|
||||
template <typename FormatContext>
|
||||
auto format(const Glib::ustring& value, FormatContext& ctx) {
|
||||
return formatter<std::string>::format(value, ctx);
|
||||
}
|
||||
};
|
||||
#include <gdkmm/general.h>
|
||||
#include <glibmm/main.h>
|
||||
#include <gtkmm/tooltip.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
|
||||
#include "util/format.hpp"
|
||||
|
||||
template <>
|
||||
struct fmt::formatter<Glib::VariantBase> : formatter<std::string> {
|
||||
@ -34,19 +33,31 @@ namespace waybar::modules::SNI {
|
||||
static const Glib::ustring SNI_INTERFACE_NAME = sn_item_interface_info()->name;
|
||||
static const unsigned UPDATE_DEBOUNCE_TIME = 10;
|
||||
|
||||
Item::Item(const std::string& bn, const std::string& op, const Json::Value& config)
|
||||
Item::Item(const std::string& bn, const std::string& op, const Json::Value& config, const Bar& bar)
|
||||
: bus_name(bn),
|
||||
object_path(op),
|
||||
icon_size(16),
|
||||
effective_icon_size(0),
|
||||
icon_theme(Gtk::IconTheme::create()),
|
||||
update_pending_(false) {
|
||||
icon_theme(Gtk::IconTheme::create()) {
|
||||
if (config["icon-size"].isUInt()) {
|
||||
icon_size = config["icon-size"].asUInt();
|
||||
}
|
||||
if (config["smooth-scrolling-threshold"].isNumeric()) {
|
||||
scroll_threshold_ = config["smooth-scrolling-threshold"].asDouble();
|
||||
}
|
||||
if (config["show-passive-items"].isBool()) {
|
||||
show_passive_ = config["show-passive-items"].asBool();
|
||||
}
|
||||
|
||||
auto &window = const_cast<Bar &>(bar).window;
|
||||
window.signal_configure_event().connect_notify(sigc::mem_fun(*this, &Item::onConfigure));
|
||||
event_box.add(image);
|
||||
event_box.add_events(Gdk::BUTTON_PRESS_MASK);
|
||||
event_box.add_events(Gdk::BUTTON_PRESS_MASK | Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);
|
||||
event_box.signal_button_press_event().connect(sigc::mem_fun(*this, &Item::handleClick));
|
||||
event_box.signal_scroll_event().connect(sigc::mem_fun(*this, &Item::handleScroll));
|
||||
// initial visibility
|
||||
event_box.show_all();
|
||||
event_box.set_visible(show_passive_);
|
||||
|
||||
cancellable_ = Gio::Cancellable::create();
|
||||
|
||||
@ -60,6 +71,10 @@ Item::Item(const std::string& bn, const std::string& op, const Json::Value& conf
|
||||
interface);
|
||||
}
|
||||
|
||||
void Item::onConfigure(GdkEventConfigure* ev) {
|
||||
this->updateImage();
|
||||
}
|
||||
|
||||
void Item::proxyReady(Glib::RefPtr<Gio::AsyncResult>& result) {
|
||||
try {
|
||||
this->proxy_ = Gio::DBus::Proxy::create_for_bus_finish(result);
|
||||
@ -73,12 +88,11 @@ void Item::proxyReady(Glib::RefPtr<Gio::AsyncResult>& result) {
|
||||
|
||||
this->proxy_->signal_signal().connect(sigc::mem_fun(*this, &Item::onSignal));
|
||||
|
||||
if (this->id.empty() || this->category.empty() || this->status.empty()) {
|
||||
if (this->id.empty() || this->category.empty()) {
|
||||
spdlog::error("Invalid Status Notifier Item: {}, {}", bus_name, object_path);
|
||||
return;
|
||||
}
|
||||
this->updateImage();
|
||||
// this->event_box.set_tooltip_text(this->title);
|
||||
|
||||
} catch (const Glib::Error& err) {
|
||||
spdlog::error("Failed to create DBus Proxy for {} {}: {}", bus_name, object_path, err.what());
|
||||
@ -88,10 +102,24 @@ void Item::proxyReady(Glib::RefPtr<Gio::AsyncResult>& result) {
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T get_variant(Glib::VariantBase& value) {
|
||||
T get_variant(const Glib::VariantBase& value) {
|
||||
return Glib::VariantBase::cast_dynamic<Glib::Variant<T>>(value).get();
|
||||
}
|
||||
|
||||
template <>
|
||||
ToolTip get_variant<ToolTip>(const Glib::VariantBase& value) {
|
||||
ToolTip result;
|
||||
// Unwrap (sa(iiay)ss)
|
||||
auto container = value.cast_dynamic<Glib::VariantContainerBase>(value);
|
||||
result.icon_name = get_variant<Glib::ustring>(container.get_child(0));
|
||||
result.text = get_variant<Glib::ustring>(container.get_child(2));
|
||||
auto description = get_variant<Glib::ustring>(container.get_child(3));
|
||||
if (!description.empty()) {
|
||||
result.text = fmt::format("<b>{}</b>\n{}", result.text, description);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) {
|
||||
try {
|
||||
spdlog::trace("Set tray item property: {}.{} = {}", id.empty() ? bus_name : id, name, value);
|
||||
@ -102,10 +130,11 @@ void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) {
|
||||
id = get_variant<std::string>(value);
|
||||
} else if (name == "Title") {
|
||||
title = get_variant<std::string>(value);
|
||||
if (tooltip.text.empty()) {
|
||||
event_box.set_tooltip_markup(title);
|
||||
}
|
||||
} else if (name == "Status") {
|
||||
status = get_variant<std::string>(value);
|
||||
} else if (name == "WindowId") {
|
||||
window_id = get_variant<int32_t>(value);
|
||||
setStatus(get_variant<Glib::ustring>(value));
|
||||
} else if (name == "IconName") {
|
||||
icon_name = get_variant<std::string>(value);
|
||||
} else if (name == "IconPixmap") {
|
||||
@ -121,7 +150,10 @@ void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) {
|
||||
} else if (name == "AttentionMovieName") {
|
||||
attention_movie_name = get_variant<std::string>(value);
|
||||
} else if (name == "ToolTip") {
|
||||
// TODO: tooltip
|
||||
tooltip = get_variant<ToolTip>(value);
|
||||
if (!tooltip.text.empty()) {
|
||||
event_box.set_tooltip_markup(tooltip.text);
|
||||
}
|
||||
} else if (name == "IconThemePath") {
|
||||
icon_theme_path = get_variant<std::string>(value);
|
||||
if (!icon_theme_path.empty()) {
|
||||
@ -148,9 +180,22 @@ void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) {
|
||||
}
|
||||
}
|
||||
|
||||
void Item::getUpdatedProperties() {
|
||||
update_pending_ = false;
|
||||
void Item::setStatus(const Glib::ustring& value) {
|
||||
Glib::ustring lower = value.lowercase();
|
||||
event_box.set_visible(show_passive_ || lower.compare("passive") != 0);
|
||||
|
||||
auto style = event_box.get_style_context();
|
||||
for (const auto& class_name : style->list_classes()) {
|
||||
style->remove_class(class_name);
|
||||
}
|
||||
if (lower.compare("needsattention") == 0) {
|
||||
// convert status to dash-case for CSS
|
||||
lower = "needs-attention";
|
||||
}
|
||||
style->add_class(lower);
|
||||
}
|
||||
|
||||
void Item::getUpdatedProperties() {
|
||||
auto params = Glib::VariantContainerBase::create_tuple(
|
||||
{Glib::Variant<Glib::ustring>::create(SNI_INTERFACE_NAME)});
|
||||
proxy_->call("org.freedesktop.DBus.Properties.GetAll",
|
||||
@ -167,33 +212,48 @@ void Item::processUpdatedProperties(Glib::RefPtr<Gio::AsyncResult>& _result) {
|
||||
auto properties = properties_variant.get();
|
||||
|
||||
for (const auto& [name, value] : properties) {
|
||||
Glib::VariantBase old_value;
|
||||
proxy_->get_cached_property(old_value, name);
|
||||
if (!old_value || !value.equal(old_value)) {
|
||||
proxy_->set_cached_property(name, value);
|
||||
if (update_pending_.count(name.raw())) {
|
||||
setProperty(name, const_cast<Glib::VariantBase&>(value));
|
||||
}
|
||||
}
|
||||
|
||||
this->updateImage();
|
||||
// this->event_box.set_tooltip_text(this->title);
|
||||
} catch (const Glib::Error& err) {
|
||||
spdlog::warn("Failed to update properties: {}", err.what());
|
||||
} catch (const std::exception& err) {
|
||||
spdlog::warn("Failed to update properties: {}", err.what());
|
||||
}
|
||||
update_pending_.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Mapping from a signal name to a set of possibly changed properties.
|
||||
* Commented signals are not handled by the tray module at the moment.
|
||||
*/
|
||||
static const std::map<std::string_view, std::set<std::string_view>> signal2props = {
|
||||
{"NewTitle", {"Title"}},
|
||||
{"NewIcon", {"IconName", "IconPixmap"}},
|
||||
// {"NewAttentionIcon", {"AttentionIconName", "AttentionIconPixmap", "AttentionMovieName"}},
|
||||
// {"NewOverlayIcon", {"OverlayIconName", "OverlayIconPixmap"}},
|
||||
{"NewIconThemePath", {"IconThemePath"}},
|
||||
{"NewToolTip", {"ToolTip"}},
|
||||
{"NewStatus", {"Status"}},
|
||||
// {"XAyatanaNewLabel", {"XAyatanaLabel"}},
|
||||
};
|
||||
|
||||
void Item::onSignal(const Glib::ustring& sender_name, const Glib::ustring& signal_name,
|
||||
const Glib::VariantContainerBase& arguments) {
|
||||
spdlog::trace("Tray item '{}' got signal {}", id, signal_name);
|
||||
if (!update_pending_ && signal_name.compare(0, 3, "New") == 0) {
|
||||
/* Debounce signals and schedule update of all properties.
|
||||
* Based on behavior of Plasma dataengine for StatusNotifierItem.
|
||||
*/
|
||||
update_pending_ = true;
|
||||
Glib::signal_timeout().connect_once(sigc::mem_fun(*this, &Item::getUpdatedProperties),
|
||||
UPDATE_DEBOUNCE_TIME);
|
||||
auto changed = signal2props.find(signal_name.raw());
|
||||
if (changed != signal2props.end()) {
|
||||
if (update_pending_.empty()) {
|
||||
/* Debounce signals and schedule update of all properties.
|
||||
* Based on behavior of Plasma dataengine for StatusNotifierItem.
|
||||
*/
|
||||
Glib::signal_timeout().connect_once(sigc::mem_fun(*this, &Item::getUpdatedProperties),
|
||||
UPDATE_DEBOUNCE_TIME);
|
||||
}
|
||||
update_pending_.insert(changed->second.begin(), changed->second.end());
|
||||
}
|
||||
}
|
||||
|
||||
@ -222,7 +282,11 @@ Glib::RefPtr<Gdk::Pixbuf> Item::extractPixBuf(GVariant* variant) {
|
||||
if (array != nullptr) {
|
||||
g_free(array);
|
||||
}
|
||||
#if GLIB_MAJOR_VERSION >= 2 && GLIB_MINOR_VERSION >= 68
|
||||
array = static_cast<guchar*>(g_memdup2(data, size));
|
||||
#else
|
||||
array = static_cast<guchar*>(g_memdup(data, size));
|
||||
#endif
|
||||
lwidth = width;
|
||||
lheight = height;
|
||||
}
|
||||
@ -252,35 +316,41 @@ Glib::RefPtr<Gdk::Pixbuf> Item::extractPixBuf(GVariant* variant) {
|
||||
}
|
||||
|
||||
void Item::updateImage() {
|
||||
image.set_from_icon_name("image-missing", Gtk::ICON_SIZE_MENU);
|
||||
image.set_pixel_size(icon_size);
|
||||
if (!icon_name.empty()) {
|
||||
try {
|
||||
// Try to find icons specified by path and filename
|
||||
auto pixbuf = getIconPixbuf();
|
||||
auto scaled_icon_size = getScaledIconSize();
|
||||
|
||||
if (!pixbuf) {
|
||||
pixbuf = getIconByName("image-missing", getScaledIconSize());
|
||||
}
|
||||
|
||||
// If the loaded icon is not square, assume that the icon height should match the
|
||||
// requested icon size, but the width is allowed to be different. As such, if the
|
||||
// height of the image does not match the requested icon size, resize the icon such that
|
||||
// the aspect ratio is maintained, but the height matches the requested icon size.
|
||||
if (pixbuf->get_height() != scaled_icon_size) {
|
||||
int width = scaled_icon_size * pixbuf->get_width() / pixbuf->get_height();
|
||||
pixbuf = pixbuf->scale_simple(width, scaled_icon_size, Gdk::InterpType::INTERP_BILINEAR);
|
||||
}
|
||||
|
||||
auto surface = Gdk::Cairo::create_surface_from_pixbuf(pixbuf, 0, image.get_window());
|
||||
image.set(surface);
|
||||
}
|
||||
|
||||
Glib::RefPtr<Gdk::Pixbuf> Item::getIconPixbuf() {
|
||||
try {
|
||||
if (!icon_name.empty()) {
|
||||
std::ifstream temp(icon_name);
|
||||
if (temp.is_open()) {
|
||||
auto pixbuf = Gdk::Pixbuf::create_from_file(icon_name);
|
||||
if (pixbuf->gobj() != nullptr) {
|
||||
// An icon specified by path and filename may be the wrong size for
|
||||
// the tray
|
||||
// Keep the aspect ratio and scale to make the height equal to icon_size
|
||||
// If people have non square icons, assume they want it to grow in width not height
|
||||
int width = icon_size * pixbuf->get_width() / pixbuf->get_height();
|
||||
|
||||
pixbuf = pixbuf->scale_simple(width, icon_size, Gdk::InterpType::INTERP_BILINEAR);
|
||||
image.set(pixbuf);
|
||||
}
|
||||
} else {
|
||||
image.set(getIconByName(icon_name, icon_size));
|
||||
return Gdk::Pixbuf::create_from_file(icon_name);
|
||||
}
|
||||
} catch (Glib::Error& e) {
|
||||
spdlog::error("Item '{}': {}", id, static_cast<std::string>(e.what()));
|
||||
return getIconByName(icon_name, getScaledIconSize());
|
||||
} else if (icon_pixmap) {
|
||||
return icon_pixmap;
|
||||
}
|
||||
} else if (icon_pixmap) {
|
||||
// An icon extracted may be the wrong size for the tray
|
||||
icon_pixmap = icon_pixmap->scale_simple(icon_size, icon_size, Gdk::InterpType::INTERP_BILINEAR);
|
||||
image.set(icon_pixmap);
|
||||
} catch (Glib::Error& e) {
|
||||
spdlog::error("Item '{}': {}", id, static_cast<std::string>(e.what()));
|
||||
}
|
||||
return getIconByName("image-missing", getScaledIconSize());
|
||||
}
|
||||
|
||||
Glib::RefPtr<Gdk::Pixbuf> Item::getIconByName(const std::string& name, int request_size) {
|
||||
@ -315,6 +385,11 @@ Glib::RefPtr<Gdk::Pixbuf> Item::getIconByName(const std::string& name, int reque
|
||||
name.c_str(), tmp_size, Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE);
|
||||
}
|
||||
|
||||
double Item::getScaledIconSize() {
|
||||
// apply the scale factor from the Gtk window to the requested icon size
|
||||
return icon_size * image.get_scale_factor();
|
||||
}
|
||||
|
||||
void Item::onMenuDestroyed(Item* self, GObject* old_menu_pointer) {
|
||||
if (old_menu_pointer == reinterpret_cast<GObject*>(self->dbus_menu)) {
|
||||
self->gtk_menu = nullptr;
|
||||
@ -360,4 +435,52 @@ bool Item::handleClick(GdkEventButton* const& ev) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Item::handleScroll(GdkEventScroll* const& ev) {
|
||||
int dx = 0, dy = 0;
|
||||
switch (ev->direction) {
|
||||
case GDK_SCROLL_UP:
|
||||
dy = -1;
|
||||
break;
|
||||
case GDK_SCROLL_DOWN:
|
||||
dy = 1;
|
||||
break;
|
||||
case GDK_SCROLL_LEFT:
|
||||
dx = -1;
|
||||
break;
|
||||
case GDK_SCROLL_RIGHT:
|
||||
dx = 1;
|
||||
break;
|
||||
case GDK_SCROLL_SMOOTH:
|
||||
distance_scrolled_x_ += ev->delta_x;
|
||||
distance_scrolled_y_ += ev->delta_y;
|
||||
// check against the configured threshold and ensure that the absolute value >= 1
|
||||
if (distance_scrolled_x_ > scroll_threshold_) {
|
||||
dx = (int)lround(std::max(distance_scrolled_x_, 1.0));
|
||||
distance_scrolled_x_ = 0;
|
||||
} else if (distance_scrolled_x_ < -scroll_threshold_) {
|
||||
dx = (int)lround(std::min(distance_scrolled_x_, -1.0));
|
||||
distance_scrolled_x_ = 0;
|
||||
}
|
||||
if (distance_scrolled_y_ > scroll_threshold_) {
|
||||
dy = (int)lround(std::max(distance_scrolled_y_, 1.0));
|
||||
distance_scrolled_y_ = 0;
|
||||
} else if (distance_scrolled_y_ < -scroll_threshold_) {
|
||||
dy = (int)lround(std::min(distance_scrolled_y_, -1.0));
|
||||
distance_scrolled_y_ = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (dx != 0) {
|
||||
auto parameters = Glib::VariantContainerBase::create_tuple(
|
||||
{Glib::Variant<int>::create(dx), Glib::Variant<Glib::ustring>::create("horizontal")});
|
||||
proxy_->call("Scroll", parameters);
|
||||
}
|
||||
if (dy != 0) {
|
||||
auto parameters = Glib::VariantContainerBase::create_tuple(
|
||||
{Glib::Variant<int>::create(dy), Glib::Variant<Glib::ustring>::create("vertical")});
|
||||
proxy_->call("Scroll", parameters);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace waybar::modules::SNI
|
||||
|
@ -7,7 +7,7 @@ Tray::Tray(const std::string& id, const Bar& bar, const Json::Value& config)
|
||||
: AModule(config, "tray", id),
|
||||
box_(bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0),
|
||||
watcher_(SNI::Watcher::getInstance()),
|
||||
host_(nb_hosts_, config, std::bind(&Tray::onAdd, this, std::placeholders::_1),
|
||||
host_(nb_hosts_, config, bar, std::bind(&Tray::onAdd, this, std::placeholders::_1),
|
||||
std::bind(&Tray::onRemove, this, std::placeholders::_1)) {
|
||||
spdlog::warn(
|
||||
"For a functional tray you must have libappindicator-* installed and export "
|
||||
@ -25,7 +25,11 @@ Tray::Tray(const std::string& id, const Bar& bar, const Json::Value& config)
|
||||
}
|
||||
|
||||
void Tray::onAdd(std::unique_ptr<Item>& item) {
|
||||
box_.pack_start(item->event_box);
|
||||
if (config_["reverse-direction"].isBool() && config_["reverse-direction"].asBool()) {
|
||||
box_.pack_end(item->event_box);
|
||||
} else {
|
||||
box_.pack_start(item->event_box);
|
||||
}
|
||||
dp.emit();
|
||||
}
|
||||
|
||||
@ -35,11 +39,8 @@ void Tray::onRemove(std::unique_ptr<Item>& item) {
|
||||
}
|
||||
|
||||
auto Tray::update() -> void {
|
||||
if (box_.get_children().empty()) {
|
||||
box_.hide();
|
||||
} else {
|
||||
box_.show_all();
|
||||
}
|
||||
// Show tray only when items are available
|
||||
box_.set_visible(!box_.get_children().empty());
|
||||
// Call parent update
|
||||
AModule::update();
|
||||
}
|
||||
|
107
src/modules/sway/bar.cpp
Normal file
107
src/modules/sway/bar.cpp
Normal file
@ -0,0 +1,107 @@
|
||||
#include "modules/sway/bar.hpp"
|
||||
|
||||
#include <fmt/ostream.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include "bar.hpp"
|
||||
#include "modules/sway/ipc/ipc.hpp"
|
||||
|
||||
namespace waybar::modules::sway {
|
||||
|
||||
BarIpcClient::BarIpcClient(waybar::Bar& bar) : bar_{bar} {
|
||||
{
|
||||
sigc::connection handle =
|
||||
ipc_.signal_cmd.connect(sigc::mem_fun(*this, &BarIpcClient::onInitialConfig));
|
||||
ipc_.sendCmd(IPC_GET_BAR_CONFIG, bar_.bar_id);
|
||||
|
||||
handle.disconnect();
|
||||
}
|
||||
|
||||
signal_config_.connect(sigc::mem_fun(*this, &BarIpcClient::onConfigUpdate));
|
||||
signal_visible_.connect(sigc::mem_fun(*this, &BarIpcClient::onVisibilityUpdate));
|
||||
|
||||
ipc_.subscribe(R"(["bar_state_update", "barconfig_update"])");
|
||||
ipc_.signal_event.connect(sigc::mem_fun(*this, &BarIpcClient::onIpcEvent));
|
||||
// Launch worker
|
||||
ipc_.setWorker([this] {
|
||||
try {
|
||||
ipc_.handleEvent();
|
||||
} catch (const std::exception& e) {
|
||||
spdlog::error("BarIpcClient::handleEvent {}", e.what());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
struct swaybar_config parseConfig(const Json::Value& payload) {
|
||||
swaybar_config conf;
|
||||
if (auto id = payload["id"]; id.isString()) {
|
||||
conf.id = id.asString();
|
||||
}
|
||||
if (auto mode = payload["mode"]; mode.isString()) {
|
||||
conf.mode = mode.asString();
|
||||
}
|
||||
if (auto hs = payload["hidden_state"]; hs.isString()) {
|
||||
conf.hidden_state = hs.asString();
|
||||
}
|
||||
return conf;
|
||||
}
|
||||
|
||||
void BarIpcClient::onInitialConfig(const struct Ipc::ipc_response& res) {
|
||||
auto payload = parser_.parse(res.payload);
|
||||
if (auto success = payload.get("success", true); !success.asBool()) {
|
||||
auto err = payload.get("error", "Unknown error");
|
||||
throw std::runtime_error(err.asString());
|
||||
}
|
||||
auto config = parseConfig(payload);
|
||||
onConfigUpdate(config);
|
||||
}
|
||||
|
||||
void BarIpcClient::onIpcEvent(const struct Ipc::ipc_response& res) {
|
||||
try {
|
||||
auto payload = parser_.parse(res.payload);
|
||||
if (auto id = payload["id"]; id.isString() && id.asString() != bar_.bar_id) {
|
||||
spdlog::trace("swaybar ipc: ignore event for {}", id.asString());
|
||||
return;
|
||||
}
|
||||
if (payload.isMember("visible_by_modifier")) {
|
||||
// visibility change for hidden bar
|
||||
signal_visible_(payload["visible_by_modifier"].asBool());
|
||||
} else {
|
||||
// configuration update
|
||||
auto config = parseConfig(payload);
|
||||
signal_config_(std::move(config));
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
spdlog::error("BarIpcClient::onEvent {}", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void BarIpcClient::onConfigUpdate(const swaybar_config& config) {
|
||||
spdlog::info("config update for {}: id {}, mode {}, hidden_state {}",
|
||||
bar_.bar_id,
|
||||
config.id,
|
||||
config.mode,
|
||||
config.hidden_state);
|
||||
bar_config_ = config;
|
||||
update();
|
||||
}
|
||||
|
||||
void BarIpcClient::onVisibilityUpdate(bool visible_by_modifier) {
|
||||
spdlog::debug("visiblity update for {}: {}", bar_.bar_id, visible_by_modifier);
|
||||
visible_by_modifier_ = visible_by_modifier;
|
||||
update();
|
||||
}
|
||||
|
||||
void BarIpcClient::update() {
|
||||
bool visible = visible_by_modifier_;
|
||||
if (bar_config_.mode == "invisible") {
|
||||
visible = false;
|
||||
} else if (bar_config_.mode != "hide" || bar_config_.hidden_state != "hide") {
|
||||
visible = true;
|
||||
}
|
||||
bar_.setMode(visible ? bar_config_.mode : Bar::MODE_INVISIBLE);
|
||||
}
|
||||
|
||||
} // namespace waybar::modules::sway
|
@ -1,10 +1,34 @@
|
||||
#include "modules/sway/language.hpp"
|
||||
|
||||
#include <fmt/core.h>
|
||||
#include <json/json.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <xkbcommon/xkbregistry.h>
|
||||
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "modules/sway/ipc/ipc.hpp"
|
||||
#include "util/string.hpp"
|
||||
|
||||
namespace waybar::modules::sway {
|
||||
|
||||
const std::string Language::XKB_LAYOUT_NAMES_KEY = "xkb_layout_names";
|
||||
const std::string Language::XKB_ACTIVE_LAYOUT_NAME_KEY = "xkb_active_layout_name";
|
||||
|
||||
Language::Language(const std::string& id, const Json::Value& config)
|
||||
: ALabel(config, "language", id, "{}", 0, true) {
|
||||
is_variant_displayed = format_.find("{variant}") != std::string::npos;
|
||||
if (format_.find("{}") != std::string::npos || format_.find("{short}") != std::string::npos) {
|
||||
displayed_short_flag |= static_cast<std::byte>(DispayedShortFlag::ShortName);
|
||||
}
|
||||
if (format_.find("{shortDescription}") != std::string::npos) {
|
||||
displayed_short_flag |= static_cast<std::byte>(DispayedShortFlag::ShortDescription);
|
||||
}
|
||||
if (config.isMember("tooltip-format")) {
|
||||
tooltip_format_ = config["tooltip-format"].asString();
|
||||
}
|
||||
ipc_.subscribe(R"(["input"])");
|
||||
ipc_.signal_event.connect(sigc::mem_fun(*this, &Language::onEvent));
|
||||
ipc_.signal_cmd.connect(sigc::mem_fun(*this, &Language::onCmd));
|
||||
@ -21,18 +45,31 @@ Language::Language(const std::string& id, const Json::Value& config)
|
||||
}
|
||||
|
||||
void Language::onCmd(const struct Ipc::ipc_response& res) {
|
||||
if (res.type != static_cast<uint32_t>(IPC_GET_INPUTS)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
auto payload = parser_.parse(res.payload);
|
||||
//Display current layout of a device with a maximum count of layouts, expecting that all will be OK
|
||||
Json::Value::ArrayIndex maxId = 0, max = 0;
|
||||
for(Json::Value::ArrayIndex i = 0; i < payload.size(); i++) {
|
||||
if(payload[i]["xkb_layout_names"].size() > max) {
|
||||
max = payload[i]["xkb_layout_names"].size();
|
||||
maxId = i;
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
auto payload = parser_.parse(res.payload);
|
||||
std::vector<std::string> used_layouts;
|
||||
// Display current layout of a device with a maximum count of layouts, expecting that all will
|
||||
// be OK
|
||||
Json::ArrayIndex max_id = 0, max = 0;
|
||||
for (Json::ArrayIndex i = 0; i < payload.size(); i++) {
|
||||
auto size = payload[i][XKB_LAYOUT_NAMES_KEY].size();
|
||||
if (size > max) {
|
||||
max = size;
|
||||
max_id = i;
|
||||
}
|
||||
}
|
||||
auto layout_name = payload[maxId]["xkb_active_layout_name"].asString().substr(0,2);
|
||||
lang_ = Glib::Markup::escape_text(layout_name);
|
||||
|
||||
for (const auto& layout : payload[max_id][XKB_LAYOUT_NAMES_KEY]) {
|
||||
used_layouts.push_back(layout.asString());
|
||||
}
|
||||
|
||||
init_layouts_map(used_layouts);
|
||||
set_current_layout(payload[max_id][XKB_ACTIVE_LAYOUT_NAME_KEY].asString());
|
||||
dp.emit();
|
||||
} catch (const std::exception& e) {
|
||||
spdlog::error("Language: {}", e.what());
|
||||
@ -40,12 +77,15 @@ void Language::onCmd(const struct Ipc::ipc_response& res) {
|
||||
}
|
||||
|
||||
void Language::onEvent(const struct Ipc::ipc_response& res) {
|
||||
if (res.type != static_cast<uint32_t>(IPC_EVENT_INPUT)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
auto payload = parser_.parse(res.payload)["input"];
|
||||
auto payload = parser_.parse(res.payload)["input"];
|
||||
if (payload["type"].asString() == "keyboard") {
|
||||
auto layout_name = payload["xkb_active_layout_name"].asString().substr(0,2);
|
||||
lang_ = Glib::Markup::escape_text(layout_name);
|
||||
set_current_layout(payload[XKB_ACTIVE_LAYOUT_NAME_KEY].asString());
|
||||
}
|
||||
dp.emit();
|
||||
} catch (const std::exception& e) {
|
||||
@ -54,17 +94,138 @@ void Language::onEvent(const struct Ipc::ipc_response& res) {
|
||||
}
|
||||
|
||||
auto Language::update() -> void {
|
||||
if (lang_.empty()) {
|
||||
event_box_.hide();
|
||||
} else {
|
||||
label_.set_markup(fmt::format(format_, lang_));
|
||||
if (tooltipEnabled()) {
|
||||
label_.set_tooltip_text(lang_);
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
auto display_layout = trim(fmt::format(format_,
|
||||
fmt::arg("short", layout_.short_name),
|
||||
fmt::arg("shortDescription", layout_.short_description),
|
||||
fmt::arg("long", layout_.full_name),
|
||||
fmt::arg("variant", layout_.variant),
|
||||
fmt::arg("flag", layout_.country_flag())));
|
||||
label_.set_markup(display_layout);
|
||||
if (tooltipEnabled()) {
|
||||
if (tooltip_format_ != "") {
|
||||
auto tooltip_display_layout = trim(fmt::format(tooltip_format_,
|
||||
fmt::arg("short", layout_.short_name),
|
||||
fmt::arg("shortDescription", layout_.short_description),
|
||||
fmt::arg("long", layout_.full_name),
|
||||
fmt::arg("variant", layout_.variant),
|
||||
fmt::arg("flag", layout_.country_flag())));
|
||||
label_.set_tooltip_markup(tooltip_display_layout);
|
||||
} else {
|
||||
label_.set_tooltip_markup(display_layout);
|
||||
}
|
||||
event_box_.show();
|
||||
}
|
||||
|
||||
event_box_.show();
|
||||
|
||||
// Call parent update
|
||||
ALabel::update();
|
||||
}
|
||||
|
||||
auto Language::set_current_layout(std::string current_layout) -> void {
|
||||
layout_ = layouts_map_[current_layout];
|
||||
}
|
||||
|
||||
auto Language::init_layouts_map(const std::vector<std::string>& used_layouts) -> void {
|
||||
std::map<std::string, std::vector<Layout*>> found_by_short_names;
|
||||
XKBContext xkb_context;
|
||||
auto layout = xkb_context.next_layout();
|
||||
for (; layout != nullptr; layout = xkb_context.next_layout()) {
|
||||
if (std::find(used_layouts.begin(), used_layouts.end(), layout->full_name) ==
|
||||
used_layouts.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!is_variant_displayed) {
|
||||
auto short_name = layout->short_name;
|
||||
if (found_by_short_names.count(short_name) > 0) {
|
||||
found_by_short_names[short_name].push_back(layout);
|
||||
} else {
|
||||
found_by_short_names[short_name] = {layout};
|
||||
}
|
||||
}
|
||||
|
||||
layouts_map_.emplace(layout->full_name, *layout);
|
||||
}
|
||||
|
||||
if (is_variant_displayed || found_by_short_names.size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::map<std::string, int> short_name_to_number_map;
|
||||
for (const auto& used_layout_name : used_layouts) {
|
||||
auto found = layouts_map_.find(used_layout_name);
|
||||
if (found == layouts_map_.end())
|
||||
continue;
|
||||
auto used_layout = &found->second;
|
||||
auto layouts_with_same_name_list = found_by_short_names[used_layout->short_name];
|
||||
if (layouts_with_same_name_list.size() < 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (short_name_to_number_map.count(used_layout->short_name) == 0) {
|
||||
short_name_to_number_map[used_layout->short_name] = 1;
|
||||
}
|
||||
|
||||
if (displayed_short_flag != static_cast<std::byte>(0)) {
|
||||
int& number = short_name_to_number_map[used_layout->short_name];
|
||||
used_layout->short_name =
|
||||
used_layout->short_name + std::to_string(number);
|
||||
used_layout->short_description =
|
||||
used_layout->short_description + std::to_string(number);
|
||||
++number;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Language::XKBContext::XKBContext() {
|
||||
context_ = rxkb_context_new(RXKB_CONTEXT_NO_DEFAULT_INCLUDES);
|
||||
rxkb_context_include_path_append_default(context_);
|
||||
rxkb_context_parse_default_ruleset(context_);
|
||||
}
|
||||
|
||||
auto Language::XKBContext::next_layout() -> Layout* {
|
||||
if (xkb_layout_ == nullptr) {
|
||||
xkb_layout_ = rxkb_layout_first(context_);
|
||||
} else {
|
||||
xkb_layout_ = rxkb_layout_next(xkb_layout_);
|
||||
}
|
||||
|
||||
if (xkb_layout_ == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto description = std::string(rxkb_layout_get_description(xkb_layout_));
|
||||
auto name = std::string(rxkb_layout_get_name(xkb_layout_));
|
||||
auto variant_ = rxkb_layout_get_variant(xkb_layout_);
|
||||
std::string variant = variant_ == nullptr ? "" : std::string(variant_);
|
||||
auto short_description_ = rxkb_layout_get_brief(xkb_layout_);
|
||||
std::string short_description;
|
||||
if (short_description_ != nullptr) {
|
||||
short_description = std::string(short_description_);
|
||||
base_layouts_by_name_.emplace(name, xkb_layout_);
|
||||
} else {
|
||||
auto base_layout = base_layouts_by_name_[name];
|
||||
short_description = base_layout == nullptr ? "" : std::string(rxkb_layout_get_brief(base_layout));
|
||||
}
|
||||
delete layout_;
|
||||
layout_ = new Layout{description, name, variant, short_description};
|
||||
return layout_;
|
||||
}
|
||||
|
||||
Language::XKBContext::~XKBContext() {
|
||||
rxkb_context_unref(context_);
|
||||
delete layout_;
|
||||
}
|
||||
|
||||
std::string Language::Layout::country_flag() const {
|
||||
if (short_name.size() != 2) return "";
|
||||
unsigned char result[] = "\xf0\x9f\x87\x00\xf0\x9f\x87\x00";
|
||||
result[3] = short_name[0] + 0x45;
|
||||
result[7] = short_name[1] + 0x45;
|
||||
// Check if both emojis are in A-Z symbol bounds
|
||||
if (result[3] < 0xa6 || result[3] > 0xbf) return "";
|
||||
if (result[7] < 0xa6 || result[7] > 0xbf) return "";
|
||||
return std::string{reinterpret_cast<char*>(result)};
|
||||
}
|
||||
} // namespace waybar::modules::sway
|
||||
|
@ -1,10 +1,20 @@
|
||||
#include "modules/sway/window.hpp"
|
||||
|
||||
#include <gdkmm/pixbuf.h>
|
||||
#include <glibmm/fileutils.h>
|
||||
#include <glibmm/keyfile.h>
|
||||
#include <glibmm/miscutils.h>
|
||||
#include <gtkmm/enums.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <regex>
|
||||
#include <string>
|
||||
|
||||
namespace waybar::modules::sway {
|
||||
|
||||
Window::Window(const std::string& id, const Bar& bar, const Json::Value& config)
|
||||
: ALabel(config, "window", id, "{}", 0, true), bar_(bar), windowId_(-1) {
|
||||
: AIconLabel(config, "window", id, "{}", 0, true), bar_(bar), windowId_(-1) {
|
||||
ipc_.subscribe(R"(["window","workspace"])");
|
||||
ipc_.signal_event.connect(sigc::mem_fun(*this, &Window::onEvent));
|
||||
ipc_.signal_cmd.connect(sigc::mem_fun(*this, &Window::onCmd));
|
||||
@ -28,12 +38,60 @@ void Window::onCmd(const struct Ipc::ipc_response& res) {
|
||||
auto payload = parser_.parse(res.payload);
|
||||
auto output = payload["output"].isString() ? payload["output"].asString() : "";
|
||||
std::tie(app_nb_, windowId_, window_, app_id_) = getFocusedNode(payload["nodes"], output);
|
||||
updateAppIcon();
|
||||
dp.emit();
|
||||
} catch (const std::exception& e) {
|
||||
spdlog::error("Window: {}", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<std::string> getDesktopFilePath(const std::string& app_id) {
|
||||
const auto data_dirs = Glib::get_system_data_dirs();
|
||||
for (const auto& data_dir : data_dirs) {
|
||||
const auto desktop_file_path = data_dir + "applications/" + app_id + ".desktop";
|
||||
if (std::filesystem::exists(desktop_file_path)) {
|
||||
return desktop_file_path;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::optional<Glib::ustring> getIconName(const std::string& app_id) {
|
||||
const auto desktop_file_path = getDesktopFilePath(app_id);
|
||||
if (!desktop_file_path.has_value()) {
|
||||
return {};
|
||||
}
|
||||
try {
|
||||
Glib::KeyFile desktop_file;
|
||||
desktop_file.load_from_file(desktop_file_path.value());
|
||||
const auto icon_name = desktop_file.get_string("Desktop Entry", "Icon");
|
||||
if (icon_name.empty()) {
|
||||
return {};
|
||||
}
|
||||
return icon_name;
|
||||
} catch (Glib::FileError& error) {
|
||||
spdlog::warn(
|
||||
"Error while loading desktop file {}: {}", desktop_file_path.value(), error.what().c_str());
|
||||
} catch (Glib::KeyFileError& error) {
|
||||
spdlog::warn(
|
||||
"Error while loading desktop file {}: {}", desktop_file_path.value(), error.what().c_str());
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void Window::updateAppIcon() {
|
||||
if (!iconEnabled()) {
|
||||
return;
|
||||
}
|
||||
const auto icon_name = getIconName(app_id_);
|
||||
if (icon_name.has_value()) {
|
||||
image_.set_from_icon_name(icon_name.value(), Gtk::ICON_SIZE_LARGE_TOOLBAR);
|
||||
image_.set_visible(true);
|
||||
return;
|
||||
}
|
||||
image_.set_visible(false);
|
||||
}
|
||||
|
||||
auto Window::update() -> void {
|
||||
if (!old_app_id_.empty()) {
|
||||
bar_.window.get_style_context()->remove_class(old_app_id_);
|
||||
@ -56,26 +114,33 @@ auto Window::update() -> void {
|
||||
bar_.window.get_style_context()->remove_class("solo");
|
||||
bar_.window.get_style_context()->remove_class("empty");
|
||||
}
|
||||
label_.set_markup(fmt::format(format_, fmt::arg("title", window_),
|
||||
label_.set_markup(fmt::format(format_, fmt::arg("title", rewriteTitle(window_)),
|
||||
fmt::arg("app_id", app_id_)));
|
||||
if (tooltipEnabled()) {
|
||||
label_.set_tooltip_text(window_);
|
||||
}
|
||||
// Call parent update
|
||||
ALabel::update();
|
||||
AIconLabel::update();
|
||||
}
|
||||
|
||||
int leafNodesInWorkspace(const Json::Value& node) {
|
||||
auto const& nodes = node["nodes"];
|
||||
if(nodes.empty()) {
|
||||
auto const& floating_nodes = node["floating_nodes"];
|
||||
if(nodes.empty() && floating_nodes.empty()) {
|
||||
if(node["type"] == "workspace")
|
||||
return 0;
|
||||
else
|
||||
return 1;
|
||||
}
|
||||
int sum = 0;
|
||||
for(auto const& node : nodes)
|
||||
sum += leafNodesInWorkspace(node);
|
||||
if (!nodes.empty()) {
|
||||
for(auto const& node : nodes)
|
||||
sum += leafNodesInWorkspace(node);
|
||||
}
|
||||
if (!floating_nodes.empty()) {
|
||||
for(auto const& node : floating_nodes)
|
||||
sum += leafNodesInWorkspace(node);
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
@ -131,4 +196,30 @@ void Window::getTree() {
|
||||
}
|
||||
}
|
||||
|
||||
std::string Window::rewriteTitle(const std::string& title) {
|
||||
const auto& rules = config_["rewrite"];
|
||||
if (!rules.isObject()) {
|
||||
return title;
|
||||
}
|
||||
|
||||
std::string res = title;
|
||||
|
||||
for (auto it = rules.begin(); it != rules.end(); ++it) {
|
||||
if (it.key().isString() && it->isString()) {
|
||||
try {
|
||||
// malformated regexes will cause an exception.
|
||||
// in this case, log error and try the next rule.
|
||||
const std::regex rule{it.key().asString()};
|
||||
if (std::regex_match(title, rule)) {
|
||||
res = std::regex_replace(res, rule, it->asString());
|
||||
}
|
||||
} catch (const std::regex_error& e) {
|
||||
spdlog::error("Invalid rule {}: {}", it.key().asString(), e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
} // namespace waybar::modules::sway
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <string>
|
||||
|
||||
@ -98,6 +99,7 @@ void Workspaces::onCmd(const struct Ipc::ipc_response &res) {
|
||||
Json::Value v;
|
||||
v["name"] = p_w_name;
|
||||
v["target_output"] = bar_.output->name;
|
||||
v["num"] = convertWorkspaceNameToNum(p_w_name);
|
||||
workspaces_.emplace_back(std::move(v));
|
||||
break;
|
||||
}
|
||||
@ -107,57 +109,59 @@ void Workspaces::onCmd(const struct Ipc::ipc_response &res) {
|
||||
Json::Value v;
|
||||
v["name"] = p_w_name;
|
||||
v["target_output"] = "";
|
||||
v["num"] = convertWorkspaceNameToNum(p_w_name);
|
||||
workspaces_.emplace_back(std::move(v));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// config option to sort numeric workspace names before others
|
||||
bool config_numeric_first = config_["numeric-first"].asBool();
|
||||
|
||||
// sway has a defined ordering of workspaces that should be preserved in
|
||||
// the representation displayed by waybar to ensure that commands such
|
||||
// as "workspace prev" or "workspace next" make sense when looking at
|
||||
// the workspace representation in the bar.
|
||||
// Due to waybar's own feature of persistent workspaces unknown to sway,
|
||||
// custom sorting logic is necessary to make these workspaces appear
|
||||
// naturally in the list of workspaces without messing up sway's
|
||||
// sorting. For this purpose, a custom numbering property is created
|
||||
// that preserves the order provided by sway while inserting numbered
|
||||
// persistent workspaces at their natural positions.
|
||||
//
|
||||
// All of this code assumes that sway provides numbered workspaces first
|
||||
// and other workspaces are sorted by their creation time.
|
||||
//
|
||||
// In a first pass, the maximum "num" value is computed to enqueue
|
||||
// unnumbered workspaces behind numbered ones when computing the sort
|
||||
// attribute.
|
||||
int max_num = -1;
|
||||
for (auto & workspace : workspaces_) {
|
||||
max_num = std::max(workspace["num"].asInt(), max_num);
|
||||
}
|
||||
for (auto & workspace : workspaces_) {
|
||||
auto workspace_num = workspace["num"].asInt();
|
||||
if (workspace_num > -1) {
|
||||
workspace["sort"] = workspace_num;
|
||||
} else {
|
||||
workspace["sort"] = ++max_num;
|
||||
}
|
||||
}
|
||||
std::sort(workspaces_.begin(),
|
||||
workspaces_.end(),
|
||||
[config_numeric_first](const Json::Value &lhs, const Json::Value &rhs) {
|
||||
// the "num" property (integer type):
|
||||
// The workspace number or -1 for workspaces that do
|
||||
// not start with a number.
|
||||
// We could rely on sway providing this property:
|
||||
//
|
||||
// auto l = lhs["num"].asInt();
|
||||
// auto r = rhs["num"].asInt();
|
||||
//
|
||||
// We cannot rely on the "num" property as provided by sway
|
||||
// via IPC, because persistent workspace might not exist in
|
||||
// sway's view. However, we need this property also for
|
||||
// not-yet created persistent workspace. As such, we simply
|
||||
// duplicate sway's logic of assigning the "num" property
|
||||
// into waybar (see convertWorkspaceNameToNum). This way the
|
||||
// sorting should work out even when we include workspaces
|
||||
// that do not currently exist.
|
||||
[](const Json::Value &lhs, const Json::Value &rhs) {
|
||||
auto lname = lhs["name"].asString();
|
||||
auto rname = rhs["name"].asString();
|
||||
int l = convertWorkspaceNameToNum(lname);
|
||||
int r = convertWorkspaceNameToNum(rname);
|
||||
int l = lhs["sort"].asInt();
|
||||
int r = rhs["sort"].asInt();
|
||||
|
||||
if (l == r) {
|
||||
// in case both integers are the same, lexicographical
|
||||
// sort. This also covers the case when both don't have a
|
||||
// number (i.e., l == r == -1).
|
||||
// In case both integers are the same, lexicographical
|
||||
// sort. The code above already ensure that this will only
|
||||
// happend in case of explicitly numbered workspaces.
|
||||
return lname < rname;
|
||||
}
|
||||
|
||||
// one of the workspaces doesn't begin with a number, so
|
||||
// num is -1.
|
||||
if (l < 0 || r < 0) {
|
||||
if (config_numeric_first) {
|
||||
return r < 0;
|
||||
}
|
||||
return l < 0;
|
||||
}
|
||||
|
||||
// both workspaces have a "num" so let's just compare those
|
||||
return l < r;
|
||||
});
|
||||
|
||||
}
|
||||
dp.emit();
|
||||
} catch (const std::exception &e) {
|
||||
|
@ -4,6 +4,7 @@
|
||||
#include "glibmm/fileutils.h"
|
||||
#include "glibmm/refptr.h"
|
||||
#include "util/format.hpp"
|
||||
#include "util/string.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
@ -11,7 +12,9 @@
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <utility>
|
||||
|
||||
#include <fmt/core.h>
|
||||
#include <gdkmm/monitor.h>
|
||||
|
||||
#include <gtkmm/icontheme.h>
|
||||
@ -24,27 +27,6 @@
|
||||
|
||||
namespace waybar::modules::wlr {
|
||||
|
||||
/* String manipulation methods */
|
||||
const std::string WHITESPACE = " \n\r\t\f\v";
|
||||
|
||||
static std::string ltrim(const std::string& s)
|
||||
{
|
||||
size_t start = s.find_first_not_of(WHITESPACE);
|
||||
return (start == std::string::npos) ? "" : s.substr(start);
|
||||
}
|
||||
|
||||
static std::string rtrim(const std::string& s)
|
||||
{
|
||||
size_t end = s.find_last_not_of(WHITESPACE);
|
||||
return (end == std::string::npos) ? "" : s.substr(0, end + 1);
|
||||
}
|
||||
|
||||
static std::string trim(const std::string& s)
|
||||
{
|
||||
return rtrim(ltrim(s));
|
||||
}
|
||||
|
||||
|
||||
/* Icon loading functions */
|
||||
static std::vector<std::string> search_prefix()
|
||||
{
|
||||
@ -86,8 +68,7 @@ static Glib::RefPtr<Gdk::Pixbuf> load_icon_from_file(std::string icon_path, int
|
||||
}
|
||||
}
|
||||
|
||||
/* Method 1 - get the correct icon name from the desktop file */
|
||||
static std::string get_from_desktop_app_info(const std::string &app_id)
|
||||
static Glib::RefPtr<Gio::DesktopAppInfo> get_app_info_by_name(const std::string& app_id)
|
||||
{
|
||||
static std::vector<std::string> prefixes = search_prefix();
|
||||
|
||||
@ -103,33 +84,29 @@ static std::string get_from_desktop_app_info(const std::string &app_id)
|
||||
".desktop"
|
||||
};
|
||||
|
||||
Glib::RefPtr<Gio::DesktopAppInfo> app_info;
|
||||
for (auto& prefix : prefixes) {
|
||||
for (auto& folder : app_folders) {
|
||||
for (auto& suffix : suffixes) {
|
||||
auto app_info_ = Gio::DesktopAppInfo::create_from_filename(prefix + folder + app_id + suffix);
|
||||
if (!app_info_) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (auto& prefix : prefixes)
|
||||
for (auto& folder : app_folders)
|
||||
for (auto& suffix : suffixes)
|
||||
if (!app_info)
|
||||
app_info = Gio::DesktopAppInfo::create_from_filename(prefix + folder + app_id + suffix);
|
||||
return app_info_;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (app_info && app_info->get_icon())
|
||||
return app_info->get_icon()->to_string();
|
||||
|
||||
return "";
|
||||
return {};
|
||||
}
|
||||
|
||||
/* Method 2 - use the app_id and check whether there is an icon with this name in the icon theme */
|
||||
static std::string get_from_icon_theme(const Glib::RefPtr<Gtk::IconTheme>& icon_theme,
|
||||
const std::string &app_id)
|
||||
Glib::RefPtr<Gio::DesktopAppInfo> get_desktop_app_info(const std::string &app_id)
|
||||
{
|
||||
if (icon_theme->lookup_icon(app_id, 24))
|
||||
return app_id;
|
||||
auto app_info = get_app_info_by_name(app_id);
|
||||
if (app_info) {
|
||||
return app_info;
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
/* Method 3 - as last resort perform a search for most appropriate desktop info file */
|
||||
static std::string get_from_desktop_app_info_search(const std::string &app_id)
|
||||
{
|
||||
std::string desktop_file = "";
|
||||
|
||||
gchar*** desktop_list = g_desktop_app_info_search(app_id.c_str());
|
||||
@ -151,65 +128,84 @@ static std::string get_from_desktop_app_info_search(const std::string &app_id)
|
||||
}
|
||||
g_free(desktop_list);
|
||||
|
||||
return get_from_desktop_app_info(desktop_file);
|
||||
return get_app_info_by_name(desktop_file);
|
||||
}
|
||||
|
||||
static bool image_load_icon(Gtk::Image& image, const Glib::RefPtr<Gtk::IconTheme>& icon_theme,
|
||||
const std::string &app_id_list, int size)
|
||||
{
|
||||
void Task::set_app_info_from_app_id_list(const std::string& app_id_list) {
|
||||
std::string app_id;
|
||||
std::istringstream stream(app_id_list);
|
||||
bool found = false;
|
||||
|
||||
/* Wayfire sends a list of app-id's in space separated format, other compositors
|
||||
* send a single app-id, but in any case this works fine */
|
||||
while (stream >> app_id)
|
||||
{
|
||||
app_info_ = get_desktop_app_info(app_id);
|
||||
if (app_info_) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lower_app_id = app_id;
|
||||
std::transform(lower_app_id.begin(), lower_app_id.end(), lower_app_id.begin(),
|
||||
[](char c){ return std::tolower(c); });
|
||||
app_info_ = get_desktop_app_info(lower_app_id);
|
||||
if (app_info_) {
|
||||
return;
|
||||
}
|
||||
|
||||
size_t start = 0, end = app_id.size();
|
||||
start = app_id.rfind(".", end);
|
||||
std::string app_name = app_id.substr(start+1, app_id.size());
|
||||
app_info_ = get_desktop_app_info(app_name);
|
||||
if (app_info_) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lower_app_id = app_id;
|
||||
std::transform(lower_app_id.begin(), lower_app_id.end(), lower_app_id.begin(),
|
||||
[](char c){ return std::tolower(c); });
|
||||
start = app_id.find("-");
|
||||
app_name = app_id.substr(0, start);
|
||||
app_info_ = get_desktop_app_info(app_name);
|
||||
}
|
||||
}
|
||||
|
||||
std::string icon_name = get_from_icon_theme(icon_theme, app_id);
|
||||
static std::string get_icon_name_from_icon_theme(const Glib::RefPtr<Gtk::IconTheme>& icon_theme,
|
||||
const std::string &app_id)
|
||||
{
|
||||
if (icon_theme->lookup_icon(app_id, 24))
|
||||
return app_id;
|
||||
|
||||
if (icon_name.empty())
|
||||
icon_name = get_from_icon_theme(icon_theme, lower_app_id);
|
||||
if (icon_name.empty())
|
||||
icon_name = get_from_icon_theme(icon_theme, app_name);
|
||||
if (icon_name.empty())
|
||||
icon_name = get_from_desktop_app_info(app_id);
|
||||
if (icon_name.empty())
|
||||
icon_name = get_from_desktop_app_info(lower_app_id);
|
||||
if (icon_name.empty())
|
||||
icon_name = get_from_desktop_app_info(app_name);
|
||||
if (icon_name.empty())
|
||||
icon_name = get_from_desktop_app_info_search(app_id);
|
||||
return "";
|
||||
}
|
||||
|
||||
if (icon_name.empty())
|
||||
icon_name = "unknown";
|
||||
bool Task::image_load_icon(Gtk::Image& image, const Glib::RefPtr<Gtk::IconTheme>& icon_theme, Glib::RefPtr<Gio::DesktopAppInfo> app_info, int size)
|
||||
{
|
||||
std::string ret_icon_name = "unknown";
|
||||
if (app_info) {
|
||||
std::string icon_name = get_icon_name_from_icon_theme(icon_theme, app_info->get_startup_wm_class());
|
||||
if (!icon_name.empty()) {
|
||||
ret_icon_name = icon_name;
|
||||
} else {
|
||||
if (app_info->get_icon()) {
|
||||
ret_icon_name = app_info->get_icon()->to_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Glib::RefPtr<Gdk::Pixbuf> pixbuf;
|
||||
Glib::RefPtr<Gdk::Pixbuf> pixbuf;
|
||||
|
||||
try {
|
||||
pixbuf = icon_theme->load_icon(icon_name, size, Gtk::ICON_LOOKUP_FORCE_SIZE);
|
||||
} catch(...) {
|
||||
if (Glib::file_test(icon_name, Glib::FILE_TEST_EXISTS))
|
||||
pixbuf = load_icon_from_file(icon_name, size);
|
||||
else
|
||||
pixbuf = {};
|
||||
}
|
||||
try {
|
||||
pixbuf = icon_theme->load_icon(ret_icon_name, size, Gtk::ICON_LOOKUP_FORCE_SIZE);
|
||||
} catch(...) {
|
||||
if (Glib::file_test(ret_icon_name, Glib::FILE_TEST_EXISTS))
|
||||
pixbuf = load_icon_from_file(ret_icon_name, size);
|
||||
else
|
||||
pixbuf = {};
|
||||
}
|
||||
|
||||
if (pixbuf) {
|
||||
image.set(pixbuf);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (pixbuf) {
|
||||
image.set(pixbuf);
|
||||
return true;
|
||||
}
|
||||
|
||||
return found;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Task class implementation */
|
||||
@ -276,8 +272,7 @@ Task::Task(const waybar::Bar &bar, const Json::Value &config, Taskbar *tbar,
|
||||
struct zwlr_foreign_toplevel_handle_v1 *tl_handle, struct wl_seat *seat) :
|
||||
bar_{bar}, config_{config}, tbar_{tbar}, handle_{tl_handle}, seat_{seat},
|
||||
id_{global_id++},
|
||||
content_{bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0},
|
||||
button_visible_{false}
|
||||
content_{bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0}
|
||||
{
|
||||
zwlr_foreign_toplevel_handle_v1_add_listener(handle_, &toplevel_handle_impl, this);
|
||||
|
||||
@ -290,13 +285,15 @@ Task::Task(const waybar::Bar &bar, const Json::Value &config, Taskbar *tbar,
|
||||
content_.show();
|
||||
button_.add(content_);
|
||||
|
||||
with_icon_ = false;
|
||||
format_before_.clear();
|
||||
format_after_.clear();
|
||||
|
||||
if (config_["format"].isString()) {
|
||||
/* The user defined a format string, use it */
|
||||
auto format = config_["format"].asString();
|
||||
if (format.find("{name}") != std::string::npos) {
|
||||
with_name_ = true;
|
||||
}
|
||||
|
||||
auto icon_pos = format.find("{icon}");
|
||||
if (icon_pos == 0) {
|
||||
@ -377,19 +374,54 @@ std::string Task::state_string(bool shortened) const
|
||||
void Task::handle_title(const char *title)
|
||||
{
|
||||
title_ = title;
|
||||
hide_if_ignored();
|
||||
}
|
||||
|
||||
void Task::hide_if_ignored()
|
||||
{
|
||||
if (tbar_->ignore_list().count(app_id_) || tbar_->ignore_list().count(title_)) {
|
||||
ignored_ = true;
|
||||
if (button_visible_) {
|
||||
auto output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj());
|
||||
handle_output_leave(output);
|
||||
}
|
||||
} else {
|
||||
bool is_was_ignored = ignored_;
|
||||
ignored_ = false;
|
||||
if (is_was_ignored) {
|
||||
auto output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj());
|
||||
handle_output_enter(output);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Task::handle_app_id(const char *app_id)
|
||||
{
|
||||
app_id_ = app_id;
|
||||
hide_if_ignored();
|
||||
|
||||
if (!with_icon_)
|
||||
auto ids_replace_map = tbar_->app_ids_replace_map();
|
||||
if (ids_replace_map.count(app_id_)) {
|
||||
auto replaced_id = ids_replace_map[app_id_];
|
||||
spdlog::debug(fmt::format("Task ({}) [{}] app_id was replaced with {}", id_, app_id_, replaced_id));
|
||||
app_id_ = replaced_id;
|
||||
}
|
||||
|
||||
if (!with_icon_ && !with_name_) {
|
||||
return;
|
||||
}
|
||||
|
||||
set_app_info_from_app_id_list(app_id_);
|
||||
name_ = app_info_ ? app_info_->get_display_name() : app_id;
|
||||
|
||||
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_id_, icon_size)) {
|
||||
if (image_load_icon(icon_, icon_theme, app_info_, icon_size)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
@ -403,6 +435,11 @@ void Task::handle_app_id(const char *app_id)
|
||||
|
||||
void Task::handle_output_enter(struct wl_output *output)
|
||||
{
|
||||
if (ignored_) {
|
||||
spdlog::debug("{} is ignored", repr());
|
||||
return;
|
||||
}
|
||||
|
||||
spdlog::debug("{} entered output {}", repr(), (void*)output);
|
||||
|
||||
if (!button_visible_ && (tbar_->all_outputs() || tbar_->show_output(output))) {
|
||||
@ -540,14 +577,17 @@ void Task::update()
|
||||
{
|
||||
bool markup = config_["markup"].isBool() ? config_["markup"].asBool() : false;
|
||||
std::string title = title_;
|
||||
std::string name = name_;
|
||||
std::string app_id = app_id_;
|
||||
if (markup) {
|
||||
title = Glib::Markup::escape_text(title);
|
||||
name = Glib::Markup::escape_text(name);
|
||||
app_id = Glib::Markup::escape_text(app_id);
|
||||
}
|
||||
if (!format_before_.empty()) {
|
||||
auto txt = fmt::format(format_before_,
|
||||
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))
|
||||
@ -561,6 +601,7 @@ void Task::update()
|
||||
if (!format_after_.empty()) {
|
||||
auto txt = fmt::format(format_after_,
|
||||
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))
|
||||
@ -575,6 +616,7 @@ void Task::update()
|
||||
if (!format_tooltip_.empty()) {
|
||||
auto txt = fmt::format(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))
|
||||
@ -694,6 +736,23 @@ Taskbar::Taskbar(const std::string &id, const waybar::Bar &bar, const Json::Valu
|
||||
|
||||
icon_themes_.push_back(it);
|
||||
}
|
||||
|
||||
// Load ignore-list
|
||||
if (config_["ignore-list"].isArray()) {
|
||||
for (auto& app_name : config_["ignore-list"]) {
|
||||
ignore_list_.emplace(app_name.asString());
|
||||
}
|
||||
}
|
||||
|
||||
// Load app_id remappings
|
||||
if (config_["app_ids-mapping"].isObject()) {
|
||||
const Json::Value& mapping = config_["app_ids-mapping"];
|
||||
const std::vector<std::string> app_ids = config_["app_ids-mapping"].getMemberNames();
|
||||
for (auto& app_id : app_ids) {
|
||||
app_ids_replace_map_.emplace(app_id, mapping[app_id].asString());
|
||||
}
|
||||
}
|
||||
|
||||
icon_themes_.push_back(Gtk::IconTheme::get_default());
|
||||
}
|
||||
|
||||
@ -825,9 +884,10 @@ bool Taskbar::all_outputs() const
|
||||
return config_["all-outputs"].isBool() && config_["all-outputs"].asBool();
|
||||
}
|
||||
|
||||
std::vector<Glib::RefPtr<Gtk::IconTheme>> Taskbar::icon_themes() const
|
||||
{
|
||||
return icon_themes_;
|
||||
}
|
||||
const std::vector<Glib::RefPtr<Gtk::IconTheme>>& Taskbar::icon_themes() const { return icon_themes_; }
|
||||
|
||||
const std::unordered_set<std::string>& Taskbar::ignore_list() const { return ignore_list_; }
|
||||
|
||||
const std::map<std::string, std::string>& Taskbar::app_ids_replace_map() const { return app_ids_replace_map_; }
|
||||
|
||||
} /* namespace waybar::modules::wlr */
|
||||
|
472
src/modules/wlr/workspace_manager.cpp
Normal file
472
src/modules/wlr/workspace_manager.cpp
Normal file
@ -0,0 +1,472 @@
|
||||
#include "modules/wlr/workspace_manager.hpp"
|
||||
|
||||
#include <gdk/gdkwayland.h>
|
||||
#include <gtkmm.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <vector>
|
||||
|
||||
#include "gtkmm/widget.h"
|
||||
#include "modules/wlr/workspace_manager_binding.hpp"
|
||||
|
||||
namespace waybar::modules::wlr {
|
||||
|
||||
uint32_t WorkspaceGroup::workspace_global_id = 0;
|
||||
uint32_t WorkspaceManager::group_global_id = 0;
|
||||
std::map<std::string, std::string> Workspace::icons_map_;
|
||||
|
||||
WorkspaceManager::WorkspaceManager(const std::string &id, const waybar::Bar &bar,
|
||||
const Json::Value &config)
|
||||
: waybar::AModule(config, "workspaces", id, false, false),
|
||||
bar_(bar),
|
||||
box_(bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0) {
|
||||
auto config_sort_by_name = config_["sort-by-name"];
|
||||
if (config_sort_by_name.isBool()) {
|
||||
sort_by_name_ = config_sort_by_name.asBool();
|
||||
}
|
||||
|
||||
auto config_sort_by_coordinates = config_["sort-by-coordinates"];
|
||||
if (config_sort_by_coordinates.isBool()) {
|
||||
sort_by_coordinates_ = config_sort_by_coordinates.asBool();
|
||||
}
|
||||
|
||||
auto config_all_outputs = config_["all-outputs"];
|
||||
if (config_all_outputs.isBool()) {
|
||||
all_outputs_ = config_all_outputs.asBool();
|
||||
}
|
||||
|
||||
auto config_active_only = config_["active-only"];
|
||||
if (config_active_only.isBool()) {
|
||||
active_only_ = config_active_only.asBool();
|
||||
creation_delayed_ = active_only_;
|
||||
}
|
||||
|
||||
box_.set_name("workspaces");
|
||||
if (!id.empty()) {
|
||||
box_.get_style_context()->add_class(id);
|
||||
}
|
||||
event_box_.add(box_);
|
||||
|
||||
add_registry_listener(this);
|
||||
if (!workspace_manager_) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto WorkspaceManager::workspace_comparator() const
|
||||
-> std::function<bool(std::unique_ptr<Workspace> &, std::unique_ptr<Workspace> &)> {
|
||||
return [=](std::unique_ptr<Workspace> &lhs, std::unique_ptr<Workspace> &rhs) {
|
||||
auto is_name_less = lhs->get_name() < rhs->get_name();
|
||||
auto is_name_eq = lhs->get_name() == rhs->get_name();
|
||||
auto is_coords_less = lhs->get_coords() < rhs->get_coords();
|
||||
if (sort_by_name_) {
|
||||
if (sort_by_coordinates_) {
|
||||
return is_name_eq ? is_coords_less : is_name_less;
|
||||
} else {
|
||||
return is_name_less;
|
||||
}
|
||||
}
|
||||
|
||||
if (sort_by_coordinates_) {
|
||||
return is_coords_less;
|
||||
}
|
||||
|
||||
return lhs->id() < rhs->id();
|
||||
};
|
||||
}
|
||||
|
||||
auto WorkspaceManager::sort_workspaces() -> void {
|
||||
std::vector<std::reference_wrapper<std::unique_ptr<Workspace>>> all_workspaces;
|
||||
for (auto &group : groups_) {
|
||||
auto &group_workspaces = group->workspaces();
|
||||
all_workspaces.reserve(all_workspaces.size() +
|
||||
std::distance(group_workspaces.begin(), group_workspaces.end()));
|
||||
if (!active_only()) {
|
||||
all_workspaces.insert(all_workspaces.end(), group_workspaces.begin(), group_workspaces.end());
|
||||
continue;
|
||||
}
|
||||
|
||||
for (auto &workspace : group_workspaces) {
|
||||
if (!workspace->is_active()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
all_workspaces.push_back(workspace);
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(all_workspaces.begin(), all_workspaces.end(), workspace_comparator());
|
||||
for (size_t i = 0; i < all_workspaces.size(); ++i) {
|
||||
box_.reorder_child(all_workspaces[i].get()->get_button_ref(), i);
|
||||
}
|
||||
}
|
||||
|
||||
auto WorkspaceManager::register_manager(wl_registry *registry, uint32_t name, uint32_t version)
|
||||
-> void {
|
||||
if (workspace_manager_) {
|
||||
spdlog::warn("Register workspace manager again although already registered!");
|
||||
return;
|
||||
}
|
||||
if (version != 1) {
|
||||
spdlog::warn("Using different workspace manager protocol version: {}", version);
|
||||
}
|
||||
workspace_manager_ = workspace_manager_bind(registry, name, version, this);
|
||||
}
|
||||
|
||||
auto WorkspaceManager::handle_workspace_group_create(
|
||||
zext_workspace_group_handle_v1 *workspace_group_handle) -> void {
|
||||
auto new_id = ++group_global_id;
|
||||
groups_.push_back(
|
||||
std::make_unique<WorkspaceGroup>(bar_, box_, config_, *this, workspace_group_handle, new_id));
|
||||
spdlog::debug("Workspace group {} created", new_id);
|
||||
}
|
||||
|
||||
auto WorkspaceManager::handle_finished() -> void {
|
||||
zext_workspace_manager_v1_destroy(workspace_manager_);
|
||||
workspace_manager_ = nullptr;
|
||||
}
|
||||
|
||||
auto WorkspaceManager::handle_done() -> void {
|
||||
for (auto &group : groups_) {
|
||||
group->handle_done();
|
||||
}
|
||||
dp.emit();
|
||||
}
|
||||
|
||||
auto WorkspaceManager::update() -> void {
|
||||
for (auto &group : groups_) {
|
||||
group->update();
|
||||
}
|
||||
if (creation_delayed()) {
|
||||
creation_delayed_ = false;
|
||||
sort_workspaces();
|
||||
}
|
||||
AModule::update();
|
||||
}
|
||||
|
||||
WorkspaceManager::~WorkspaceManager() {
|
||||
if (!workspace_manager_) {
|
||||
return;
|
||||
}
|
||||
|
||||
zext_workspace_manager_v1_destroy(workspace_manager_);
|
||||
workspace_manager_ = nullptr;
|
||||
}
|
||||
|
||||
auto WorkspaceManager::remove_workspace_group(uint32_t id) -> void {
|
||||
auto it = std::find_if(groups_.begin(),
|
||||
groups_.end(),
|
||||
[id](const std::unique_ptr<WorkspaceGroup> &g) { return g->id() == id; });
|
||||
|
||||
if (it == groups_.end()) {
|
||||
spdlog::warn("Can't find group with id {}", id);
|
||||
return;
|
||||
}
|
||||
|
||||
groups_.erase(it);
|
||||
}
|
||||
auto WorkspaceManager::commit() -> void { zext_workspace_manager_v1_commit(workspace_manager_); }
|
||||
|
||||
WorkspaceGroup::WorkspaceGroup(const Bar &bar, Gtk::Box &box, const Json::Value &config,
|
||||
WorkspaceManager &manager,
|
||||
zext_workspace_group_handle_v1 *workspace_group_handle, uint32_t id)
|
||||
: bar_(bar),
|
||||
box_(box),
|
||||
config_(config),
|
||||
workspace_manager_(manager),
|
||||
workspace_group_handle_(workspace_group_handle),
|
||||
id_(id) {
|
||||
add_workspace_group_listener(workspace_group_handle, this);
|
||||
}
|
||||
|
||||
auto WorkspaceGroup::active_only() const -> bool { return workspace_manager_.active_only(); }
|
||||
auto WorkspaceGroup::creation_delayed() const -> bool {
|
||||
return workspace_manager_.creation_delayed();
|
||||
}
|
||||
|
||||
auto WorkspaceGroup::add_button(Gtk::Button &button) -> void {
|
||||
box_.pack_start(button, false, false);
|
||||
}
|
||||
|
||||
WorkspaceGroup::~WorkspaceGroup() {
|
||||
if (!workspace_group_handle_) {
|
||||
return;
|
||||
}
|
||||
|
||||
zext_workspace_group_handle_v1_destroy(workspace_group_handle_);
|
||||
workspace_group_handle_ = nullptr;
|
||||
}
|
||||
|
||||
auto WorkspaceGroup::handle_workspace_create(zext_workspace_handle_v1 *workspace) -> void {
|
||||
auto new_id = ++workspace_global_id;
|
||||
workspaces_.push_back(std::make_unique<Workspace>(bar_, config_, *this, workspace, new_id));
|
||||
spdlog::debug("Workspace {} created", new_id);
|
||||
}
|
||||
|
||||
auto WorkspaceGroup::handle_remove() -> void {
|
||||
zext_workspace_group_handle_v1_destroy(workspace_group_handle_);
|
||||
workspace_group_handle_ = nullptr;
|
||||
workspace_manager_.remove_workspace_group(id_);
|
||||
}
|
||||
|
||||
auto WorkspaceGroup::handle_output_enter(wl_output *output) -> void {
|
||||
spdlog::debug("Output {} assigned to {} group", (void *)output, id_);
|
||||
output_ = output;
|
||||
|
||||
if (!is_visible() || workspace_manager_.creation_delayed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto &workspace : workspaces_) {
|
||||
add_button(workspace->get_button_ref());
|
||||
}
|
||||
}
|
||||
|
||||
auto WorkspaceGroup::is_visible() const -> bool {
|
||||
return output_ != nullptr &&
|
||||
(workspace_manager_.all_outputs() ||
|
||||
output_ == gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()));
|
||||
}
|
||||
|
||||
auto WorkspaceGroup::handle_output_leave() -> void {
|
||||
spdlog::debug("Output {} remove from {} group", (void *)output_, id_);
|
||||
output_ = nullptr;
|
||||
|
||||
if (output_ != gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj())) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto &workspace : workspaces_) {
|
||||
remove_button(workspace->get_button_ref());
|
||||
}
|
||||
}
|
||||
|
||||
auto WorkspaceGroup::update() -> void {
|
||||
for (auto &workspace : workspaces_) {
|
||||
if (workspace_manager_.creation_delayed()) {
|
||||
add_button(workspace->get_button_ref());
|
||||
if (is_visible() && (workspace->is_active() || workspace->is_urgent())) {
|
||||
workspace->show();
|
||||
}
|
||||
}
|
||||
|
||||
workspace->update();
|
||||
}
|
||||
}
|
||||
|
||||
auto WorkspaceGroup::remove_workspace(uint32_t id) -> void {
|
||||
auto it = std::find_if(workspaces_.begin(),
|
||||
workspaces_.end(),
|
||||
[id](const std::unique_ptr<Workspace> &w) { return w->id() == id; });
|
||||
|
||||
if (it == workspaces_.end()) {
|
||||
spdlog::warn("Can't find workspace with id {}", id);
|
||||
return;
|
||||
}
|
||||
|
||||
workspaces_.erase(it);
|
||||
}
|
||||
|
||||
auto WorkspaceGroup::handle_done() -> void {
|
||||
need_to_sort = false;
|
||||
if (!is_visible()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto &workspace : workspaces_) {
|
||||
workspace->handle_done();
|
||||
}
|
||||
|
||||
if (creation_delayed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!workspace_manager_.all_outputs()) {
|
||||
sort_workspaces();
|
||||
} else {
|
||||
workspace_manager_.sort_workspaces();
|
||||
}
|
||||
}
|
||||
|
||||
auto WorkspaceGroup::commit() -> void { workspace_manager_.commit(); }
|
||||
|
||||
auto WorkspaceGroup::sort_workspaces() -> void {
|
||||
std::sort(workspaces_.begin(), workspaces_.end(), workspace_manager_.workspace_comparator());
|
||||
for (size_t i = 0; i < workspaces_.size(); ++i) {
|
||||
box_.reorder_child(workspaces_[i]->get_button_ref(), i);
|
||||
}
|
||||
}
|
||||
|
||||
auto WorkspaceGroup::remove_button(Gtk::Button &button) -> void { box_.remove(button); }
|
||||
|
||||
Workspace::Workspace(const Bar &bar, const Json::Value &config, WorkspaceGroup &workspace_group,
|
||||
zext_workspace_handle_v1 *workspace, uint32_t id)
|
||||
: bar_(bar),
|
||||
config_(config),
|
||||
workspace_group_(workspace_group),
|
||||
workspace_handle_(workspace),
|
||||
id_(id) {
|
||||
add_workspace_listener(workspace, this);
|
||||
|
||||
auto config_format = config["format"];
|
||||
|
||||
format_ = config_format.isString() ? config_format.asString() : "{name}";
|
||||
with_icon_ = format_.find("{icon}") != std::string::npos;
|
||||
|
||||
if (with_icon_ && icons_map_.empty()) {
|
||||
auto format_icons = config["format-icons"];
|
||||
for (auto &name : format_icons.getMemberNames()) {
|
||||
icons_map_.emplace(name, format_icons[name].asString());
|
||||
}
|
||||
}
|
||||
|
||||
/* Handle click events if configured */
|
||||
if (config_["on-click"].isString() || config_["on-click-middle"].isString() ||
|
||||
config_["on-click-right"].isString()) {
|
||||
button_.add_events(Gdk::BUTTON_PRESS_MASK);
|
||||
button_.signal_button_press_event().connect(sigc::mem_fun(*this, &Workspace::handle_clicked),
|
||||
false);
|
||||
}
|
||||
|
||||
button_.set_relief(Gtk::RELIEF_NONE);
|
||||
content_.set_center_widget(label_);
|
||||
button_.add(content_);
|
||||
|
||||
if (!workspace_group.is_visible()) {
|
||||
return;
|
||||
}
|
||||
|
||||
workspace_group.add_button(button_);
|
||||
button_.show_all();
|
||||
}
|
||||
|
||||
Workspace::~Workspace() {
|
||||
workspace_group_.remove_button(button_);
|
||||
if (!workspace_handle_) {
|
||||
return;
|
||||
}
|
||||
|
||||
zext_workspace_handle_v1_destroy(workspace_handle_);
|
||||
workspace_handle_ = nullptr;
|
||||
}
|
||||
|
||||
auto Workspace::update() -> void {
|
||||
label_.set_markup(fmt::format(
|
||||
format_, fmt::arg("name", name_), fmt::arg("icon", with_icon_ ? get_icon() : "")));
|
||||
}
|
||||
|
||||
auto Workspace::handle_state(const std::vector<uint32_t> &state) -> void {
|
||||
state_ = 0;
|
||||
for (auto state_entry : state) {
|
||||
switch (state_entry) {
|
||||
case ZEXT_WORKSPACE_HANDLE_V1_STATE_ACTIVE:
|
||||
state_ |= (uint32_t)State::ACTIVE;
|
||||
break;
|
||||
case ZEXT_WORKSPACE_HANDLE_V1_STATE_URGENT:
|
||||
state_ |= (uint32_t)State::URGENT;
|
||||
break;
|
||||
case ZEXT_WORKSPACE_HANDLE_V1_STATE_HIDDEN:
|
||||
state_ |= (uint32_t)State::HIDDEN;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto Workspace::handle_remove() -> void {
|
||||
zext_workspace_handle_v1_destroy(workspace_handle_);
|
||||
workspace_handle_ = nullptr;
|
||||
workspace_group_.remove_workspace(id_);
|
||||
}
|
||||
|
||||
auto add_or_remove_class(Glib::RefPtr<Gtk::StyleContext> context, bool condition,
|
||||
const std::string &class_name) {
|
||||
if (condition) {
|
||||
context->add_class(class_name);
|
||||
} else {
|
||||
context->remove_class(class_name);
|
||||
}
|
||||
}
|
||||
|
||||
auto Workspace::handle_done() -> void {
|
||||
spdlog::debug("Workspace {} changed to state {}", id_, state_);
|
||||
auto style_context = button_.get_style_context();
|
||||
add_or_remove_class(style_context, is_active(), "active");
|
||||
add_or_remove_class(style_context, is_urgent(), "urgent");
|
||||
add_or_remove_class(style_context, is_hidden(), "hidden");
|
||||
|
||||
if (workspace_group_.creation_delayed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (workspace_group_.active_only() && (is_active() || is_urgent())) {
|
||||
button_.show_all();
|
||||
} else if (workspace_group_.active_only() && !(is_active() || is_urgent())) {
|
||||
button_.hide();
|
||||
}
|
||||
}
|
||||
|
||||
auto Workspace::get_icon() -> std::string {
|
||||
if (is_active()) {
|
||||
auto active_icon_it = icons_map_.find("active");
|
||||
if (active_icon_it != icons_map_.end()) {
|
||||
return active_icon_it->second;
|
||||
}
|
||||
}
|
||||
|
||||
auto named_icon_it = icons_map_.find(name_);
|
||||
if (named_icon_it != icons_map_.end()) {
|
||||
return named_icon_it->second;
|
||||
}
|
||||
|
||||
auto default_icon_it = icons_map_.find("default");
|
||||
if (default_icon_it != icons_map_.end()) {
|
||||
return default_icon_it->second;
|
||||
}
|
||||
|
||||
return name_;
|
||||
}
|
||||
|
||||
auto Workspace::handle_clicked(GdkEventButton *bt) -> bool {
|
||||
std::string action;
|
||||
if (config_["on-click"].isString() && bt->button == 1) {
|
||||
action = config_["on-click"].asString();
|
||||
} else if (config_["on-click-middle"].isString() && bt->button == 2) {
|
||||
action = config_["on-click-middle"].asString();
|
||||
} else if (config_["on-click-right"].isString() && bt->button == 3) {
|
||||
action = config_["on-click-right"].asString();
|
||||
}
|
||||
|
||||
if (action.empty())
|
||||
return true;
|
||||
else if (action == "activate") {
|
||||
zext_workspace_handle_v1_activate(workspace_handle_);
|
||||
} else if (action == "close") {
|
||||
zext_workspace_handle_v1_remove(workspace_handle_);
|
||||
} else {
|
||||
spdlog::warn("Unknown action {}", action);
|
||||
}
|
||||
|
||||
workspace_group_.commit();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto Workspace::show() -> void { button_.show_all(); }
|
||||
auto Workspace::hide() -> void { button_.hide(); }
|
||||
|
||||
auto Workspace::handle_name(const std::string &name) -> void {
|
||||
if (name_ != name) {
|
||||
workspace_group_.set_need_to_sort();
|
||||
}
|
||||
name_ = name;
|
||||
}
|
||||
|
||||
auto Workspace::handle_coordinates(const std::vector<uint32_t> &coordinates) -> void {
|
||||
if (coordinates_ != coordinates) {
|
||||
workspace_group_.set_need_to_sort();
|
||||
}
|
||||
coordinates_ = coordinates;
|
||||
}
|
||||
} // namespace waybar::modules::wlr
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user