Compare commits
219 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0fcda9afa5 | ||
|
|
4730fc4d77 | ||
|
|
2b9601b9a4 | ||
|
|
6a1d89e80d | ||
|
|
35d5203b4e | ||
|
|
13ed499999 | ||
|
|
0c1f1f2df9 | ||
|
|
ee91d18ad9 | ||
|
|
f27df33280 | ||
|
|
ea1ee2c027 | ||
|
|
54f7991325 | ||
|
|
9246297d63 | ||
|
|
630f85dcb1 | ||
|
|
77b42105a6 | ||
|
|
df138e12c4 | ||
|
|
373fd77f7a | ||
|
|
f5e6e5e9fc | ||
|
|
a88e5795a4 | ||
|
|
e9d0f2254d | ||
|
|
71c6c6032b | ||
|
|
780e96bd78 | ||
|
|
a563a3fceb | ||
|
|
286cff2e3d | ||
|
|
cbdbc492bb | ||
|
|
d6b6158ae9 | ||
|
|
f314150736 | ||
|
|
6206cebd75 | ||
|
|
0cae53747e | ||
|
|
249b452829 | ||
|
|
bfedb7c446 | ||
|
|
9ca04ee2fd | ||
|
|
eea9561525 | ||
|
|
bef539e4de | ||
|
|
456c3add4a | ||
|
|
a48116799a | ||
|
|
74a6726f4f | ||
|
|
3d00c4bed4 | ||
|
|
d8218a301d | ||
|
|
44a8910022 | ||
|
|
341e39fbcf | ||
|
|
7b9db134dd | ||
|
|
7d5905b38c | ||
|
|
5a013bdba0 | ||
|
|
ae777c65a6 | ||
|
|
103487f672 | ||
|
|
3c197811e2 | ||
|
|
0bdea1e46f | ||
|
|
8daaad1e13 | ||
|
|
25f432b0ce | ||
|
|
e4dd2ecc5a | ||
|
|
a40c53bd5d | ||
|
|
84bd0d452e | ||
|
|
d4f61ad271 | ||
|
|
c6fceb03c8 | ||
|
|
cfb47790ad | ||
|
|
d5e3a9f894 | ||
|
|
0731117679 | ||
|
|
4bb06b86bc | ||
|
|
74255d0c7e | ||
|
|
5c2cf4c65c | ||
|
|
dcbbe3bb97 | ||
|
|
37a6106d3e | ||
|
|
b8a985d606 | ||
|
|
8f35dc17b8 | ||
|
|
dfbf1ff2a0 | ||
|
|
93d85a0cea | ||
|
|
3abbdb849d | ||
|
|
7da053f1bd | ||
|
|
2f6f9620d2 | ||
|
|
c266befe0a | ||
|
|
76d0b44214 | ||
|
|
bdcab011ee | ||
|
|
f8f795ac38 | ||
|
|
af9d61fa8f | ||
|
|
bd28bb959f | ||
|
|
f4496c9648 | ||
|
|
07468357f4 | ||
|
|
2c482a2917 | ||
|
|
5e14698b4e | ||
|
|
250f58eed6 | ||
|
|
3ebf2d96e5 | ||
|
|
4d9403601a | ||
|
|
f73d26722c | ||
|
|
6cfaf4ff63 | ||
|
|
831602a913 | ||
|
|
703be13b00 | ||
|
|
fca159ad01 | ||
|
|
35c6e9c21c | ||
|
|
b36a283f83 | ||
|
|
715503ec3e | ||
|
|
c26978eca8 | ||
|
|
34484919d6 | ||
|
|
05cfd73804 | ||
|
|
c0e7aad60e | ||
|
|
d53135f834 | ||
|
|
15f54cd6ef | ||
|
|
24a30b7ffd | ||
|
|
4f55d7da90 | ||
|
|
7613069a40 | ||
|
|
d41a664779 | ||
|
|
cdf3ca910d | ||
|
|
19d2430516 | ||
|
|
47e0f42523 | ||
|
|
6319569ed0 | ||
|
|
0340760e12 | ||
|
|
f4b68f41e6 | ||
|
|
7b5206128c | ||
|
|
9bf8c8277a | ||
|
|
ff4ed82693 | ||
|
|
72184b2205 | ||
|
|
afeea62214 | ||
|
|
0332d2ebf8 | ||
|
|
7cbdce5064 | ||
|
|
ba8ea3d952 | ||
|
|
8b48982bf6 | ||
|
|
5382a09db0 | ||
|
|
20642f47df | ||
|
|
d0c6e91094 | ||
|
|
956e39d3d3 | ||
|
|
22ec8e0770 | ||
|
|
d331331b76 | ||
|
|
dfa40d6a00 | ||
|
|
45ebf45343 | ||
|
|
55f52c3457 | ||
|
|
5c48373cfe | ||
|
|
c8484ebb1d | ||
|
|
bf4f3ab064 | ||
|
|
913e0665e7 | ||
|
|
e85025f805 | ||
|
|
517eb7651e | ||
|
|
67272cc47f | ||
|
|
1dfde583df | ||
|
|
3e16c3f74e | ||
|
|
252e4f78bf | ||
|
|
dbd3ffd732 | ||
|
|
97591c825a | ||
|
|
b03ecb3d74 | ||
|
|
7e845f506e | ||
|
|
afb1ee5422 | ||
|
|
682492f7a9 | ||
|
|
633bf9e00f | ||
|
|
addf44d945 | ||
|
|
e92b0a86b5 | ||
|
|
9ca52a48c8 | ||
|
|
056295cbc6 | ||
|
|
80669f7134 | ||
|
|
5ff6b0ad0f | ||
|
|
84162ec604 | ||
|
|
91ef6e51ed | ||
|
|
c5bc3bc59a | ||
|
|
569445f8b3 | ||
|
|
eb254db867 | ||
|
|
c0b8c4d468 | ||
|
|
9d2b137594 | ||
|
|
4a8d527dd5 | ||
|
|
b98e717433 | ||
|
|
fda7abb33d | ||
|
|
9681cfd2e8 | ||
|
|
567ae16a68 | ||
|
|
2fdfd1028a | ||
|
|
8476a7dc84 | ||
|
|
e74844b8d4 | ||
|
|
a622dfd072 | ||
|
|
775067f2da | ||
|
|
508d3cd3dd | ||
|
|
eb2df58f5a | ||
|
|
4ba1947a50 | ||
|
|
6fd859c0c4 | ||
|
|
8a15cbad5c | ||
|
|
f631d5eaf9 | ||
|
|
906170400e | ||
|
|
5e4dac1c0a | ||
|
|
f7b4451564 | ||
|
|
4a6c417ef5 | ||
|
|
9f71de5227 | ||
|
|
17cee0d876 | ||
|
|
0c6ca8321c | ||
|
|
26a344b131 | ||
|
|
8bd0285c88 | ||
|
|
212c676251 | ||
|
|
ddf5b3e07b | ||
|
|
d1998de47a | ||
|
|
78d5c3ef3a | ||
|
|
937b62ea9a | ||
|
|
a26ed50d0f | ||
|
|
6004316f1a | ||
|
|
36a1c89e30 | ||
|
|
d7e4a7d91f | ||
|
|
565602114e | ||
|
|
d29a17d703 | ||
|
|
334fc6e0e4 | ||
|
|
17d7cea4fd | ||
|
|
ddb3016440 | ||
|
|
278588b1a3 | ||
|
|
2dfef1c213 | ||
|
|
71a53eb79d | ||
|
|
636ef21f6e | ||
|
|
4cb2bf06b7 | ||
|
|
fd67c6e915 | ||
|
|
b82bcdb515 | ||
|
|
13bc497abd | ||
|
|
8254bd72b7 | ||
|
|
9254ef6f2f | ||
|
|
a5e322ee66 | ||
|
|
6aa8aa3b22 | ||
|
|
f7e1d34251 | ||
|
|
38ffb24c52 | ||
|
|
1b282e67a7 | ||
|
|
1806edcb06 | ||
|
|
e40bc27257 | ||
|
|
58e4f89a82 | ||
|
|
d64c80e234 | ||
|
|
5c859bf520 | ||
|
|
ea4b95fdd2 | ||
|
|
d831a45622 | ||
|
|
2de8a83d84 | ||
|
|
ee0958973b | ||
|
|
15132aeec3 | ||
|
|
38af4a6f16 |
6
.github/workflows/clang-format.yml
vendored
6
.github/workflows/clang-format.yml
vendored
@@ -7,11 +7,13 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: DoozyX/clang-format-lint-action@v0.16.2
|
||||
# TODO: bump to clang 19 release
|
||||
# - uses: DoozyX/clang-format-lint-action@v0.18.2
|
||||
- uses: DoozyX/clang-format-lint-action@558090054b3f39e3d6af24f0cd73b319535da809
|
||||
name: clang-format
|
||||
with:
|
||||
source: "."
|
||||
|
||||
2
.github/workflows/clang-tidy.yml.bak
vendored
2
.github/workflows/clang-tidy.yml.bak
vendored
@@ -7,7 +7,7 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: alexays/waybar:debian
|
||||
|
||||
7
.github/workflows/docker.yml
vendored
7
.github/workflows/docker.yml
vendored
@@ -1,14 +1,15 @@
|
||||
name: Build and Push Docker Image
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
# run every night at midnight
|
||||
- cron: '0 0 * * *'
|
||||
# run monthly
|
||||
- cron: '0 0 1 * *'
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'Alexays/Waybar'
|
||||
if: github.event_name != 'schedule' || github.repository == 'Alexays/Waybar'
|
||||
strategy:
|
||||
fail-fast: false # don't fail the other jobs if one of the images fails to build
|
||||
matrix:
|
||||
|
||||
6
.github/workflows/freebsd.yml
vendored
6
.github/workflows/freebsd.yml
vendored
@@ -7,21 +7,21 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
clang:
|
||||
build:
|
||||
# Run actions in a FreeBSD VM on the ubuntu runner
|
||||
# https://github.com/actions/runner/issues/385 - for FreeBSD runner support
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Test in FreeBSD VM
|
||||
uses: cross-platform-actions/action@v0.25.0
|
||||
uses: cross-platform-actions/action@v0.28.0
|
||||
timeout-minutes: 180
|
||||
env:
|
||||
CPPFLAGS: '-isystem/usr/local/include'
|
||||
LDFLAGS: '-L/usr/local/lib'
|
||||
with:
|
||||
operating_system: freebsd
|
||||
version: "14.1"
|
||||
version: "14.2"
|
||||
environment_variables: CPPFLAGS LDFLAGS
|
||||
sync_files: runner-to-vm
|
||||
run: |
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=Dockerfile
|
||||
|
||||
FROM debian:sid
|
||||
FROM debian:sid-slim
|
||||
|
||||
RUN apt update && \
|
||||
apt install --no-install-recommends --no-install-suggests -y \
|
||||
|
||||
@@ -6,6 +6,6 @@ RUN export FEATURES="-ipc-sandbox -network-sandbox -pid-sandbox -sandbox -usersa
|
||||
emerge --sync && \
|
||||
eselect news read --quiet new 1>/dev/null 2>&1 && \
|
||||
emerge --verbose --update --deep --with-bdeps=y --backtrack=30 --newuse @world && \
|
||||
USE="wayland gtk3 gtk -doc X pulseaudio minimal" emerge dev-vcs/git dev-libs/wayland dev-libs/wayland-protocols =dev-cpp/gtkmm-3.24.6 x11-libs/libxkbcommon \
|
||||
USE="wayland gtk3 gtk -doc X pulseaudio minimal" emerge dev-vcs/git dev-libs/wayland dev-libs/wayland-protocols dev-cpp/gtkmm:3.0 x11-libs/libxkbcommon \
|
||||
x11-libs/gtk+:3 dev-libs/libdbusmenu dev-libs/libnl sys-power/upower media-libs/libpulse dev-libs/libevdev media-libs/libmpdclient \
|
||||
media-sound/sndio gui-libs/gtk-layer-shell app-text/scdoc media-sound/playerctl dev-libs/iniparser sci-libs/fftw
|
||||
|
||||
19
default.nix
19
default.nix
@@ -1,10 +1,9 @@
|
||||
(import
|
||||
(
|
||||
let lock = builtins.fromJSON (builtins.readFile ./flake.lock); in
|
||||
fetchTarball {
|
||||
url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz";
|
||||
sha256 = lock.nodes.flake-compat.locked.narHash;
|
||||
}
|
||||
)
|
||||
{ src = ./.; }
|
||||
).defaultNix
|
||||
(import (
|
||||
let
|
||||
lock = builtins.fromJSON (builtins.readFile ./flake.lock);
|
||||
in
|
||||
fetchTarball {
|
||||
url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz";
|
||||
sha256 = lock.nodes.flake-compat.locked.narHash;
|
||||
}
|
||||
) { src = ./.; }).defaultNix
|
||||
|
||||
12
flake.lock
generated
12
flake.lock
generated
@@ -3,11 +3,11 @@
|
||||
"flake-compat": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1733328505,
|
||||
"narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=",
|
||||
"lastModified": 1747046372,
|
||||
"narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=",
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec",
|
||||
"rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -18,11 +18,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1738142207,
|
||||
"narHash": "sha256-NGqpVVxNAHwIicXpgaVqJEJWeyqzoQJ9oc8lnK9+WC4=",
|
||||
"lastModified": 1748460289,
|
||||
"narHash": "sha256-7doLyJBzCllvqX4gszYtmZUToxKvMUrg45EUWaUYmBg=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "9d3ae807ebd2981d593cddd0080856873139aa40",
|
||||
"rev": "96ec055edbe5ee227f28cdbc3f1ddf1df5965102",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
129
flake.nix
129
flake.nix
@@ -9,47 +9,96 @@
|
||||
};
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, ... }:
|
||||
outputs =
|
||||
{ self, nixpkgs, ... }:
|
||||
let
|
||||
inherit (nixpkgs) lib;
|
||||
genSystems = func: lib.genAttrs [
|
||||
"x86_64-linux"
|
||||
"aarch64-linux"
|
||||
]
|
||||
(system: func (import nixpkgs {
|
||||
inherit system;
|
||||
overlays = with self.overlays; [
|
||||
waybar
|
||||
];
|
||||
}));
|
||||
genSystems =
|
||||
func:
|
||||
lib.genAttrs
|
||||
[
|
||||
"x86_64-linux"
|
||||
"aarch64-linux"
|
||||
]
|
||||
(
|
||||
system:
|
||||
func (
|
||||
import nixpkgs {
|
||||
inherit system;
|
||||
overlays = with self.overlays; [
|
||||
waybar
|
||||
];
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
mkDate = longDate: (lib.concatStringsSep "-" [
|
||||
(builtins.substring 0 4 longDate)
|
||||
(builtins.substring 4 2 longDate)
|
||||
(builtins.substring 6 2 longDate)
|
||||
]);
|
||||
mkDate =
|
||||
longDate:
|
||||
(lib.concatStringsSep "-" [
|
||||
(builtins.substring 0 4 longDate)
|
||||
(builtins.substring 4 2 longDate)
|
||||
(builtins.substring 6 2 longDate)
|
||||
]);
|
||||
in
|
||||
{
|
||||
devShells = genSystems
|
||||
(pkgs:
|
||||
{
|
||||
default =
|
||||
pkgs.mkShell
|
||||
{
|
||||
name = "waybar-shell";
|
||||
devShells = genSystems (pkgs: {
|
||||
default = pkgs.mkShell {
|
||||
name = "waybar-shell";
|
||||
|
||||
# inherit attributes from upstream nixpkgs derivation
|
||||
inherit (pkgs.waybar) buildInputs depsBuildBuild depsBuildBuildPropagated depsBuildTarget
|
||||
depsBuildTargetPropagated depsHostHost depsHostHostPropagated depsTargetTarget
|
||||
depsTargetTargetPropagated propagatedBuildInputs propagatedNativeBuildInputs strictDeps;
|
||||
# inherit attributes from upstream nixpkgs derivation
|
||||
inherit (pkgs.waybar)
|
||||
buildInputs
|
||||
depsBuildBuild
|
||||
depsBuildBuildPropagated
|
||||
depsBuildTarget
|
||||
depsBuildTargetPropagated
|
||||
depsHostHost
|
||||
depsHostHostPropagated
|
||||
depsTargetTarget
|
||||
depsTargetTargetPropagated
|
||||
propagatedBuildInputs
|
||||
propagatedNativeBuildInputs
|
||||
strictDeps
|
||||
;
|
||||
|
||||
# overrides for local development
|
||||
nativeBuildInputs = pkgs.waybar.nativeBuildInputs ++ (with pkgs; [
|
||||
clang-tools
|
||||
gdb
|
||||
]);
|
||||
# overrides for local development
|
||||
nativeBuildInputs =
|
||||
pkgs.waybar.nativeBuildInputs
|
||||
++ (with pkgs; [
|
||||
nixfmt-rfc-style
|
||||
clang-tools
|
||||
gdb
|
||||
]);
|
||||
};
|
||||
});
|
||||
|
||||
formatter = genSystems (
|
||||
pkgs:
|
||||
pkgs.treefmt.withConfig {
|
||||
settings = [
|
||||
{
|
||||
formatter = {
|
||||
clang-format = {
|
||||
options = [ "-i" ];
|
||||
command = lib.getExe' pkgs.clang-tools "clang-format";
|
||||
excludes = [ ];
|
||||
includes = [
|
||||
"*.c"
|
||||
"*.cpp"
|
||||
"*.h"
|
||||
"*.hpp"
|
||||
];
|
||||
};
|
||||
});
|
||||
nixfmt = {
|
||||
command = lib.getExe pkgs.nixfmt-rfc-style;
|
||||
includes = [ "*.nix" ];
|
||||
};
|
||||
};
|
||||
tree-root-file = ".git/index";
|
||||
}
|
||||
];
|
||||
}
|
||||
);
|
||||
|
||||
overlays = {
|
||||
default = self.overlays.waybar;
|
||||
@@ -58,11 +107,15 @@
|
||||
waybar = prev.waybar;
|
||||
# take the first "version: '...'" from meson.build
|
||||
version =
|
||||
(builtins.head (builtins.split "'"
|
||||
(builtins.elemAt
|
||||
(builtins.split " version: '" (builtins.readFile ./meson.build))
|
||||
2)))
|
||||
+ "+date=" + (mkDate (self.lastModifiedDate or "19700101")) + "_" + (self.shortRev or "dirty");
|
||||
(builtins.head (
|
||||
builtins.split "'" (
|
||||
builtins.elemAt (builtins.split " version: '" (builtins.readFile ./meson.build)) 2
|
||||
)
|
||||
))
|
||||
+ "+date="
|
||||
+ (mkDate (self.lastModifiedDate or "19700101"))
|
||||
+ "_"
|
||||
+ (self.shortRev or "dirty");
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -36,6 +36,7 @@ class AModule : public IModule {
|
||||
SCROLL_DIR getScrollDir(GdkEventScroll *e);
|
||||
bool tooltipEnabled() const;
|
||||
|
||||
std::vector<int> pid_children_;
|
||||
const std::string name_;
|
||||
const Json::Value &config_;
|
||||
Gtk::EventBox event_box_;
|
||||
@@ -54,7 +55,6 @@ class AModule : public IModule {
|
||||
const bool isTooltip;
|
||||
const bool isExpand;
|
||||
bool hasUserEvents_;
|
||||
std::vector<int> pid_;
|
||||
gdouble distance_scrolled_y_;
|
||||
gdouble distance_scrolled_x_;
|
||||
std::map<std::string, std::string> eventActionMap_;
|
||||
|
||||
@@ -5,7 +5,16 @@
|
||||
|
||||
namespace cava {
|
||||
extern "C" {
|
||||
// Need sdl_glsl output feature to be enabled on libcava
|
||||
#ifndef SDL_GLSL
|
||||
#define SDL_GLSL
|
||||
#endif
|
||||
|
||||
#include <cava/common.h>
|
||||
|
||||
#ifdef SDL_GLSL
|
||||
#undef SDL_GLSL
|
||||
#endif
|
||||
}
|
||||
} // namespace cava
|
||||
|
||||
|
||||
@@ -38,39 +38,39 @@ class Clock final : public ALabel {
|
||||
5 - tooltip-format
|
||||
*/
|
||||
std::map<int, std::string const> fmtMap_;
|
||||
uint cldMonCols_{3}; // calendar count month columns
|
||||
int cldWnLen_{3}; // calendar week number length
|
||||
const int cldMonColLen_{20}; // calendar month column length
|
||||
WS cldWPos_{WS::HIDDEN}; // calendar week side to print
|
||||
months cldCurrShift_{0}; // calendar months shift
|
||||
int cldShift_{1}; // calendar months shift factor
|
||||
year_month_day cldYearShift_; // calendar Year mode. Cached ymd
|
||||
std::string cldYearCached_; // calendar Year mode. Cached calendar
|
||||
year_month cldMonShift_; // calendar Month mode. Cached ym
|
||||
std::string cldMonCached_; // calendar Month mode. Cached calendar
|
||||
day cldBaseDay_{0}; // calendar Cached day. Is used when today is changing(midnight)
|
||||
std::string cldText_{""}; // calendar text to print
|
||||
uint cldMonCols_{3}; // calendar count month columns
|
||||
int cldWnLen_{3}; // calendar week number length
|
||||
const int cldMonColLen_{20}; // calendar month column length
|
||||
WS cldWPos_{WS::HIDDEN}; // calendar week side to print
|
||||
date::months cldCurrShift_{0}; // calendar months shift
|
||||
int cldShift_{1}; // calendar months shift factor
|
||||
date::year_month_day cldYearShift_; // calendar Year mode. Cached ymd
|
||||
std::string cldYearCached_; // calendar Year mode. Cached calendar
|
||||
date::year_month cldMonShift_; // calendar Month mode. Cached ym
|
||||
std::string cldMonCached_; // calendar Month mode. Cached calendar
|
||||
date::day cldBaseDay_{0}; // calendar Cached day. Is used when today is changing(midnight)
|
||||
std::string cldText_{""}; // calendar text to print
|
||||
CldMode cldMode_{CldMode::MONTH};
|
||||
auto get_calendar(const year_month_day& today, const year_month_day& ymd, const time_zone* tz)
|
||||
-> const std::string;
|
||||
auto get_calendar(const date::year_month_day& today, const date::year_month_day& ymd,
|
||||
const date::time_zone* tz) -> const std::string;
|
||||
|
||||
// get local time zone
|
||||
auto local_zone() -> const time_zone*;
|
||||
auto local_zone() -> const date::time_zone*;
|
||||
|
||||
// time zoned time in tooltip
|
||||
const bool tzInTooltip_; // if need to print time zones text
|
||||
std::vector<const time_zone*> tzList_; // time zones list
|
||||
int tzCurrIdx_; // current time zone index for tzList_
|
||||
std::string tzText_{""}; // time zones text to print
|
||||
const bool tzInTooltip_; // if need to print time zones text
|
||||
std::vector<const date::time_zone*> tzList_; // time zones list
|
||||
int tzCurrIdx_; // current time zone index for tzList_
|
||||
std::string tzText_{""}; // time zones text to print
|
||||
util::SleeperThread thread_;
|
||||
|
||||
// ordinal date in tooltip
|
||||
const bool ordInTooltip_;
|
||||
std::string ordText_{""};
|
||||
auto get_ordinal_date(const year_month_day& today) -> std::string;
|
||||
auto get_ordinal_date(const date::year_month_day& today) -> std::string;
|
||||
|
||||
auto getTZtext(sys_seconds now) -> std::string;
|
||||
auto first_day_of_week() -> weekday;
|
||||
auto getTZtext(date::sys_seconds now) -> std::string;
|
||||
auto first_day_of_week() -> date::weekday;
|
||||
// Module actions
|
||||
void cldModeSwitch();
|
||||
void cldShift_up();
|
||||
|
||||
40
include/modules/gps.hpp
Normal file
40
include/modules/gps.hpp
Normal file
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <sys/statvfs.h>
|
||||
|
||||
#ifdef WANT_RFKILL
|
||||
#include "util/rfkill.hpp"
|
||||
#endif
|
||||
|
||||
#include <gps.h>
|
||||
|
||||
#include "ALabel.hpp"
|
||||
#include "util/sleeper_thread.hpp"
|
||||
|
||||
namespace waybar::modules {
|
||||
|
||||
class Gps : public ALabel {
|
||||
public:
|
||||
Gps(const std::string&, const Json::Value&);
|
||||
virtual ~Gps();
|
||||
auto update() -> void override;
|
||||
|
||||
private:
|
||||
#ifdef WANT_RFKILL
|
||||
util::Rfkill rfkill_;
|
||||
#endif
|
||||
const std::string getFixModeName() const;
|
||||
const std::string getFixModeString() const;
|
||||
|
||||
const std::string getFixStatusString() const;
|
||||
|
||||
util::SleeperThread thread_, gps_thread_;
|
||||
gps_data_t gps_data_;
|
||||
std::string state_;
|
||||
|
||||
bool hideDisconnected = true;
|
||||
bool hideNoFix = false;
|
||||
};
|
||||
|
||||
} // namespace waybar::modules
|
||||
@@ -46,4 +46,5 @@ class IPC {
|
||||
};
|
||||
|
||||
inline bool modulesReady = false;
|
||||
inline std::unique_ptr<IPC> gIPC;
|
||||
}; // namespace waybar::modules::hyprland
|
||||
|
||||
41
include/modules/hyprland/windowcount.hpp
Normal file
41
include/modules/hyprland/windowcount.hpp
Normal file
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "AAppIconLabel.hpp"
|
||||
#include "bar.hpp"
|
||||
#include "modules/hyprland/backend.hpp"
|
||||
#include "util/json.hpp"
|
||||
|
||||
namespace waybar::modules::hyprland {
|
||||
|
||||
class WindowCount : public waybar::AAppIconLabel, public EventHandler {
|
||||
public:
|
||||
WindowCount(const std::string&, const waybar::Bar&, const Json::Value&);
|
||||
~WindowCount() override;
|
||||
|
||||
auto update() -> void override;
|
||||
|
||||
private:
|
||||
struct Workspace {
|
||||
int id;
|
||||
int windows;
|
||||
bool hasfullscreen;
|
||||
static auto parse(const Json::Value& value) -> Workspace;
|
||||
};
|
||||
|
||||
static auto getActiveWorkspace(const std::string&) -> Workspace;
|
||||
static auto getActiveWorkspace() -> Workspace;
|
||||
void onEvent(const std::string& ev) override;
|
||||
void queryActiveWorkspace();
|
||||
void setClass(const std::string&, bool enable);
|
||||
|
||||
bool separateOutputs_;
|
||||
std::mutex mutex_;
|
||||
const Bar& bar_;
|
||||
Workspace workspace_;
|
||||
};
|
||||
|
||||
} // namespace waybar::modules::hyprland
|
||||
@@ -42,7 +42,7 @@ class WindowCreationPayload {
|
||||
std::string getWorkspaceName() const { return m_workspaceName; }
|
||||
WindowAddress getAddress() const { return m_windowAddress; }
|
||||
|
||||
void moveToWorksace(std::string& new_workspace_name);
|
||||
void moveToWorkspace(std::string& new_workspace_name);
|
||||
|
||||
private:
|
||||
void clearAddr();
|
||||
|
||||
@@ -55,11 +55,11 @@ class Workspace {
|
||||
void setName(std::string const& value) { m_name = value; };
|
||||
void setOutput(std::string const& value) { m_output = value; };
|
||||
bool containsWindow(WindowAddress const& addr) const { return m_windowMap.contains(addr); }
|
||||
void insertWindow(WindowCreationPayload create_window_paylod);
|
||||
void insertWindow(WindowCreationPayload create_window_payload);
|
||||
std::string removeWindow(WindowAddress const& addr);
|
||||
void initializeWindowMap(const Json::Value& clients_data);
|
||||
|
||||
bool onWindowOpened(WindowCreationPayload const& create_window_paylod);
|
||||
bool onWindowOpened(WindowCreationPayload const& create_window_payload);
|
||||
std::optional<std::string> closeWindow(WindowAddress const& addr);
|
||||
|
||||
void update(const std::string& format, const std::string& icon);
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <regex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -36,6 +37,7 @@ class Workspaces : public AModule, public EventHandler {
|
||||
auto showSpecial() const -> bool { return m_showSpecial; }
|
||||
auto activeOnly() const -> bool { return m_activeOnly; }
|
||||
auto specialVisibleOnly() const -> bool { return m_specialVisibleOnly; }
|
||||
auto persistentOnly() const -> bool { return m_persistentOnly; }
|
||||
auto moveToMonitor() const -> bool { return m_moveToMonitor; }
|
||||
|
||||
auto getBarOutput() const -> std::string { return m_bar.output->name; }
|
||||
@@ -49,13 +51,14 @@ class Workspaces : public AModule, public EventHandler {
|
||||
private:
|
||||
void onEvent(const std::string& e) override;
|
||||
void updateWindowCount();
|
||||
void sortSpecialCentered();
|
||||
void sortWorkspaces();
|
||||
void createWorkspace(Json::Value const& workspace_data,
|
||||
Json::Value const& clients_data = Json::Value::nullRef);
|
||||
|
||||
static Json::Value createMonitorWorkspaceData(std::string const& name,
|
||||
std::string const& monitor);
|
||||
void removeWorkspace(std::string const& name);
|
||||
void removeWorkspace(std::string const& workspaceString);
|
||||
void setUrgentWorkspace(std::string const& windowaddress);
|
||||
|
||||
// Config
|
||||
@@ -74,10 +77,11 @@ class Workspaces : public AModule, public EventHandler {
|
||||
void onWorkspaceActivated(std::string const& payload);
|
||||
void onSpecialWorkspaceActivated(std::string const& payload);
|
||||
void onWorkspaceDestroyed(std::string const& payload);
|
||||
void onWorkspaceCreated(std::string const& workspaceName,
|
||||
void onWorkspaceCreated(std::string const& payload,
|
||||
Json::Value const& clientsData = Json::Value::nullRef);
|
||||
void onWorkspaceMoved(std::string const& payload);
|
||||
void onWorkspaceRenamed(std::string const& payload);
|
||||
static std::optional<int> parseWorkspaceId(std::string const& workspaceIdStr);
|
||||
|
||||
// monitor events
|
||||
void onMonitorFocused(std::string const& payload);
|
||||
@@ -93,11 +97,18 @@ class Workspaces : public AModule, public EventHandler {
|
||||
|
||||
int windowRewritePriorityFunction(std::string const& window_rule);
|
||||
|
||||
// event payload management
|
||||
template <typename... Args>
|
||||
static std::string makePayload(Args const&... args);
|
||||
static std::pair<std::string, std::string> splitDoublePayload(std::string const& payload);
|
||||
static std::tuple<std::string, std::string, std::string> splitTriplePayload(
|
||||
std::string const& payload);
|
||||
|
||||
// Update methods
|
||||
void doUpdate();
|
||||
void removeWorkspacesToRemove();
|
||||
void createWorkspacesToCreate();
|
||||
static std::vector<std::string> getVisibleWorkspaces();
|
||||
static std::vector<int> getVisibleWorkspaces();
|
||||
void updateWorkspaceStates();
|
||||
bool updateWindowsToCreate();
|
||||
|
||||
@@ -113,20 +124,22 @@ class Workspaces : public AModule, public EventHandler {
|
||||
bool m_showSpecial = false;
|
||||
bool m_activeOnly = false;
|
||||
bool m_specialVisibleOnly = false;
|
||||
bool m_persistentOnly = false;
|
||||
bool m_moveToMonitor = false;
|
||||
Json::Value m_persistentWorkspaceConfig;
|
||||
|
||||
// Map for windows stored in workspaces not present in the current bar.
|
||||
// This happens when the user has multiple monitors (hence, multiple bars)
|
||||
// and doesn't share windows accross bars (a.k.a `all-outputs` = false)
|
||||
// and doesn't share windows across bars (a.k.a `all-outputs` = false)
|
||||
std::map<WindowAddress, std::string> m_orphanWindowMap;
|
||||
|
||||
enum class SortMethod { ID, NAME, NUMBER, DEFAULT };
|
||||
enum class SortMethod { ID, NAME, NUMBER, SPECIAL_CENTERED, DEFAULT };
|
||||
util::EnumParser<SortMethod> m_enumParser;
|
||||
SortMethod m_sortBy = SortMethod::DEFAULT;
|
||||
std::map<std::string, SortMethod> m_sortMap = {{"ID", SortMethod::ID},
|
||||
{"NAME", SortMethod::NAME},
|
||||
{"NUMBER", SortMethod::NUMBER},
|
||||
{"SPECIAL-CENTERED", SortMethod::SPECIAL_CENTERED},
|
||||
{"DEFAULT", SortMethod::DEFAULT}};
|
||||
|
||||
std::string m_format;
|
||||
@@ -138,7 +151,7 @@ class Workspaces : public AModule, public EventHandler {
|
||||
|
||||
bool m_withIcon;
|
||||
uint64_t m_monitorId;
|
||||
std::string m_activeWorkspaceName;
|
||||
int m_activeWorkspaceId;
|
||||
std::string m_activeSpecialWorkspaceName;
|
||||
std::vector<std::unique_ptr<Workspace>> m_workspaces;
|
||||
std::vector<std::pair<Json::Value, Json::Value>> m_workspacesToCreate;
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
#include "util/rfkill.hpp"
|
||||
#endif
|
||||
|
||||
enum ip_addr_pref : uint8_t { IPV4, IPV6, IPV4_6 };
|
||||
|
||||
namespace waybar::modules {
|
||||
|
||||
class Network : public ALabel {
|
||||
@@ -50,6 +52,7 @@ class Network : public ALabel {
|
||||
std::optional<std::pair<unsigned long long, unsigned long long>> readBandwidthUsage();
|
||||
|
||||
int ifid_;
|
||||
ip_addr_pref addr_pref_;
|
||||
struct sockaddr_nl nladdr_ = {0};
|
||||
struct nl_sock* sock_ = nullptr;
|
||||
struct nl_sock* ev_sock_ = nullptr;
|
||||
@@ -73,9 +76,12 @@ class Network : public ALabel {
|
||||
bool carrier_;
|
||||
std::string ifname_;
|
||||
std::string ipaddr_;
|
||||
std::string ipaddr6_;
|
||||
std::string gwaddr_;
|
||||
std::string netmask_;
|
||||
std::string netmask6_;
|
||||
int cidr_;
|
||||
int cidr6_;
|
||||
int32_t signal_strength_dbm_;
|
||||
uint8_t signal_strength_;
|
||||
std::string signal_strength_app_;
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace waybar::modules::privacy {
|
||||
|
||||
class Privacy : public AModule {
|
||||
public:
|
||||
Privacy(const std::string &, const Json::Value &, const std::string &pos);
|
||||
Privacy(const std::string &, const Json::Value &, Gtk::Orientation, const std::string &pos);
|
||||
auto update() -> void override;
|
||||
|
||||
void onPrivacyNodesChanged();
|
||||
@@ -31,6 +31,8 @@ class Privacy : public AModule {
|
||||
uint iconSpacing = 4;
|
||||
uint iconSize = 20;
|
||||
uint transition_duration = 250;
|
||||
std::set<std::pair<PrivacyNodeType, std::string>> ignore;
|
||||
bool ignore_monitor = true;
|
||||
|
||||
std::shared_ptr<util::PipewireBackend::PipewireBackend> backend = nullptr;
|
||||
};
|
||||
|
||||
@@ -17,8 +17,8 @@ namespace waybar::modules::privacy {
|
||||
class PrivacyItem : public Gtk::Revealer {
|
||||
public:
|
||||
PrivacyItem(const Json::Value &config_, enum PrivacyNodeType privacy_type_,
|
||||
std::list<PrivacyNodeInfo *> *nodes, const std::string &pos, const uint icon_size,
|
||||
const uint transition_duration);
|
||||
std::list<PrivacyNodeInfo *> *nodes, Gtk::Orientation orientation,
|
||||
const std::string &pos, const uint icon_size, const uint transition_duration);
|
||||
|
||||
enum PrivacyNodeType privacy_type;
|
||||
|
||||
|
||||
43
include/modules/sni/icon_manager.hpp
Normal file
43
include/modules/sni/icon_manager.hpp
Normal file
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include <json/json.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
class IconManager {
|
||||
public:
|
||||
static IconManager& instance() {
|
||||
static IconManager instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void setIconsConfig(const Json::Value& icons_config) {
|
||||
if (icons_config.isObject()) {
|
||||
for (const auto& key : icons_config.getMemberNames()) {
|
||||
std::string app_name = key;
|
||||
const Json::Value& icon_value = icons_config[key];
|
||||
|
||||
if (icon_value.isString()) {
|
||||
std::string icon_path = icon_value.asString();
|
||||
icons_map_[app_name] = icon_path;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
spdlog::warn("Invalid icon config format.");
|
||||
}
|
||||
}
|
||||
|
||||
std::string getIconForApp(const std::string& app_name) const {
|
||||
auto it = icons_map_.find(app_name);
|
||||
if (it != icons_map_.end()) {
|
||||
return it->second;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private:
|
||||
IconManager() = default;
|
||||
std::unordered_map<std::string, std::string> icons_map_;
|
||||
};
|
||||
@@ -62,6 +62,7 @@ class Item : public sigc::trackable {
|
||||
void proxyReady(Glib::RefPtr<Gio::AsyncResult>& result);
|
||||
void setProperty(const Glib::ustring& name, Glib::VariantBase& value);
|
||||
void setStatus(const Glib::ustring& value);
|
||||
void setCustomIcon(const std::string& id);
|
||||
void getUpdatedProperties();
|
||||
void processUpdatedProperties(Glib::RefPtr<Gio::AsyncResult>& result);
|
||||
void onSignal(const Glib::ustring& sender_name, const Glib::ustring& signal_name,
|
||||
|
||||
@@ -21,7 +21,7 @@ class Language : public ALabel, public sigc::trackable {
|
||||
auto update() -> void override;
|
||||
|
||||
private:
|
||||
enum class DispayedShortFlag { None = 0, ShortName = 1, ShortDescription = 1 << 1 };
|
||||
enum class DisplayedShortFlag { None = 0, ShortName = 1, ShortDescription = 1 << 1 };
|
||||
|
||||
struct Layout {
|
||||
std::string full_name;
|
||||
@@ -58,7 +58,7 @@ class Language : public ALabel, public sigc::trackable {
|
||||
std::map<std::string, Layout> layouts_map_;
|
||||
bool hide_single_;
|
||||
bool is_variant_displayed;
|
||||
std::byte displayed_short_flag = static_cast<std::byte>(DispayedShortFlag::None);
|
||||
std::byte displayed_short_flag = static_cast<std::byte>(DisplayedShortFlag::None);
|
||||
|
||||
util::JsonParser parser_;
|
||||
std::mutex mutex_;
|
||||
|
||||
@@ -19,10 +19,11 @@ class Window : public AAppIconLabel, public sigc::trackable {
|
||||
auto update() -> void override;
|
||||
|
||||
private:
|
||||
void setClass(std::string classname, bool enable);
|
||||
void setClass(const std::string& classname, bool enable);
|
||||
void onEvent(const struct Ipc::ipc_response&);
|
||||
void onCmd(const struct Ipc::ipc_response&);
|
||||
std::tuple<std::size_t, int, int, std::string, std::string, std::string, std::string, std::string>
|
||||
std::tuple<std::size_t, int, int, std::string, std::string, std::string, std::string, std::string,
|
||||
std::string>
|
||||
getFocusedNode(const Json::Value& nodes, std::string& output);
|
||||
void getTree();
|
||||
|
||||
@@ -35,6 +36,7 @@ class Window : public AAppIconLabel, public sigc::trackable {
|
||||
std::string old_app_id_;
|
||||
std::size_t app_nb_;
|
||||
std::string shell_;
|
||||
std::string marks_;
|
||||
int floating_count_;
|
||||
util::JsonParser parser_;
|
||||
std::mutex mutex_;
|
||||
|
||||
@@ -48,7 +48,7 @@ class Workspaces : public AModule, public sigc::trackable {
|
||||
std::vector<std::string> high_priority_named_;
|
||||
std::vector<std::string> workspaces_order_;
|
||||
Gtk::Box box_;
|
||||
std::string m_formatWindowSeperator;
|
||||
std::string m_formatWindowSeparator;
|
||||
util::RegexCollection m_windowRewriteRules;
|
||||
util::JsonParser parser_;
|
||||
std::unordered_map<std::string, Gtk::Button> buttons_;
|
||||
|
||||
@@ -19,12 +19,15 @@ class SystemdFailedUnits : public ALabel {
|
||||
std::string format_ok;
|
||||
|
||||
bool update_pending;
|
||||
uint32_t nr_failed_system, nr_failed_user;
|
||||
std::string system_state, user_state, overall_state;
|
||||
uint32_t nr_failed_system, nr_failed_user, nr_failed;
|
||||
std::string last_status;
|
||||
Glib::RefPtr<Gio::DBus::Proxy> system_proxy, user_proxy;
|
||||
|
||||
void notify_cb(const Glib::ustring &sender_name, const Glib::ustring &signal_name,
|
||||
const Glib::VariantContainerBase &arguments);
|
||||
void RequestFailedUnits();
|
||||
void RequestSystemState();
|
||||
void updateData();
|
||||
};
|
||||
|
||||
|
||||
122
include/modules/wayfire/backend.hpp
Normal file
122
include/modules/wayfire/backend.hpp
Normal file
@@ -0,0 +1,122 @@
|
||||
#pragma once
|
||||
|
||||
#include <json/json.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <functional>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
namespace waybar::modules::wayfire {
|
||||
|
||||
using EventHandler = std::function<void(const std::string& event)>;
|
||||
|
||||
struct State {
|
||||
/*
|
||||
┌───────────┐ ┌───────────┐
|
||||
│ output #1 │ │ output #2 │
|
||||
└─────┬─────┘ └─────┬─────┘
|
||||
└─┐ └─────┐─ ─ ─ ─ ─ ─ ─ ─ ┐
|
||||
┌───────┴───────┐ ┌───────┴──────┐ ┌───────┴───────┐
|
||||
│ wset #1 │ │ wset #2 │ │ wset #3 │
|
||||
│┌────────────┐ │ │┌────────────┐│ │┌────────────┐ │
|
||||
││ workspaces │ │ ││ workspaces ││ ││ workspaces │ │
|
||||
│└─┬──────────┘ │ │└────────────┘│ │└─┬──────────┘ │
|
||||
│ │ ┌─────────┐│ └──────────────┘ │ │ ┌─────────┐│
|
||||
│ ├─┤ view #1 ││ │ └─┤ view #3 ││
|
||||
│ │ └─────────┘│ │ └─────────┘│
|
||||
│ │ ┌─────────┐│ └───────────────┘
|
||||
│ └─┤ view #2 ││
|
||||
│ └─────────┘│
|
||||
└───────────────┘
|
||||
*/
|
||||
|
||||
struct Output {
|
||||
size_t id;
|
||||
size_t w, h;
|
||||
size_t wset_idx;
|
||||
};
|
||||
|
||||
struct Workspace {
|
||||
size_t num_views;
|
||||
size_t num_sticky_views;
|
||||
};
|
||||
|
||||
struct Wset {
|
||||
std::optional<std::reference_wrapper<Output>> output;
|
||||
std::vector<Workspace> wss;
|
||||
size_t ws_w, ws_h, ws_x, ws_y;
|
||||
size_t focused_view_id;
|
||||
|
||||
auto ws_idx() const { return ws_w * ws_y + ws_x; }
|
||||
auto count_ws(const Json::Value& pos) -> Workspace&;
|
||||
auto locate_ws(const Json::Value& geo) -> Workspace&;
|
||||
auto locate_ws(const Json::Value& geo) const -> const Workspace&;
|
||||
};
|
||||
|
||||
std::unordered_map<std::string, Output> outputs;
|
||||
std::unordered_map<size_t, Wset> wsets;
|
||||
std::unordered_map<size_t, Json::Value> views;
|
||||
std::string focused_output_name;
|
||||
size_t maybe_empty_focus_wset_idx = {};
|
||||
size_t vswitch_sticky_view_id = {};
|
||||
bool new_output_detected = {};
|
||||
bool vswitching = {};
|
||||
|
||||
auto update_view(const Json::Value& view) -> void;
|
||||
};
|
||||
|
||||
struct Sock {
|
||||
int fd;
|
||||
|
||||
Sock(int fd) : fd{fd} {}
|
||||
~Sock() { close(fd); }
|
||||
Sock(const Sock&) = delete;
|
||||
auto operator=(const Sock&) = delete;
|
||||
Sock(Sock&& rhs) noexcept {
|
||||
fd = rhs.fd;
|
||||
rhs.fd = -1;
|
||||
}
|
||||
auto& operator=(Sock&& rhs) noexcept {
|
||||
fd = rhs.fd;
|
||||
rhs.fd = -1;
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
class IPC {
|
||||
static std::weak_ptr<IPC> instance;
|
||||
Json::CharReaderBuilder reader_builder;
|
||||
Json::StreamWriterBuilder writer_builder;
|
||||
std::list<std::pair<std::string, std::reference_wrapper<const EventHandler>>> handlers;
|
||||
std::mutex handlers_mutex;
|
||||
State state;
|
||||
std::mutex state_mutex;
|
||||
|
||||
IPC() { start(); }
|
||||
|
||||
static auto connect() -> Sock;
|
||||
auto receive(Sock& sock) -> Json::Value;
|
||||
auto start() -> void;
|
||||
auto root_event_handler(const std::string& event, const Json::Value& data) -> void;
|
||||
auto update_state_handler(const std::string& event, const Json::Value& data) -> void;
|
||||
|
||||
public:
|
||||
static auto get_instance() -> std::shared_ptr<IPC>;
|
||||
auto send(const std::string& method, Json::Value&& data) -> Json::Value;
|
||||
auto register_handler(const std::string& event, const EventHandler& handler) -> void;
|
||||
auto unregister_handler(EventHandler& handler) -> void;
|
||||
|
||||
auto lock_state() -> std::lock_guard<std::mutex> { return std::lock_guard{state_mutex}; }
|
||||
auto& get_outputs() const { return state.outputs; }
|
||||
auto& get_wsets() const { return state.wsets; }
|
||||
auto& get_views() const { return state.views; }
|
||||
auto& get_focused_output_name() const { return state.focused_output_name; }
|
||||
};
|
||||
|
||||
} // namespace waybar::modules::wayfire
|
||||
24
include/modules/wayfire/window.hpp
Normal file
24
include/modules/wayfire/window.hpp
Normal file
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "AAppIconLabel.hpp"
|
||||
#include "bar.hpp"
|
||||
#include "modules/wayfire/backend.hpp"
|
||||
|
||||
namespace waybar::modules::wayfire {
|
||||
|
||||
class Window : public AAppIconLabel {
|
||||
std::shared_ptr<IPC> ipc;
|
||||
EventHandler handler;
|
||||
|
||||
const Bar& bar_;
|
||||
std::string old_app_id_;
|
||||
|
||||
public:
|
||||
Window(const std::string& id, const Bar& bar, const Json::Value& config);
|
||||
~Window() override;
|
||||
|
||||
auto update() -> void override;
|
||||
auto update_icon_label() -> void;
|
||||
};
|
||||
|
||||
} // namespace waybar::modules::wayfire
|
||||
32
include/modules/wayfire/workspaces.hpp
Normal file
32
include/modules/wayfire/workspaces.hpp
Normal file
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include <gtkmm/button.h>
|
||||
#include <json/json.h>
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "AModule.hpp"
|
||||
#include "bar.hpp"
|
||||
#include "modules/wayfire/backend.hpp"
|
||||
|
||||
namespace waybar::modules::wayfire {
|
||||
|
||||
class Workspaces : public AModule {
|
||||
std::shared_ptr<IPC> ipc;
|
||||
EventHandler handler;
|
||||
|
||||
const Bar& bar_;
|
||||
Gtk::Box box_;
|
||||
std::vector<Gtk::Button> buttons_;
|
||||
|
||||
auto handleScroll(GdkEventScroll* e) -> bool override;
|
||||
auto update() -> void override;
|
||||
auto update_box() -> void;
|
||||
|
||||
public:
|
||||
Workspaces(const std::string& id, const Bar& bar, const Json::Value& config);
|
||||
~Workspaces() override;
|
||||
};
|
||||
|
||||
} // namespace waybar::modules::wayfire
|
||||
@@ -18,7 +18,7 @@ class Wireplumber : public ALabel {
|
||||
|
||||
private:
|
||||
void asyncLoadRequiredApiModules();
|
||||
void prepare();
|
||||
void prepare(waybar::modules::Wireplumber* self);
|
||||
void activatePlugins();
|
||||
static void updateVolume(waybar::modules::Wireplumber* self, uint32_t id);
|
||||
static void updateNodeName(waybar::modules::Wireplumber* self, uint32_t id);
|
||||
@@ -32,6 +32,8 @@ class Wireplumber : public ALabel {
|
||||
|
||||
bool handleScroll(GdkEventScroll* e) override;
|
||||
|
||||
static std::list<waybar::modules::Wireplumber*> modules;
|
||||
|
||||
WpCore* wp_core_;
|
||||
GPtrArray* apis_;
|
||||
WpObjectManager* om_;
|
||||
@@ -44,6 +46,7 @@ class Wireplumber : public ALabel {
|
||||
double min_step_;
|
||||
uint32_t node_id_{0};
|
||||
std::string node_name_;
|
||||
gchar* type_;
|
||||
};
|
||||
|
||||
} // namespace waybar::modules
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
namespace date {
|
||||
#if HAVE_CHRONO_TIMEZONES
|
||||
using namespace std::chrono;
|
||||
using namespace std;
|
||||
using std::format;
|
||||
#else
|
||||
|
||||
using system_clock = std::chrono::system_clock;
|
||||
@@ -73,5 +73,3 @@ struct fmt::formatter<date::zoned_time<Duration, TimeZonePtr>> {
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
using namespace date;
|
||||
|
||||
@@ -10,5 +10,7 @@ class DefaultGtkIconThemeWrapper {
|
||||
|
||||
public:
|
||||
static bool has_icon(const std::string&);
|
||||
static Glib::RefPtr<Gdk::Pixbuf> load_icon(const char*, int, Gtk::IconLookupFlags);
|
||||
static Glib::RefPtr<Gdk::Pixbuf> load_icon(
|
||||
const char*, int, Gtk::IconLookupFlags,
|
||||
Glib::RefPtr<Gtk::StyleContext> style = Glib::RefPtr<Gtk::StyleContext>());
|
||||
};
|
||||
|
||||
@@ -25,6 +25,7 @@ class PrivacyNodeInfo {
|
||||
std::string media_name;
|
||||
std::string node_name;
|
||||
std::string application_name;
|
||||
bool is_monitor = false;
|
||||
|
||||
std::string pipewire_access_portal_app_id;
|
||||
std::string application_icon_name;
|
||||
|
||||
@@ -6,14 +6,12 @@
|
||||
|
||||
namespace waybar {
|
||||
|
||||
using namespace Gio;
|
||||
|
||||
enum class Appearance {
|
||||
UNKNOWN = 0,
|
||||
DARK = 1,
|
||||
LIGHT = 2,
|
||||
};
|
||||
class Portal : private DBus::Proxy {
|
||||
class Portal : private Gio::DBus::Proxy {
|
||||
public:
|
||||
Portal();
|
||||
void refreshAppearance();
|
||||
|
||||
@@ -4,6 +4,6 @@
|
||||
|
||||
namespace waybar::util {
|
||||
|
||||
// Get a signal emited with value true when entering sleep, and false when exiting
|
||||
// Get a signal emitted with value true when entering sleep, and false when exiting
|
||||
SafeSignal<bool>& prepare_for_sleep();
|
||||
} // namespace waybar::util
|
||||
|
||||
@@ -40,7 +40,7 @@ The brightness can be controlled by dragging the slider across the bar or clicki
|
||||
|
||||
```
|
||||
"modules-right": [
|
||||
"backlight-slider",
|
||||
"backlight/slider",
|
||||
],
|
||||
"backlight/slider": {
|
||||
"min": 0,
|
||||
|
||||
@@ -117,7 +117,7 @@ View all valid format options in *strftime(3)* or have a look https://en.cpprefe
|
||||
:[ 3
|
||||
:[ Relevant for *mode=year*. Count of months per row
|
||||
|[ *weeks-pos*
|
||||
:[ integer
|
||||
:[ string
|
||||
:[
|
||||
:[ The position where week numbers should be displayed. Disabled when is empty.
|
||||
Possible values: left|right
|
||||
|
||||
111
man/waybar-gps.5.scd
Normal file
111
man/waybar-gps.5.scd
Normal file
@@ -0,0 +1,111 @@
|
||||
waybar-gps(5) "waybar-gps" "User Manual"
|
||||
|
||||
# NAME
|
||||
|
||||
waybar - gps module
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
*gps* module for gpsd.
|
||||
|
||||
|
||||
# FILES
|
||||
|
||||
$XDG_CONFIG_HOME/waybar/config ++
|
||||
Per user configuration file
|
||||
|
||||
# ADDITIONAL FILES
|
||||
|
||||
libgps lives in:
|
||||
|
||||
. /usr/lib/libgps.so or /usr/lib64/libgps.so
|
||||
. /usr/lib/pkgconfig/libgps.pc or /usr/lib64/pkgconfig/libgps.pc
|
||||
. /usr/include/gps
|
||||
|
||||
# CONFIGURATION
|
||||
|
||||
*format*: ++
|
||||
typeof: string ++
|
||||
default: {glyph} ++
|
||||
The text format.
|
||||
|
||||
*tooltip*: ++
|
||||
typeof: bool ++
|
||||
default: true ++
|
||||
Option to disable tooltip on hover.
|
||||
|
||||
*tooltip-format*: ++
|
||||
typeof: string ++
|
||||
default: Games running: {glyph} ++
|
||||
The text format of the tooltip.
|
||||
|
||||
*interval*: ++
|
||||
typeof: integer ++
|
||||
default: 5 ++
|
||||
The interval in which the GPS information gets polled (e.g. current speed).
|
||||
Significant updates (e.g. the current fix mode) are updated immediately.
|
||||
|
||||
*hide-disconnected*: ++
|
||||
typeof: bool ++
|
||||
default: true ++
|
||||
Defines if the module should be hidden if there is no GPS receiver.
|
||||
|
||||
*hide-no-fix*: ++
|
||||
typeof: bool ++
|
||||
default: false ++
|
||||
Defines if the module should be hidden if there is no GPS fix.
|
||||
|
||||
# FORMAT REPLACEMENTS
|
||||
|
||||
*{mode}*: Fix mode
|
||||
|
||||
*{status}*: Technology used for GPS fix. Not all GPS receivers report this.
|
||||
|
||||
*{latitude}*: Latitude, decimal degrees. Can be NaN.
|
||||
|
||||
*{latitude_error}*: Latitude uncertainty, meters. Can be NaN.
|
||||
|
||||
*{longitude}*: Longitude, decimal degrees. Can be NaN.
|
||||
|
||||
*{longitude_error}*: Longitude uncertainty, meters. Can be NaN.
|
||||
|
||||
*{altitude_hae}*: Altitude, height above ellipsoid, meters. Can be NaN.
|
||||
|
||||
*{altitude_msl}*: Longitude, MSL, meters. Can be NaN.
|
||||
|
||||
*{altitude_error}*: Altitude uncertainty, meters. Can be NaN.
|
||||
|
||||
*{speed}*: Speed over ground, meters/sec. Can be NaN.
|
||||
|
||||
*{speed_error}*: Speed uncertainty, meters/sec. Can be NaN.
|
||||
|
||||
*{climb}*: Vertical speed, meters/sec. Can be NaN.
|
||||
|
||||
*{climb_error}*: Vertical speed uncertainty, meters/sec. Can be NaN.
|
||||
|
||||
*{satellites_visible}*: Number of satellites visible from the GPS receiver.
|
||||
|
||||
*{satellites_used}*: Number of satellites used for the GPS fix.
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
```
|
||||
"gps": {
|
||||
"format": "{mode}",
|
||||
"format-disabled": "", // an empty format will hide the module
|
||||
"format-no-fix": "No fix",
|
||||
"format-fix-3d": "{status}",
|
||||
"tooltip-format": "{mode}",
|
||||
"tooltip-format-no-fix": "{satellites_visible} satellites visible",
|
||||
"tooltip-format-fix-2d": "{satellites_used}/{satellites_visible} satellites used",
|
||||
"tooltip-format-fix-3d": "Altitude: {altitude_hae}m",
|
||||
"hide-disconnected": false
|
||||
}
|
||||
```
|
||||
# STYLE
|
||||
|
||||
- *#gps*
|
||||
- *#gps.disabled* Applied when GPS is disabled.
|
||||
- *#gps.fix-none* Applied when GPS is present, but there is no fix.
|
||||
- *#gps.fix-2d* Applied when there is a 2D fix.
|
||||
- *#gps.fix-3d* Applied when there is a 3D fix.
|
||||
46
man/waybar-hyprland-windowcount.5.scd
Normal file
46
man/waybar-hyprland-windowcount.5.scd
Normal file
@@ -0,0 +1,46 @@
|
||||
waybar-hyprland-windowcount(5)
|
||||
|
||||
# NAME
|
||||
|
||||
waybar - hyprland window count module
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
The *windowcount* module displays the number of windows in the current Hyprland workspace.
|
||||
|
||||
# CONFIGURATION
|
||||
|
||||
Addressed by *hyprland/windowcount*
|
||||
|
||||
*format*: ++
|
||||
typeof: string ++
|
||||
default: {} ++
|
||||
The format for how information should be displayed. On {} the current workspace window count is displayed.
|
||||
|
||||
*format-empty*: ++
|
||||
typeof: string ++
|
||||
Override the format when the workspace contains no windows window
|
||||
|
||||
*format-windowed*: ++
|
||||
typeof: string ++
|
||||
Override the format when the workspace contains no fullscreen windows
|
||||
|
||||
*format-fullscreen*: ++
|
||||
typeof: string ++
|
||||
Override the format when the workspace contains a fullscreen window
|
||||
|
||||
*separate-outputs*: ++
|
||||
typeof: bool ++
|
||||
default: true ++
|
||||
Show the active workspace window count of the monitor the bar belongs to, instead of the focused workspace.
|
||||
|
||||
# STYLE
|
||||
|
||||
- *#windowcount*
|
||||
|
||||
The following classes are applied to the entire Waybar rather than just the
|
||||
windowcount widget:
|
||||
|
||||
- *window#waybar.empty* When no windows are in the workspace
|
||||
- *window#waybar.fullscreen* When there is a fullscreen window in the workspace;
|
||||
useful with Hyprland's *fullscreen, 1* mode
|
||||
@@ -48,6 +48,11 @@ Addressed by *hyprland/workspaces*
|
||||
default: false ++
|
||||
If this and show-special are to true, special workspaces will be shown only if visible.
|
||||
|
||||
*persistent-only*: ++
|
||||
typeof: bool ++
|
||||
default: false ++
|
||||
If set to true, only persistent workspaces will be shown on bar.
|
||||
|
||||
*all-outputs*: ++
|
||||
typeof: bool ++
|
||||
default: false ++
|
||||
@@ -76,6 +81,7 @@ Addressed by *hyprland/workspaces*
|
||||
If set to number, workspaces will sort by number.
|
||||
If set to name, workspaces will sort by name.
|
||||
If set to id, workspaces will sort by id.
|
||||
If set to special-centered, workspaces will sort by default with special workspaces in the center.
|
||||
If none of those, workspaces will sort with default behavior.
|
||||
|
||||
*expand*: ++
|
||||
|
||||
@@ -125,3 +125,9 @@ screensaver, also known as "presentation mode".
|
||||
"timeout": 30.5
|
||||
}
|
||||
```
|
||||
|
||||
# STYLE
|
||||
|
||||
- *#idle_inhibitor*
|
||||
- *#idle_inhibitor.activated*
|
||||
- *#idle_inhibitor.deactivated*
|
||||
|
||||
@@ -120,6 +120,8 @@ Addressed by *memory*
|
||||
|
||||
*{swapAvail}*: Amount of available swap in GiB.
|
||||
|
||||
*{swapState}*: Signals if swap is activated or not
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
```
|
||||
|
||||
@@ -7,7 +7,7 @@ waybar - menu property
|
||||
# OVERVIEW
|
||||
|
||||
|
||||
Some modules support a 'menu', which allows to have a popup menu whan a defined
|
||||
Some modules support a 'menu', which allows to have a popup menu when a defined
|
||||
click is done over the module.
|
||||
|
||||
# PROPERTIES
|
||||
|
||||
@@ -24,7 +24,7 @@ Addressed by *network*
|
||||
*family*: ++
|
||||
typeof: string ++
|
||||
default: *ipv4* ++
|
||||
The address family that is used for the format replacement {ipaddr} and to determine if a network connection is present.
|
||||
The address family that is used for the format replacement {ipaddr} and to determine if a network connection is present. Set it to ipv4_6 to display both.
|
||||
|
||||
*format*: ++
|
||||
typeof: string ++
|
||||
@@ -155,9 +155,13 @@ Addressed by *network*
|
||||
|
||||
*{gwaddr}*: The default gateway for the interface
|
||||
|
||||
*{netmask}*: The subnetmask corresponding to the IP.
|
||||
*{netmask}*: The subnetmask corresponding to the IP(V4).
|
||||
|
||||
*{cidr}*: The subnetmask corresponding to the IP in CIDR notation.
|
||||
*{netmask6}*: The subnetmask corresponding to the IP(V6).
|
||||
|
||||
*{cidr}*: The subnetmask corresponding to the IP(V4) in CIDR notation.
|
||||
|
||||
*{cidr6}*: The subnetmask corresponding to the IP(V6) in CIDR notation.
|
||||
|
||||
*{essid}*: Name (SSID) of the wireless network.
|
||||
|
||||
@@ -167,7 +171,7 @@ Addressed by *network*
|
||||
|
||||
*{signaldBm}*: Signal strength of the wireless network in dBm.
|
||||
|
||||
*{frequency}*: Frequency of the wireless network in MHz.
|
||||
*{frequency}*: Frequency of the wireless network in GHz.
|
||||
|
||||
*{bandwidthUpBits}*: Instant up speed in bits/seconds.
|
||||
|
||||
|
||||
@@ -70,6 +70,8 @@ Additional to workspace name matching, the following *format-icons* can be set.
|
||||
- *default*: Will be shown, when no string matches are found.
|
||||
- *focused*: Will be shown, when workspace is focused.
|
||||
- *active*: Will be shown, when workspace is active on its output.
|
||||
- *urgent*: Will be shown, when workspace has urgent windows.
|
||||
- *empty*: Will be shown, when workspace is empty.
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
@@ -95,6 +97,7 @@ Additional to workspace name matching, the following *format-icons* can be set.
|
||||
- *#workspaces button*
|
||||
- *#workspaces button.focused*: The single focused workspace.
|
||||
- *#workspaces button.active*: The workspace is active (visible) on its output.
|
||||
- *#workspaces button.urgent*: The workspace has one or more urgent windows.
|
||||
- *#workspaces button.empty*: The workspace is empty.
|
||||
- *#workspaces button.current_output*: The workspace is from the same output as
|
||||
the bar that it is displayed on.
|
||||
|
||||
@@ -37,6 +37,17 @@ the screen or playing audio.
|
||||
default: false ++
|
||||
Enables this module to consume all left over space dynamically.
|
||||
|
||||
*ignore-monitor* ++
|
||||
typeof: bool ++
|
||||
default: true ++
|
||||
Ignore streams with *stream.monitor* property.
|
||||
|
||||
*ignore* ++
|
||||
typeof: array of objects ++
|
||||
default: [] ++
|
||||
Additional streams to be ignored. See *IGNORE CONFIGURATION* for++
|
||||
more information.
|
||||
|
||||
# MODULES CONFIGURATION
|
||||
|
||||
*type*: ++
|
||||
@@ -54,6 +65,14 @@ the screen or playing audio.
|
||||
default: 24 ++
|
||||
The size of each icon in the tooltip.
|
||||
|
||||
# IGNORE CONFIGURATION
|
||||
|
||||
*type*: ++
|
||||
typeof: string
|
||||
|
||||
*name*: ++
|
||||
typeof: string
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
```
|
||||
@@ -77,6 +96,17 @@ the screen or playing audio.
|
||||
"tooltip": true,
|
||||
"tooltip-icon-size": 24
|
||||
}
|
||||
],
|
||||
"ignore-monitor": true,
|
||||
"ignore": [
|
||||
{
|
||||
"type": "audio-in",
|
||||
"name": "cava"
|
||||
},
|
||||
{
|
||||
"type": "screenshare",
|
||||
"name": "obs"
|
||||
}
|
||||
]
|
||||
},
|
||||
```
|
||||
|
||||
@@ -89,6 +89,11 @@ Addressed by *sway/window*
|
||||
default: false ++
|
||||
If the workspace itself is focused and the workspace contains nodes or floating_nodes, show the workspace name. If not set, text remains empty but styles according to nodes in the workspace are still applied.
|
||||
|
||||
*show-hidden-marks*: ++
|
||||
typeof: bool ++
|
||||
default: false ++
|
||||
For the *{marks}* format replacement, include hidden marks that start with an underscore.
|
||||
|
||||
*rewrite*: ++
|
||||
typeof: object ++
|
||||
Rules to rewrite the module format output. See *rewrite rules*.
|
||||
@@ -117,6 +122,8 @@ Addressed by *sway/window*
|
||||
*{shell}*: The shell of the focused window. It's 'xwayland' when the window is
|
||||
running through xwayland, otherwise, it's 'xdg-shell'.
|
||||
|
||||
*{marks}*: Marks of the window.
|
||||
|
||||
# REWRITE RULES
|
||||
|
||||
*rewrite* is an object where keys are regular expressions and values are
|
||||
|
||||
@@ -62,6 +62,12 @@ Addressed by *systemd-failed-units*
|
||||
|
||||
*{nr_failed}*: Number of total failed units.
|
||||
|
||||
*{systemd_state}:* State of the systemd system session
|
||||
|
||||
*{user_state}:* State of the systemd user session
|
||||
|
||||
*{overall_state}:* Overall state of the systemd and user session. ("Ok" or "Degraded")
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
```
|
||||
|
||||
@@ -47,7 +47,11 @@ Addressed by *tray*
|
||||
```
|
||||
"tray": {
|
||||
"icon-size": 21,
|
||||
"spacing": 10
|
||||
"spacing": 10,
|
||||
"icons": {
|
||||
"blueman": "bluetooth",
|
||||
"TelegramDesktop": "$HOME/.local/share/icons/hicolor/16x16/apps/telegram.png"
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
82
man/waybar-wayfire-window.5.scd
Normal file
82
man/waybar-wayfire-window.5.scd
Normal file
@@ -0,0 +1,82 @@
|
||||
waybar-wayfire-window(5)
|
||||
|
||||
# NAME
|
||||
|
||||
waybar - wayfire window module
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
The *window* module displays the title of the currently focused window in wayfire.
|
||||
|
||||
# CONFIGURATION
|
||||
|
||||
Addressed by *wayfire/window*
|
||||
|
||||
*format*: ++
|
||||
typeof: string ++
|
||||
default: {title} ++
|
||||
The format, how information should be displayed. On {} the current window title is displayed.
|
||||
|
||||
*rewrite*: ++
|
||||
typeof: object ++
|
||||
Rules to rewrite window title. See *rewrite rules*.
|
||||
|
||||
*icon*: ++
|
||||
typeof: bool ++
|
||||
default: false ++
|
||||
Option to hide the application icon.
|
||||
|
||||
*icon-size*: ++
|
||||
typeof: integer ++
|
||||
default: 24 ++
|
||||
Option to change the size of the application icon.
|
||||
|
||||
*expand*: ++
|
||||
typeof: bool ++
|
||||
default: false ++
|
||||
Enables this module to consume all left over space dynamically.
|
||||
|
||||
# FORMAT REPLACEMENTS
|
||||
|
||||
See the output of "wayfire msg windows" for examples
|
||||
|
||||
*{title}*: The current title of the focused window.
|
||||
|
||||
*{app_id}*: The current app ID of the focused window.
|
||||
|
||||
# REWRITE RULES
|
||||
|
||||
*rewrite* is an object where keys are regular expressions and values are
|
||||
rewrite rules if the expression matches. Rules may contain references to
|
||||
captures of the expression.
|
||||
|
||||
Regular expression and replacement follow ECMA-script rules.
|
||||
|
||||
If no expression matches, the title is left unchanged.
|
||||
|
||||
Invalid expressions (e.g., mismatched parentheses) are skipped.
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
```
|
||||
"wayfire/window": {
|
||||
"format": "{}",
|
||||
"rewrite": {
|
||||
"(.*) - Mozilla Firefox": "🌎 $1",
|
||||
"(.*) - zsh": "> [$1]"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
# STYLE
|
||||
|
||||
- *#window*
|
||||
- *window#waybar.empty #window* When no windows are on the workspace
|
||||
|
||||
The following classes are applied to the entire Waybar rather than just the
|
||||
window widget:
|
||||
|
||||
- *window#waybar.empty* When no windows are in the workspace
|
||||
- *window#waybar.solo* When only one window is on the workspace
|
||||
- *window#waybar.<app-id>* Where *app-id* is the app ID of the only window on
|
||||
the workspace
|
||||
86
man/waybar-wayfire-workspaces.5.scd
Normal file
86
man/waybar-wayfire-workspaces.5.scd
Normal file
@@ -0,0 +1,86 @@
|
||||
waybar-wayfire-workspaces(5)
|
||||
|
||||
# NAME
|
||||
|
||||
waybar - wayfire workspaces module
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
The *workspaces* module displays the currently used workspaces in wayfire.
|
||||
|
||||
# CONFIGURATION
|
||||
|
||||
Addressed by *wayfire/workspaces*
|
||||
|
||||
*format*: ++
|
||||
typeof: string ++
|
||||
default: {value} ++
|
||||
The format, how information should be displayed.
|
||||
|
||||
*format-icons*: ++
|
||||
typeof: array ++
|
||||
Based on the workspace name, index and state, the corresponding icon gets selected. See *icons*.
|
||||
|
||||
*disable-click*: ++
|
||||
typeof: bool ++
|
||||
default: false ++
|
||||
If set to false, you can click to change workspace. If set to true this behaviour is disabled.
|
||||
|
||||
*disable-markup*: ++
|
||||
typeof: bool ++
|
||||
default: false ++
|
||||
If set to true, button label will escape pango markup.
|
||||
|
||||
*current-only*: ++
|
||||
typeof: bool ++
|
||||
default: false ++
|
||||
If set to true, only the active or focused workspace will be shown.
|
||||
|
||||
*on-update*: ++
|
||||
typeof: string ++
|
||||
Command to execute when the module is updated.
|
||||
|
||||
*expand*: ++
|
||||
typeof: bool ++
|
||||
default: false ++
|
||||
Enables this module to consume all left over space dynamically.
|
||||
|
||||
# FORMAT REPLACEMENTS
|
||||
|
||||
*{icon}*: Icon, as defined in *format-icons*.
|
||||
|
||||
*{index}*: Index of the workspace on its output.
|
||||
|
||||
*{output}*: Output where the workspace is located.
|
||||
|
||||
# ICONS
|
||||
|
||||
Additional to workspace name matching, the following *format-icons* can be set.
|
||||
|
||||
- *default*: Will be shown, when no string matches are found.
|
||||
- *focused*: Will be shown, when workspace is focused.
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
```
|
||||
"wayfire/workspaces": {
|
||||
"format": "{icon}",
|
||||
"format-icons": {
|
||||
"1": "",
|
||||
"2": "",
|
||||
"3": "",
|
||||
"4": "",
|
||||
"5": "",
|
||||
"focused": "",
|
||||
"default": ""
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
# Style
|
||||
|
||||
- *#workspaces button*
|
||||
- *#workspaces button.focused*: The single focused workspace.
|
||||
- *#workspaces button.empty*: The workspace is empty.
|
||||
- *#workspaces button.current_output*: The workspace is from the same output as
|
||||
the bar that it is displayed on.
|
||||
@@ -19,6 +19,11 @@ The *wireplumber* module displays the current volume reported by WirePlumber.
|
||||
typeof: string ++
|
||||
This format is used when the sound is muted.
|
||||
|
||||
*node-type*: ++
|
||||
typeof: string ++
|
||||
default: *Audio/Sink* ++
|
||||
The WirePlumber node type to attach to. Use *Audio/Source* to manage microphones etc.
|
||||
|
||||
*tooltip*: ++
|
||||
typeof: bool ++
|
||||
default: *true* ++
|
||||
@@ -108,6 +113,8 @@ The *wireplumber* module displays the current volume reported by WirePlumber.
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
## Basic:
|
||||
|
||||
```
|
||||
"wireplumber": {
|
||||
"format": "{volume}%",
|
||||
@@ -116,6 +123,26 @@ The *wireplumber* module displays the current volume reported by WirePlumber.
|
||||
}
|
||||
```
|
||||
|
||||
## Separate Sink and Source Widgets
|
||||
|
||||
```
|
||||
"wireplumber#sink": {
|
||||
"format": "{volume}% {icon}",
|
||||
"format-muted": "",
|
||||
"format-icons": ["", "", ""],
|
||||
"on-click": "helvum",
|
||||
"on-click-right": "wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle",
|
||||
"scroll-step": 5
|
||||
},
|
||||
"wireplumber#source": {
|
||||
"node-type": "Audio/Source",
|
||||
"format": "{volume}% ",
|
||||
"format-muted": "",
|
||||
"on-click-right": "wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle",
|
||||
"scroll-step": 5
|
||||
}
|
||||
```
|
||||
|
||||
# STYLE
|
||||
|
||||
- *#wireplumber*
|
||||
|
||||
@@ -86,7 +86,7 @@ The visual display elements for waybar use a CSS stylesheet, see *waybar-styles(
|
||||
*no-center* ++
|
||||
typeof: bool ++
|
||||
default: false ++
|
||||
Option to disable the center modules fully usefull together with expand-\*.
|
||||
Option to disable the center modules fully useful together with expand-\*.
|
||||
|
||||
*spacing* ++
|
||||
typeof: integer ++
|
||||
@@ -272,6 +272,17 @@ When positioning Waybar on the left or right side of the screen, sometimes it's
|
||||
|
||||
Valid options for the "rotate" property are: 0, 90, 180, and 270.
|
||||
|
||||
## Swapping icon and label
|
||||
|
||||
If a module displays both a label and an icon, it might be desirable to swap them (for instance, for panels on the left or right of the screen, or for user adopting a right-to-left script). This can be achieved with the "swap-icon-label" property, taking a boolean. Example:
|
||||
```
|
||||
{
|
||||
"sway/window": {
|
||||
"swap-icon-label": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Grouping modules
|
||||
|
||||
Module groups allow stacking modules in any direction. By default, when the bar is positioned on the top or bottom of the screen, modules in a group are stacked vertically. Likewise, when positioned on the left or right, modules in a group are stacked horizontally. This can be changed with the "orientation" property.
|
||||
|
||||
28
meson.build
28
meson.build
@@ -1,6 +1,6 @@
|
||||
project(
|
||||
'waybar', 'cpp', 'c',
|
||||
version: '0.12.0',
|
||||
version: '0.13.0',
|
||||
license: 'MIT',
|
||||
meson_version: '>= 0.59.0',
|
||||
default_options : [
|
||||
@@ -93,6 +93,7 @@ libmpdclient = dependency('libmpdclient', required: get_option('mpd'))
|
||||
xkbregistry = dependency('xkbregistry')
|
||||
libjack = dependency('jack', required: get_option('jack'))
|
||||
libwireplumber = dependency('wireplumber-0.5', required: get_option('wireplumber'))
|
||||
libgps = dependency('libgps', required: get_option('gps'))
|
||||
|
||||
libsndio = compiler.find_library('sndio', required: get_option('sndio'))
|
||||
if libsndio.found()
|
||||
@@ -306,6 +307,7 @@ if true
|
||||
'src/modules/hyprland/language.cpp',
|
||||
'src/modules/hyprland/submap.cpp',
|
||||
'src/modules/hyprland/window.cpp',
|
||||
'src/modules/hyprland/windowcount.cpp',
|
||||
'src/modules/hyprland/workspace.cpp',
|
||||
'src/modules/hyprland/workspaces.cpp',
|
||||
'src/modules/hyprland/windowcreationpayload.cpp',
|
||||
@@ -333,6 +335,19 @@ if get_option('niri')
|
||||
)
|
||||
endif
|
||||
|
||||
if true
|
||||
add_project_arguments('-DHAVE_WAYFIRE', language: 'cpp')
|
||||
src_files += files(
|
||||
'src/modules/wayfire/backend.cpp',
|
||||
'src/modules/wayfire/window.cpp',
|
||||
'src/modules/wayfire/workspaces.cpp',
|
||||
)
|
||||
endif
|
||||
|
||||
if get_option('login-proxy')
|
||||
add_project_arguments('-DHAVE_LOGIN_PROXY', language: 'cpp')
|
||||
endif
|
||||
|
||||
if libnl.found() and libnlgen.found()
|
||||
add_project_arguments('-DHAVE_LIBNL', language: 'cpp')
|
||||
src_files += files('src/modules/network.cpp')
|
||||
@@ -482,7 +497,7 @@ if get_option('experimental')
|
||||
endif
|
||||
|
||||
cava = dependency('cava',
|
||||
version : '>=0.10.3',
|
||||
version : '>=0.10.4',
|
||||
required: get_option('cava'),
|
||||
fallback : ['cava', 'cava_dep'],
|
||||
not_found_message: 'cava is not found. Building waybar without cava')
|
||||
@@ -493,6 +508,12 @@ if cava.found()
|
||||
man_files += files('man/waybar-cava.5.scd')
|
||||
endif
|
||||
|
||||
if libgps.found()
|
||||
add_project_arguments('-DHAVE_LIBGPS', language: 'cpp')
|
||||
src_files += files('src/modules/gps.cpp')
|
||||
man_files += files('man/waybar-gps.5.scd')
|
||||
endif
|
||||
|
||||
subdir('protocol')
|
||||
|
||||
app_resources = []
|
||||
@@ -531,7 +552,8 @@ executable(
|
||||
libsndio,
|
||||
tz_dep,
|
||||
xkbregistry,
|
||||
cava
|
||||
cava,
|
||||
libgps
|
||||
],
|
||||
include_directories: inc_dirs,
|
||||
install: true,
|
||||
|
||||
@@ -20,3 +20,5 @@ option('jack', type: 'feature', value: 'auto', description: 'Enable support for
|
||||
option('wireplumber', type: 'feature', value: 'auto', description: 'Enable support for WirePlumber')
|
||||
option('cava', type: 'feature', value: 'auto', description: 'Enable support for Cava')
|
||||
option('niri', type: 'boolean', description: 'Enable support for niri')
|
||||
option('login-proxy', type: 'boolean', description: 'Enable interfacing with dbus login interface')
|
||||
option('gps', type: 'feature', value: 'auto', description: 'Enable support for gps')
|
||||
|
||||
@@ -1,42 +1,47 @@
|
||||
{ lib
|
||||
, pkgs
|
||||
, waybar
|
||||
, version
|
||||
{
|
||||
lib,
|
||||
pkgs,
|
||||
waybar,
|
||||
version,
|
||||
}:
|
||||
let
|
||||
libcava = rec {
|
||||
version = "0.10.3";
|
||||
version = "0.10.4";
|
||||
src = pkgs.fetchFromGitHub {
|
||||
owner = "LukashonakV";
|
||||
repo = "cava";
|
||||
rev = version;
|
||||
hash = "sha256-ZDFbI69ECsUTjbhlw2kHRufZbQMu+FQSMmncCJ5pagg=";
|
||||
tag = version;
|
||||
hash = "sha256-9eTDqM+O1tA/3bEfd1apm8LbEcR9CVgELTIspSVPMKM=";
|
||||
};
|
||||
};
|
||||
in
|
||||
(waybar.overrideAttrs (
|
||||
oldAttrs: {
|
||||
inherit version;
|
||||
(waybar.overrideAttrs (oldAttrs: {
|
||||
inherit version;
|
||||
|
||||
src = lib.cleanSourceWith {
|
||||
filter = name: type: type != "regular" || !lib.hasSuffix ".nix" name;
|
||||
src = lib.cleanSource ../.;
|
||||
};
|
||||
src = lib.cleanSourceWith {
|
||||
filter = name: type: type != "regular" || !lib.hasSuffix ".nix" name;
|
||||
src = lib.cleanSource ../.;
|
||||
};
|
||||
|
||||
mesonFlags = lib.remove "-Dgtk-layer-shell=enabled" oldAttrs.mesonFlags;
|
||||
mesonFlags = lib.remove "-Dgtk-layer-shell=enabled" oldAttrs.mesonFlags;
|
||||
|
||||
# downstream patch should not affect upstream
|
||||
patches = [];
|
||||
# downstream patch should not affect upstream
|
||||
patches = [ ];
|
||||
# nixpkgs checks version, no need when building locally
|
||||
nativeInstallCheckInputs = [ ];
|
||||
|
||||
buildInputs = (builtins.filter (p: p.pname != "wireplumber") oldAttrs.buildInputs) ++ [
|
||||
pkgs.wireplumber
|
||||
];
|
||||
buildInputs = (builtins.filter (p:
|
||||
p.pname != "wireplumber" &&
|
||||
p.pname != "gps"
|
||||
) oldAttrs.buildInputs) ++ [
|
||||
pkgs.wireplumber
|
||||
pkgs.gpsd
|
||||
];
|
||||
|
||||
postUnpack = ''
|
||||
pushd "$sourceRoot"
|
||||
cp -R --no-preserve=mode,ownership ${libcava.src} subprojects/cava-${libcava.version}
|
||||
patchShebangs .
|
||||
popd
|
||||
'';
|
||||
}
|
||||
))
|
||||
postUnpack = ''
|
||||
pushd "$sourceRoot"
|
||||
cp -R --no-preserve=mode,ownership ${libcava.src} subprojects/cava-${libcava.version}
|
||||
patchShebangs .
|
||||
popd
|
||||
'';
|
||||
}))
|
||||
|
||||
@@ -104,7 +104,11 @@
|
||||
},
|
||||
"tray": {
|
||||
// "icon-size": 21,
|
||||
"spacing": 10
|
||||
"spacing": 10,
|
||||
// "icons": {
|
||||
// "blueman": "bluetooth",
|
||||
// "TelegramDesktop": "$HOME/.local/share/icons/hicolor/16x16/apps/telegram.png"
|
||||
// }
|
||||
},
|
||||
"clock": {
|
||||
// "timezone": "America/New_York",
|
||||
|
||||
@@ -17,7 +17,7 @@ void onclicked(GtkButton* button) {
|
||||
}
|
||||
|
||||
// You must
|
||||
const size_t wbcffi_version = 1;
|
||||
const size_t wbcffi_version = 2;
|
||||
|
||||
void* wbcffi_init(const wbcffi_init_info* init_info, const wbcffi_config_entry* config_entries,
|
||||
size_t config_entries_len) {
|
||||
@@ -67,4 +67,4 @@ void wbcffi_refresh(void* instance, int signal) {
|
||||
|
||||
void wbcffi_doaction(void* instance, const char* name) {
|
||||
printf("cffi_example inst=%p: doAction(%s)\n", instance, name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/// Waybar ABI version. 1 is the latest version
|
||||
/// Waybar ABI version. 2 is the latest version
|
||||
extern const size_t wbcffi_version;
|
||||
|
||||
/// Private Waybar CFFI module
|
||||
@@ -35,7 +35,13 @@ typedef struct {
|
||||
typedef struct {
|
||||
/// Entry key
|
||||
const char* key;
|
||||
/// Entry value as string. JSON object and arrays are serialized.
|
||||
/// Entry value
|
||||
///
|
||||
/// In ABI version 1, this may be either a bare string if the value is a
|
||||
/// string, or the JSON representation of any other JSON object as a string.
|
||||
///
|
||||
/// From ABI version 2 onwards, this is always the JSON representation of the
|
||||
/// value as a string.
|
||||
const char* value;
|
||||
} wbcffi_config_entry;
|
||||
|
||||
|
||||
@@ -112,6 +112,7 @@ class PlayerManager:
|
||||
logger.debug(f"Metadata changed for player {player.props.player_name}")
|
||||
player_name = player.props.player_name
|
||||
artist = player.get_artist()
|
||||
artist = artist.replace("&", "&")
|
||||
title = player.get_title()
|
||||
title = title.replace("&", "&")
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[Unit]
|
||||
Description=Highly customizable Wayland bar for Sway and Wlroots based compositors.
|
||||
Description=Highly customizable Wayland bar for Sway and Wlroots based compositors
|
||||
Documentation=https://github.com/Alexays/Waybar/wiki/
|
||||
PartOf=graphical-session.target
|
||||
After=graphical-session.target
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "AIconLabel.hpp"
|
||||
|
||||
#include <gdkmm/pixbuf.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
namespace waybar {
|
||||
|
||||
@@ -17,14 +18,36 @@ AIconLabel::AIconLabel(const Json::Value &config, const std::string &name, const
|
||||
box_.get_style_context()->add_class(id);
|
||||
}
|
||||
|
||||
box_.set_orientation(Gtk::Orientation::ORIENTATION_HORIZONTAL);
|
||||
int rot = 0;
|
||||
|
||||
if (config_["rotate"].isUInt()) {
|
||||
rot = config["rotate"].asUInt() % 360;
|
||||
if ((rot % 90) != 00) rot = 0;
|
||||
rot /= 90;
|
||||
}
|
||||
|
||||
if ((rot % 2) == 0)
|
||||
box_.set_orientation(Gtk::Orientation::ORIENTATION_HORIZONTAL);
|
||||
else
|
||||
box_.set_orientation(Gtk::Orientation::ORIENTATION_VERTICAL);
|
||||
box_.set_name(name);
|
||||
|
||||
int spacing = config_["icon-spacing"].isInt() ? config_["icon-spacing"].asInt() : 8;
|
||||
box_.set_spacing(spacing);
|
||||
|
||||
box_.add(image_);
|
||||
box_.add(label_);
|
||||
bool swap_icon_label = false;
|
||||
if (not config_["swap-icon-label"].isBool())
|
||||
spdlog::warn("'swap-icon-label' must be a bool.");
|
||||
else
|
||||
swap_icon_label = config_["swap-icon-label"].asBool();
|
||||
|
||||
if ((rot == 0 || rot == 3) ^ swap_icon_label) {
|
||||
box_.add(image_);
|
||||
box_.add(label_);
|
||||
} else {
|
||||
box_.add(label_);
|
||||
box_.add(image_);
|
||||
}
|
||||
|
||||
event_box_.add(box_);
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ AModule::AModule(const Json::Value& config, const std::string& name, const std::
|
||||
}
|
||||
|
||||
AModule::~AModule() {
|
||||
for (const auto& pid : pid_) {
|
||||
for (const auto& pid : pid_children_) {
|
||||
if (pid != -1) {
|
||||
killpg(pid, SIGTERM);
|
||||
}
|
||||
@@ -93,15 +93,15 @@ AModule::~AModule() {
|
||||
auto AModule::update() -> void {
|
||||
// Run user-provided update handler if configured
|
||||
if (config_["on-update"].isString()) {
|
||||
pid_.push_back(util::command::forkExec(config_["on-update"].asString()));
|
||||
pid_children_.push_back(util::command::forkExec(config_["on-update"].asString()));
|
||||
}
|
||||
}
|
||||
// Get mapping between event name and module action name
|
||||
// Then call overrided doAction in order to call appropriate module action
|
||||
// Then call overridden doAction in order to call appropriate module action
|
||||
auto AModule::doAction(const std::string& name) -> void {
|
||||
if (!name.empty()) {
|
||||
const std::map<std::string, std::string>::const_iterator& recA{eventActionMap_.find(name)};
|
||||
// Call overrided action if derrived class has implemented it
|
||||
// Call overridden action if derived class has implemented it
|
||||
if (recA != eventActionMap_.cend() && name != recA->second) this->doAction(recA->second);
|
||||
}
|
||||
}
|
||||
@@ -182,7 +182,7 @@ bool AModule::handleUserEvent(GdkEventButton* const& e) {
|
||||
format.clear();
|
||||
}
|
||||
if (!format.empty()) {
|
||||
pid_.push_back(util::command::forkExec(format));
|
||||
pid_children_.push_back(util::command::forkExec(format));
|
||||
}
|
||||
dp.emit();
|
||||
return true;
|
||||
@@ -267,7 +267,7 @@ bool AModule::handleScroll(GdkEventScroll* e) {
|
||||
this->AModule::doAction(eventName);
|
||||
// Second call user scripts
|
||||
if (config_[eventName].isString())
|
||||
pid_.push_back(util::command::forkExec(config_[eventName].asString()));
|
||||
pid_children_.push_back(util::command::forkExec(config_[eventName].asString()));
|
||||
|
||||
dp.emit();
|
||||
return true;
|
||||
|
||||
@@ -545,7 +545,6 @@ auto waybar::Bar::setupWidgets() -> void {
|
||||
if (config["fixed-center"].isBool() ? config["fixed-center"].asBool() : true) {
|
||||
box_.set_center_widget(center_);
|
||||
} else {
|
||||
spdlog::error("No fixed center_");
|
||||
box_.pack_start(center_, true, expand_center);
|
||||
}
|
||||
}
|
||||
@@ -569,13 +568,13 @@ auto waybar::Bar::setupWidgets() -> void {
|
||||
|
||||
if (!no_center) {
|
||||
for (auto const& module : modules_center_) {
|
||||
center_.pack_start(*module, false, false);
|
||||
center_.pack_start(*module, module->expandEnabled(), module->expandEnabled());
|
||||
}
|
||||
}
|
||||
|
||||
std::reverse(modules_right_.begin(), modules_right_.end());
|
||||
for (auto const& module : modules_right_) {
|
||||
right_.pack_end(*module, false, false);
|
||||
right_.pack_end(*module, module->expandEnabled(), module->expandEnabled());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ void waybar::Client::handleOutputDone(void *data, struct zxdg_output_v1 * /*xdg_
|
||||
}
|
||||
}
|
||||
} catch (const std::exception &e) {
|
||||
std::cerr << e.what() << '\n';
|
||||
spdlog::warn("caught exception in zxdg_output_v1_listener::done: {}", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ void waybar::Client::handleOutputName(void *data, struct zxdg_output_v1 * /*xdg_
|
||||
auto &output = client->getOutput(data);
|
||||
output.name = name;
|
||||
} catch (const std::exception &e) {
|
||||
std::cerr << e.what() << '\n';
|
||||
spdlog::warn("caught exception in zxdg_output_v1_listener::name: {}", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,13 +106,13 @@ void waybar::Client::handleOutputDescription(void *data, struct zxdg_output_v1 *
|
||||
auto *client = waybar::Client::inst();
|
||||
try {
|
||||
auto &output = client->getOutput(data);
|
||||
const char *open_paren = strrchr(description, '(');
|
||||
|
||||
// Description format: "identifier (name)"
|
||||
size_t identifier_length = open_paren - description;
|
||||
output.identifier = std::string(description, identifier_length - 1);
|
||||
auto s = std::string(description);
|
||||
auto pos = s.find(" (");
|
||||
output.identifier = pos != std::string::npos ? s.substr(0, pos) : s;
|
||||
} catch (const std::exception &e) {
|
||||
std::cerr << e.what() << '\n';
|
||||
spdlog::warn("caught exception in zxdg_output_v1_listener::description: {}", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
#include "modules/hyprland/language.hpp"
|
||||
#include "modules/hyprland/submap.hpp"
|
||||
#include "modules/hyprland/window.hpp"
|
||||
#include "modules/hyprland/windowcount.hpp"
|
||||
#include "modules/hyprland/workspaces.hpp"
|
||||
#endif
|
||||
#ifdef HAVE_NIRI
|
||||
@@ -41,6 +42,10 @@
|
||||
#include "modules/niri/window.hpp"
|
||||
#include "modules/niri/workspaces.hpp"
|
||||
#endif
|
||||
#ifdef HAVE_WAYFIRE
|
||||
#include "modules/wayfire/window.hpp"
|
||||
#include "modules/wayfire/workspaces.hpp"
|
||||
#endif
|
||||
#if defined(__FreeBSD__) || defined(__linux__)
|
||||
#include "modules/battery.hpp"
|
||||
#endif
|
||||
@@ -109,6 +114,9 @@
|
||||
#ifdef HAVE_SYSTEMD_MONITOR
|
||||
#include "modules/systemd_failed_units.hpp"
|
||||
#endif
|
||||
#ifdef HAVE_LIBGPS
|
||||
#include "modules/gps.hpp"
|
||||
#endif
|
||||
#include "modules/cffi.hpp"
|
||||
#include "modules/custom.hpp"
|
||||
#include "modules/image.hpp"
|
||||
@@ -140,7 +148,7 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name,
|
||||
#endif
|
||||
#ifdef HAVE_PIPEWIRE
|
||||
if (ref == "privacy") {
|
||||
return new waybar::modules::privacy::Privacy(id, config_[name], pos);
|
||||
return new waybar::modules::privacy::Privacy(id, config_[name], bar_.orientation, pos);
|
||||
}
|
||||
#endif
|
||||
#ifdef HAVE_MPRIS
|
||||
@@ -201,6 +209,9 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name,
|
||||
if (ref == "hyprland/window") {
|
||||
return new waybar::modules::hyprland::Window(id, bar_, config_[name]);
|
||||
}
|
||||
if (ref == "hyprland/windowcount") {
|
||||
return new waybar::modules::hyprland::WindowCount(id, bar_, config_[name]);
|
||||
}
|
||||
if (ref == "hyprland/language") {
|
||||
return new waybar::modules::hyprland::Language(id, bar_, config_[name]);
|
||||
}
|
||||
@@ -221,6 +232,14 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name,
|
||||
if (ref == "niri/workspaces") {
|
||||
return new waybar::modules::niri::Workspaces(id, bar_, config_[name]);
|
||||
}
|
||||
#endif
|
||||
#ifdef HAVE_WAYFIRE
|
||||
if (ref == "wayfire/window") {
|
||||
return new waybar::modules::wayfire::Window(id, bar_, config_[name]);
|
||||
}
|
||||
if (ref == "wayfire/workspaces") {
|
||||
return new waybar::modules::wayfire::Workspaces(id, bar_, config_[name]);
|
||||
}
|
||||
#endif
|
||||
if (ref == "idle_inhibitor") {
|
||||
return new waybar::modules::IdleInhibitor(id, bar_, config_[name]);
|
||||
@@ -331,6 +350,11 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name,
|
||||
if (ref == "systemd-failed-units") {
|
||||
return new waybar::modules::SystemdFailedUnits(id, config_[name]);
|
||||
}
|
||||
#endif
|
||||
#ifdef HAVE_LIBGPS
|
||||
if (ref == "gps") {
|
||||
return new waybar::modules::Gps(id, config_[name]);
|
||||
}
|
||||
#endif
|
||||
if (ref == "temperature") {
|
||||
return new waybar::modules::Temperature(id, config_[name]);
|
||||
|
||||
170
src/main.cpp
170
src/main.cpp
@@ -1,3 +1,4 @@
|
||||
#include <fcntl.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
@@ -7,66 +8,115 @@
|
||||
#include <mutex>
|
||||
|
||||
#include "client.hpp"
|
||||
#include "util/SafeSignal.hpp"
|
||||
|
||||
std::mutex reap_mtx;
|
||||
std::list<pid_t> reap;
|
||||
volatile bool reload;
|
||||
|
||||
void* signalThread(void* args) {
|
||||
int err;
|
||||
int signum;
|
||||
sigset_t mask;
|
||||
sigemptyset(&mask);
|
||||
sigaddset(&mask, SIGCHLD);
|
||||
static int signal_pipe_write_fd;
|
||||
|
||||
// Write a single signal to `signal_pipe_write_fd`.
|
||||
// This function is set as a signal handler, so it must be async-signal-safe.
|
||||
static void writeSignalToPipe(int signum) {
|
||||
ssize_t amt = write(signal_pipe_write_fd, &signum, sizeof(int));
|
||||
|
||||
// There's not much we can safely do inside of a signal handler.
|
||||
// Let's just ignore any errors.
|
||||
(void)amt;
|
||||
}
|
||||
|
||||
// This initializes `signal_pipe_write_fd`, and sets up signal handlers.
|
||||
//
|
||||
// This function will run forever, emitting every `SIGUSR1`, `SIGUSR2`,
|
||||
// `SIGINT`, `SIGCHLD`, and `SIGRTMIN + 1`...`SIGRTMAX` signal received
|
||||
// to `signal_handler`.
|
||||
static void catchSignals(waybar::SafeSignal<int>& signal_handler) {
|
||||
int fd[2];
|
||||
pipe(fd);
|
||||
|
||||
int signal_pipe_read_fd = fd[0];
|
||||
signal_pipe_write_fd = fd[1];
|
||||
|
||||
// This pipe should be able to buffer ~thousands of signals. If it fills up,
|
||||
// we'll drop signals instead of blocking.
|
||||
|
||||
// We can't allow the write end to block because we'll be writing to it in a
|
||||
// signal handler, which could interrupt the loop that's reading from it and
|
||||
// deadlock.
|
||||
|
||||
fcntl(signal_pipe_write_fd, F_SETFL, O_NONBLOCK);
|
||||
|
||||
std::signal(SIGUSR1, writeSignalToPipe);
|
||||
std::signal(SIGUSR2, writeSignalToPipe);
|
||||
std::signal(SIGINT, writeSignalToPipe);
|
||||
std::signal(SIGCHLD, writeSignalToPipe);
|
||||
|
||||
for (int sig = SIGRTMIN + 1; sig <= SIGRTMAX; ++sig) {
|
||||
std::signal(sig, writeSignalToPipe);
|
||||
}
|
||||
|
||||
while (true) {
|
||||
err = sigwait(&mask, &signum);
|
||||
if (err != 0) {
|
||||
spdlog::error("sigwait failed: {}", strerror(errno));
|
||||
int signum;
|
||||
ssize_t amt = read(signal_pipe_read_fd, &signum, sizeof(int));
|
||||
if (amt < 0) {
|
||||
spdlog::error("read from signal pipe failed with error {}, closing thread", strerror(errno));
|
||||
break;
|
||||
}
|
||||
|
||||
if (amt != sizeof(int)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (signum) {
|
||||
case SIGCHLD:
|
||||
spdlog::debug("Received SIGCHLD in signalThread");
|
||||
if (!reap.empty()) {
|
||||
reap_mtx.lock();
|
||||
for (auto it = reap.begin(); it != reap.end(); ++it) {
|
||||
if (waitpid(*it, nullptr, WNOHANG) == *it) {
|
||||
spdlog::debug("Reaped child with PID: {}", *it);
|
||||
it = reap.erase(it);
|
||||
}
|
||||
}
|
||||
reap_mtx.unlock();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
spdlog::debug("Received signal with number {}, but not handling", signum);
|
||||
break;
|
||||
}
|
||||
signal_handler.emit(signum);
|
||||
}
|
||||
}
|
||||
|
||||
void startSignalThread() {
|
||||
int err;
|
||||
sigset_t mask;
|
||||
sigemptyset(&mask);
|
||||
sigaddset(&mask, SIGCHLD);
|
||||
// Must be called on the main thread.
|
||||
//
|
||||
// If this signal should restart or close the bar, this function will write
|
||||
// `true` or `false`, respectively, into `reload`.
|
||||
static void handleSignalMainThread(int signum, bool& reload) {
|
||||
if (signum >= SIGRTMIN + 1 && signum <= SIGRTMAX) {
|
||||
for (auto& bar : waybar::Client::inst()->bars) {
|
||||
bar->handleSignal(signum);
|
||||
}
|
||||
|
||||
// Block SIGCHLD so it can be handled by the signal thread
|
||||
// Any threads created by this one (the main thread) should not
|
||||
// modify their signal mask to unblock SIGCHLD
|
||||
err = pthread_sigmask(SIG_BLOCK, &mask, nullptr);
|
||||
if (err != 0) {
|
||||
spdlog::error("pthread_sigmask failed in startSignalThread: {}", strerror(err));
|
||||
exit(1);
|
||||
return;
|
||||
}
|
||||
|
||||
pthread_t thread_id;
|
||||
err = pthread_create(&thread_id, nullptr, signalThread, nullptr);
|
||||
if (err != 0) {
|
||||
spdlog::error("pthread_create failed in startSignalThread: {}", strerror(err));
|
||||
exit(1);
|
||||
switch (signum) {
|
||||
case SIGUSR1:
|
||||
spdlog::debug("Visibility toggled");
|
||||
for (auto& bar : waybar::Client::inst()->bars) {
|
||||
bar->toggle();
|
||||
}
|
||||
break;
|
||||
case SIGUSR2:
|
||||
spdlog::info("Reloading...");
|
||||
reload = true;
|
||||
waybar::Client::inst()->reset();
|
||||
break;
|
||||
case SIGINT:
|
||||
spdlog::info("Quitting.");
|
||||
reload = false;
|
||||
waybar::Client::inst()->reset();
|
||||
break;
|
||||
case SIGCHLD:
|
||||
spdlog::debug("Received SIGCHLD in signalThread");
|
||||
if (!reap.empty()) {
|
||||
reap_mtx.lock();
|
||||
for (auto it = reap.begin(); it != reap.end(); ++it) {
|
||||
if (waitpid(*it, nullptr, WNOHANG) == *it) {
|
||||
spdlog::debug("Reaped child with PID: {}", *it);
|
||||
it = reap.erase(it);
|
||||
}
|
||||
}
|
||||
reap_mtx.unlock();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
spdlog::debug("Received signal with number {}, but not handling", signum);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,32 +124,16 @@ int main(int argc, char* argv[]) {
|
||||
try {
|
||||
auto* client = waybar::Client::inst();
|
||||
|
||||
std::signal(SIGUSR1, [](int /*signal*/) {
|
||||
for (auto& bar : waybar::Client::inst()->bars) {
|
||||
bar->toggle();
|
||||
}
|
||||
});
|
||||
bool reload;
|
||||
|
||||
std::signal(SIGUSR2, [](int /*signal*/) {
|
||||
spdlog::info("Reloading...");
|
||||
reload = true;
|
||||
waybar::Client::inst()->reset();
|
||||
});
|
||||
waybar::SafeSignal<int> posix_signal_received;
|
||||
posix_signal_received.connect([&](int signum) { handleSignalMainThread(signum, reload); });
|
||||
|
||||
std::signal(SIGINT, [](int /*signal*/) {
|
||||
spdlog::info("Quitting.");
|
||||
reload = false;
|
||||
waybar::Client::inst()->reset();
|
||||
});
|
||||
std::thread signal_thread([&]() { catchSignals(posix_signal_received); });
|
||||
|
||||
for (int sig = SIGRTMIN + 1; sig <= SIGRTMAX; ++sig) {
|
||||
std::signal(sig, [](int sig) {
|
||||
for (auto& bar : waybar::Client::inst()->bars) {
|
||||
bar->handleSignal(sig);
|
||||
}
|
||||
});
|
||||
}
|
||||
startSignalThread();
|
||||
// Every `std::thread` must be joined or detached.
|
||||
// This thread should run forever, so detach it.
|
||||
signal_thread.detach();
|
||||
|
||||
auto ret = 0;
|
||||
do {
|
||||
|
||||
@@ -28,7 +28,7 @@ CFFI::CFFI(const std::string& name, const std::string& id, const Json::Value& co
|
||||
}
|
||||
|
||||
// Fetch functions
|
||||
if (*wbcffi_version == 1) {
|
||||
if (*wbcffi_version == 1 || *wbcffi_version == 2) {
|
||||
// Mandatory functions
|
||||
hooks_.init = reinterpret_cast<InitFn*>(dlsym(handle, "wbcffi_init"));
|
||||
if (!hooks_.init) {
|
||||
@@ -58,10 +58,14 @@ CFFI::CFFI(const std::string& name, const std::string& id, const Json::Value& co
|
||||
const auto& keys = config.getMemberNames();
|
||||
for (size_t i = 0; i < keys.size(); i++) {
|
||||
const auto& value = config[keys[i]];
|
||||
if (value.isConvertibleTo(Json::ValueType::stringValue)) {
|
||||
config_entries_stringstor.push_back(config[keys[i]].asString());
|
||||
if (*wbcffi_version == 1) {
|
||||
if (value.isConvertibleTo(Json::ValueType::stringValue)) {
|
||||
config_entries_stringstor.push_back(value.asString());
|
||||
} else {
|
||||
config_entries_stringstor.push_back(value.toStyledString());
|
||||
}
|
||||
} else {
|
||||
config_entries_stringstor.push_back(config[keys[i]].toStyledString());
|
||||
config_entries_stringstor.push_back(value.toStyledString());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "modules/clock.hpp"
|
||||
|
||||
#include <glib.h>
|
||||
#include <gtkmm/tooltip.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
@@ -16,6 +17,7 @@
|
||||
#include <clocale>
|
||||
#endif
|
||||
|
||||
using namespace date;
|
||||
namespace fmt_lib = waybar::util::date::format;
|
||||
|
||||
waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
|
||||
@@ -25,6 +27,7 @@ waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
|
||||
m_tooltip_{new Gtk::Label()},
|
||||
cldInTooltip_{m_tlpFmt_.find("{" + kCldPlaceholder + "}") != std::string::npos},
|
||||
cldYearShift_{January / 1 / 1900},
|
||||
cldMonShift_{year(1900) / January},
|
||||
tzInTooltip_{m_tlpFmt_.find("{" + kTZPlaceholder + "}") != std::string::npos},
|
||||
tzCurrIdx_{0},
|
||||
ordInTooltip_{m_tlpFmt_.find("{" + kOrdPlaceholder + "}") != std::string::npos} {
|
||||
@@ -348,9 +351,9 @@ auto waybar::modules::Clock::get_calendar(const year_month_day& today, const yea
|
||||
m_locale_, fmtMap_[4],
|
||||
fmt_lib::make_format_args(
|
||||
(line == 2)
|
||||
? static_cast<const date::zoned_seconds&&>(
|
||||
? static_cast<const zoned_seconds&&>(
|
||||
zoned_seconds{tz, local_days{ymTmp / 1}})
|
||||
: static_cast<const date::zoned_seconds&&>(zoned_seconds{
|
||||
: static_cast<const zoned_seconds&&>(zoned_seconds{
|
||||
tz, local_days{cldGetWeekForLine(ymTmp, firstdow, line)}})))
|
||||
<< ' ';
|
||||
} else
|
||||
@@ -358,10 +361,23 @@ auto waybar::modules::Clock::get_calendar(const year_month_day& today, const yea
|
||||
}
|
||||
}
|
||||
|
||||
os << Glib::ustring::format((cldWPos_ != WS::LEFT || line == 0) ? std::left : std::right,
|
||||
std::setfill(L' '),
|
||||
std::setw(cldMonColLen_ + ((line < 2) ? cldWnLen_ : 0)),
|
||||
getCalendarLine(today, ymTmp, line, firstdow, &m_locale_));
|
||||
// Count wide characters to avoid extra padding
|
||||
size_t wideCharCount = 0;
|
||||
std::string calendarLine = getCalendarLine(today, ymTmp, line, firstdow, &m_locale_);
|
||||
if (line < 2) {
|
||||
for (gchar *data = calendarLine.data(), *end = data + calendarLine.size();
|
||||
data != nullptr;) {
|
||||
gunichar c = g_utf8_get_char_validated(data, end - data);
|
||||
if (g_unichar_iswide(c)) {
|
||||
wideCharCount++;
|
||||
}
|
||||
data = g_utf8_find_next_char(data, end);
|
||||
}
|
||||
}
|
||||
os << Glib::ustring::format(
|
||||
(cldWPos_ != WS::LEFT || line == 0) ? std::left : std::right, std::setfill(L' '),
|
||||
std::setw(cldMonColLen_ + ((line < 2) ? cldWnLen_ - wideCharCount : 0)),
|
||||
calendarLine);
|
||||
|
||||
// Week numbers on the right
|
||||
if (cldWPos_ == WS::RIGHT && line > 0) {
|
||||
@@ -371,9 +387,9 @@ auto waybar::modules::Clock::get_calendar(const year_month_day& today, const yea
|
||||
<< fmt_lib::vformat(
|
||||
m_locale_, fmtMap_[4],
|
||||
fmt_lib::make_format_args(
|
||||
(line == 2) ? static_cast<const date::zoned_seconds&&>(
|
||||
(line == 2) ? static_cast<const zoned_seconds&&>(
|
||||
zoned_seconds{tz, local_days{ymTmp / 1}})
|
||||
: static_cast<const date::zoned_seconds&&>(
|
||||
: static_cast<const zoned_seconds&&>(
|
||||
zoned_seconds{tz, local_days{cldGetWeekForLine(
|
||||
ymTmp, firstdow, line)}})));
|
||||
else
|
||||
|
||||
@@ -35,6 +35,13 @@ waybar::modules::Custom::~Custom() {
|
||||
|
||||
void waybar::modules::Custom::delayWorker() {
|
||||
thread_ = [this] {
|
||||
for (int i : this->pid_children_) {
|
||||
int status;
|
||||
waitpid(i, &status, 0);
|
||||
}
|
||||
|
||||
this->pid_children_.clear();
|
||||
|
||||
bool can_update = true;
|
||||
if (config_["exec-if"].isString()) {
|
||||
output_ = util::command::execNoRead(config_["exec-if"].asString());
|
||||
@@ -62,7 +69,7 @@ void waybar::modules::Custom::continuousWorker() {
|
||||
}
|
||||
thread_ = [this, cmd] {
|
||||
char* buff = nullptr;
|
||||
waybar::util::ScopeGuard buff_deleter([buff]() {
|
||||
waybar::util::ScopeGuard buff_deleter([&buff]() {
|
||||
if (buff) {
|
||||
free(buff);
|
||||
}
|
||||
|
||||
216
src/modules/gps.cpp
Normal file
216
src/modules/gps.cpp
Normal file
@@ -0,0 +1,216 @@
|
||||
#include "modules/gps.hpp"
|
||||
|
||||
#include <gps.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
|
||||
// In the 80000 version of fmt library authors decided to optimize imports
|
||||
// and moved declarations required for fmt::dynamic_format_arg_store in new
|
||||
// header fmt/args.h
|
||||
#if (FMT_VERSION >= 80000)
|
||||
#include <fmt/args.h>
|
||||
#else
|
||||
#include <fmt/core.h>
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
using namespace waybar::util;
|
||||
constexpr const char* DEFAULT_FORMAT = "{mode}";
|
||||
} // namespace
|
||||
|
||||
waybar::modules::Gps::Gps(const std::string& id, const Json::Value& config)
|
||||
: ALabel(config, "gps", id, "{}", 5)
|
||||
#ifdef WANT_RFKILL
|
||||
,
|
||||
rfkill_{RFKILL_TYPE_GPS}
|
||||
#endif
|
||||
{
|
||||
thread_ = [this] {
|
||||
dp.emit();
|
||||
thread_.sleep_for(interval_);
|
||||
};
|
||||
|
||||
if (0 != gps_open("localhost", "2947", &gps_data_)) {
|
||||
throw std::runtime_error("Can't open gpsd socket");
|
||||
}
|
||||
|
||||
if (config_["hide-disconnected"].isBool()) {
|
||||
hideDisconnected = config_["hide-disconnected"].asBool();
|
||||
}
|
||||
|
||||
if (config_["hide-no-fix"].isBool()) {
|
||||
hideNoFix = config_["hide-no-fix"].asBool();
|
||||
}
|
||||
|
||||
gps_thread_ = [this] {
|
||||
dp.emit();
|
||||
gps_stream(&gps_data_, WATCH_ENABLE, NULL);
|
||||
int last_gps_mode = 0;
|
||||
|
||||
while (gps_waiting(&gps_data_, 5000000)) {
|
||||
if (gps_read(&gps_data_, NULL, 0) == -1) {
|
||||
throw std::runtime_error("Can't read data from gpsd.");
|
||||
}
|
||||
|
||||
if (MODE_SET != (MODE_SET & gps_data_.set)) {
|
||||
// did not even get mode, nothing to see here
|
||||
continue;
|
||||
}
|
||||
|
||||
if (gps_data_.fix.mode != last_gps_mode) {
|
||||
// significant update
|
||||
dp.emit();
|
||||
}
|
||||
last_gps_mode = gps_data_.fix.mode;
|
||||
}
|
||||
};
|
||||
|
||||
#ifdef WANT_RFKILL
|
||||
rfkill_.on_update.connect(sigc::hide(sigc::mem_fun(*this, &Gps::update)));
|
||||
#endif
|
||||
}
|
||||
|
||||
const std::string waybar::modules::Gps::getFixModeName() const {
|
||||
switch (gps_data_.fix.mode) {
|
||||
case MODE_NO_FIX:
|
||||
return "fix-none";
|
||||
case MODE_2D:
|
||||
return "fix-2d";
|
||||
case MODE_3D:
|
||||
return "fix-3d";
|
||||
default:
|
||||
#ifdef WANT_RFKILL
|
||||
if (rfkill_.getState()) return "disabled";
|
||||
#endif
|
||||
return "disconnected";
|
||||
}
|
||||
}
|
||||
|
||||
const std::string waybar::modules::Gps::getFixModeString() const {
|
||||
switch (gps_data_.fix.mode) {
|
||||
case MODE_NO_FIX:
|
||||
return "No fix";
|
||||
case MODE_2D:
|
||||
return "2D Fix";
|
||||
case MODE_3D:
|
||||
return "3D Fix";
|
||||
default:
|
||||
return "Disconnected";
|
||||
}
|
||||
}
|
||||
|
||||
const std::string waybar::modules::Gps::getFixStatusString() const {
|
||||
switch (gps_data_.fix.status) {
|
||||
case STATUS_GPS:
|
||||
return "GPS";
|
||||
case STATUS_DGPS:
|
||||
return "DGPS";
|
||||
case STATUS_RTK_FIX:
|
||||
return "RTK Fixed";
|
||||
case STATUS_RTK_FLT:
|
||||
return "RTK Float";
|
||||
case STATUS_DR:
|
||||
return "Dead Reckoning";
|
||||
case STATUS_GNSSDR:
|
||||
return "GNSS + Dead Reckoning";
|
||||
case STATUS_TIME:
|
||||
return "Time Only";
|
||||
case STATUS_PPS_FIX:
|
||||
return "PPS Fix";
|
||||
default:
|
||||
|
||||
#ifdef WANT_RFKILL
|
||||
if (rfkill_.getState()) return "Disabled";
|
||||
#endif
|
||||
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
auto waybar::modules::Gps::update() -> void {
|
||||
sleep(0); // Wait for gps status change
|
||||
|
||||
if ((gps_data_.fix.mode == MODE_NOT_SEEN && hideDisconnected) ||
|
||||
(gps_data_.fix.mode == MODE_NO_FIX && hideNoFix)) {
|
||||
event_box_.set_visible(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Show the module
|
||||
if (!event_box_.get_visible()) event_box_.set_visible(true);
|
||||
|
||||
std::string tooltip_format;
|
||||
|
||||
if (!alt_) {
|
||||
auto state = getFixModeName();
|
||||
if (!state_.empty() && label_.get_style_context()->has_class(state_)) {
|
||||
label_.get_style_context()->remove_class(state_);
|
||||
}
|
||||
if (config_["format-" + state].isString()) {
|
||||
default_format_ = config_["format-" + state].asString();
|
||||
} else if (config_["format"].isString()) {
|
||||
default_format_ = config_["format"].asString();
|
||||
} else {
|
||||
default_format_ = DEFAULT_FORMAT;
|
||||
}
|
||||
if (config_["tooltip-format-" + state].isString()) {
|
||||
tooltip_format = config_["tooltip-format-" + state].asString();
|
||||
}
|
||||
if (!label_.get_style_context()->has_class(state)) {
|
||||
label_.get_style_context()->add_class(state);
|
||||
}
|
||||
format_ = default_format_;
|
||||
state_ = state;
|
||||
}
|
||||
|
||||
auto format = format_;
|
||||
|
||||
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||
store.push_back(fmt::arg("mode", getFixModeString()));
|
||||
store.push_back(fmt::arg("status", getFixStatusString()));
|
||||
|
||||
store.push_back(fmt::arg("latitude", gps_data_.fix.latitude));
|
||||
store.push_back(fmt::arg("latitude_error", gps_data_.fix.epy));
|
||||
|
||||
store.push_back(fmt::arg("longitude", gps_data_.fix.longitude));
|
||||
store.push_back(fmt::arg("longitude_error", gps_data_.fix.epx));
|
||||
|
||||
store.push_back(fmt::arg("altitude_hae", gps_data_.fix.altHAE));
|
||||
store.push_back(fmt::arg("altitude_msl", gps_data_.fix.altMSL));
|
||||
store.push_back(fmt::arg("altitude_error", gps_data_.fix.epv));
|
||||
|
||||
store.push_back(fmt::arg("speed", gps_data_.fix.speed));
|
||||
store.push_back(fmt::arg("speed_error", gps_data_.fix.eps));
|
||||
|
||||
store.push_back(fmt::arg("climb", gps_data_.fix.climb));
|
||||
store.push_back(fmt::arg("climb_error", gps_data_.fix.epc));
|
||||
|
||||
store.push_back(fmt::arg("satellites_used", gps_data_.satellites_used));
|
||||
store.push_back(fmt::arg("satellites_visible", gps_data_.satellites_visible));
|
||||
|
||||
auto text = fmt::vformat(format, store);
|
||||
|
||||
if (tooltipEnabled()) {
|
||||
if (tooltip_format.empty() && config_["tooltip-format"].isString()) {
|
||||
tooltip_format = config_["tooltip-format"].asString();
|
||||
}
|
||||
if (!tooltip_format.empty()) {
|
||||
auto tooltip_text = fmt::vformat(tooltip_format, store);
|
||||
if (label_.get_tooltip_text() != tooltip_text) {
|
||||
label_.set_tooltip_markup(tooltip_text);
|
||||
}
|
||||
} else if (label_.get_tooltip_text() != text) {
|
||||
label_.set_tooltip_markup(text);
|
||||
}
|
||||
}
|
||||
label_.set_markup(text);
|
||||
// Call parent update
|
||||
ALabel::update();
|
||||
}
|
||||
|
||||
waybar::modules::Gps::~Gps() {
|
||||
gps_stream(&gps_data_, WATCH_DISABLE, NULL);
|
||||
gps_close(&gps_data_);
|
||||
}
|
||||
@@ -66,7 +66,12 @@ auto Language::update() -> void {
|
||||
void Language::onEvent(const std::string& ev) {
|
||||
std::lock_guard<std::mutex> lg(mutex_);
|
||||
std::string kbName(begin(ev) + ev.find_last_of('>') + 1, begin(ev) + ev.find_first_of(','));
|
||||
auto layoutName = ev.substr(ev.find_last_of(',') + 1);
|
||||
|
||||
// Last comma before variants parenthesis, eg:
|
||||
// activelayout>>micro-star-int'l-co.,-ltd.-msi-gk50-elite-gaming-keyboard,English (US, intl.,
|
||||
// with dead keys)
|
||||
std::string beforParenthesis(begin(ev), begin(ev) + ev.find_last_of('('));
|
||||
auto layoutName = ev.substr(beforParenthesis.find_last_of(',') + 1);
|
||||
|
||||
if (config_.isMember("keyboard-name") && kbName != config_["keyboard-name"].asString())
|
||||
return; // ignore
|
||||
|
||||
@@ -16,7 +16,7 @@ Submap::Submap(const std::string& id, const Bar& bar, const Json::Value& config)
|
||||
ALabel::update();
|
||||
|
||||
// Displays widget immediately if always_on_ assuming default submap
|
||||
// Needs an actual way to retrive current submap on startup
|
||||
// Needs an actual way to retrieve current submap on startup
|
||||
if (always_on_) {
|
||||
submap_ = default_submap_;
|
||||
label_.get_style_context()->add_class(submap_);
|
||||
@@ -68,8 +68,7 @@ void Submap::onEvent(const std::string& ev) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto submapName = ev.substr(ev.find_last_of('>') + 1);
|
||||
submapName = waybar::util::sanitize_string(submapName);
|
||||
auto submapName = ev.substr(ev.find_first_of('>') + 2);
|
||||
|
||||
if (!submap_.empty()) {
|
||||
label_.get_style_context()->remove_class(submap_);
|
||||
|
||||
142
src/modules/hyprland/windowcount.cpp
Normal file
142
src/modules/hyprland/windowcount.cpp
Normal file
@@ -0,0 +1,142 @@
|
||||
#include "modules/hyprland/windowcount.hpp"
|
||||
|
||||
#include <glibmm/fileutils.h>
|
||||
#include <glibmm/keyfile.h>
|
||||
#include <glibmm/miscutils.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
#include "modules/hyprland/backend.hpp"
|
||||
#include "util/sanitize_str.hpp"
|
||||
|
||||
namespace waybar::modules::hyprland {
|
||||
|
||||
WindowCount::WindowCount(const std::string& id, const Bar& bar, const Json::Value& config)
|
||||
: AAppIconLabel(config, "windowcount", id, "{count}", 0, true), bar_(bar) {
|
||||
modulesReady = true;
|
||||
separateOutputs_ =
|
||||
config.isMember("separate-outputs") ? config["separate-outputs"].asBool() : true;
|
||||
|
||||
if (!gIPC) {
|
||||
gIPC = std::make_unique<IPC>();
|
||||
}
|
||||
|
||||
queryActiveWorkspace();
|
||||
update();
|
||||
dp.emit();
|
||||
|
||||
// register for hyprland ipc
|
||||
gIPC->registerForIPC("fullscreen", this);
|
||||
gIPC->registerForIPC("workspace", this);
|
||||
gIPC->registerForIPC("focusedmon", this);
|
||||
gIPC->registerForIPC("openwindow", this);
|
||||
gIPC->registerForIPC("closewindow", this);
|
||||
gIPC->registerForIPC("movewindow", this);
|
||||
}
|
||||
|
||||
WindowCount::~WindowCount() {
|
||||
gIPC->unregisterForIPC(this);
|
||||
// wait for possible event handler to finish
|
||||
std::lock_guard<std::mutex> lg(mutex_);
|
||||
}
|
||||
|
||||
auto WindowCount::update() -> void {
|
||||
std::lock_guard<std::mutex> lg(mutex_);
|
||||
|
||||
std::string format = config_["format"].asString();
|
||||
std::string formatEmpty = config_["format-empty"].asString();
|
||||
std::string formatWindowed = config_["format-windowed"].asString();
|
||||
std::string formatFullscreen = config_["format-fullscreen"].asString();
|
||||
|
||||
setClass("empty", workspace_.windows == 0);
|
||||
setClass("fullscreen", workspace_.hasfullscreen);
|
||||
|
||||
if (workspace_.windows == 0 && !formatEmpty.empty()) {
|
||||
label_.set_markup(fmt::format(fmt::runtime(formatEmpty), workspace_.windows));
|
||||
} else if (!workspace_.hasfullscreen && !formatWindowed.empty()) {
|
||||
label_.set_markup(fmt::format(fmt::runtime(formatWindowed), workspace_.windows));
|
||||
} else if (workspace_.hasfullscreen && !formatFullscreen.empty()) {
|
||||
label_.set_markup(fmt::format(fmt::runtime(formatFullscreen), workspace_.windows));
|
||||
} else if (!format.empty()) {
|
||||
label_.set_markup(fmt::format(fmt::runtime(format), workspace_.windows));
|
||||
} else {
|
||||
label_.set_text(fmt::format("{}", workspace_.windows));
|
||||
}
|
||||
|
||||
label_.show();
|
||||
AAppIconLabel::update();
|
||||
}
|
||||
|
||||
auto WindowCount::getActiveWorkspace() -> Workspace {
|
||||
const auto workspace = gIPC->getSocket1JsonReply("activeworkspace");
|
||||
|
||||
if (workspace.isObject()) {
|
||||
return Workspace::parse(workspace);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
auto WindowCount::getActiveWorkspace(const std::string& monitorName) -> Workspace {
|
||||
const auto monitors = gIPC->getSocket1JsonReply("monitors");
|
||||
if (monitors.isArray()) {
|
||||
auto monitor = std::find_if(monitors.begin(), monitors.end(), [&](Json::Value monitor) {
|
||||
return monitor["name"] == monitorName;
|
||||
});
|
||||
if (monitor == std::end(monitors)) {
|
||||
spdlog::warn("Monitor not found: {}", monitorName);
|
||||
return Workspace{-1, 0, false};
|
||||
}
|
||||
const int id = (*monitor)["activeWorkspace"]["id"].asInt();
|
||||
|
||||
const auto workspaces = gIPC->getSocket1JsonReply("workspaces");
|
||||
if (workspaces.isArray()) {
|
||||
auto workspace = std::find_if(workspaces.begin(), workspaces.end(),
|
||||
[&](Json::Value workspace) { return workspace["id"] == id; });
|
||||
if (workspace == std::end(workspaces)) {
|
||||
spdlog::warn("No workspace with id {}", id);
|
||||
return Workspace{-1, 0, false};
|
||||
}
|
||||
return Workspace::parse(*workspace);
|
||||
};
|
||||
};
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
auto WindowCount::Workspace::parse(const Json::Value& value) -> WindowCount::Workspace {
|
||||
return Workspace{
|
||||
value["id"].asInt(),
|
||||
value["windows"].asInt(),
|
||||
value["hasfullscreen"].asBool(),
|
||||
};
|
||||
}
|
||||
|
||||
void WindowCount::queryActiveWorkspace() {
|
||||
std::lock_guard<std::mutex> lg(mutex_);
|
||||
|
||||
if (separateOutputs_) {
|
||||
workspace_ = getActiveWorkspace(this->bar_.output->name);
|
||||
} else {
|
||||
workspace_ = getActiveWorkspace();
|
||||
}
|
||||
}
|
||||
|
||||
void WindowCount::onEvent(const std::string& ev) {
|
||||
queryActiveWorkspace();
|
||||
dp.emit();
|
||||
}
|
||||
|
||||
void WindowCount::setClass(const std::string& classname, bool enable) {
|
||||
if (enable) {
|
||||
if (!bar_.window.get_style_context()->has_class(classname)) {
|
||||
bar_.window.get_style_context()->add_class(classname);
|
||||
}
|
||||
} else {
|
||||
bar_.window.get_style_context()->remove_class(classname);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace waybar::modules::hyprland
|
||||
@@ -88,7 +88,7 @@ bool WindowCreationPayload::isEmpty(Workspaces &workspace_manager) {
|
||||
|
||||
int WindowCreationPayload::incrementTimeSpentUncreated() { return m_timeSpentUncreated++; }
|
||||
|
||||
void WindowCreationPayload::moveToWorksace(std::string &new_workspace_name) {
|
||||
void WindowCreationPayload::moveToWorkspace(std::string &new_workspace_name) {
|
||||
m_workspaceName = new_workspace_name;
|
||||
}
|
||||
|
||||
|
||||
@@ -91,19 +91,19 @@ void Workspace::initializeWindowMap(const Json::Value &clients_data) {
|
||||
}
|
||||
}
|
||||
|
||||
void Workspace::insertWindow(WindowCreationPayload create_window_paylod) {
|
||||
if (!create_window_paylod.isEmpty(m_workspaceManager)) {
|
||||
auto repr = create_window_paylod.repr(m_workspaceManager);
|
||||
void Workspace::insertWindow(WindowCreationPayload create_window_payload) {
|
||||
if (!create_window_payload.isEmpty(m_workspaceManager)) {
|
||||
auto repr = create_window_payload.repr(m_workspaceManager);
|
||||
|
||||
if (!repr.empty()) {
|
||||
m_windowMap[create_window_paylod.getAddress()] = repr;
|
||||
m_windowMap[create_window_payload.getAddress()] = repr;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
bool Workspace::onWindowOpened(WindowCreationPayload const &create_window_paylod) {
|
||||
if (create_window_paylod.getWorkspaceName() == name()) {
|
||||
insertWindow(create_window_paylod);
|
||||
bool Workspace::onWindowOpened(WindowCreationPayload const &create_window_payload) {
|
||||
if (create_window_payload.getWorkspaceName() == name()) {
|
||||
insertWindow(create_window_payload);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -173,6 +173,10 @@ std::string &Workspace::selectIcon(std::map<std::string, std::string> &icons_map
|
||||
}
|
||||
|
||||
void Workspace::update(const std::string &format, const std::string &icon) {
|
||||
if (this->m_workspaceManager.persistentOnly() && !this->isPersistent()) {
|
||||
m_button.hide();
|
||||
return;
|
||||
}
|
||||
// clang-format off
|
||||
if (this->m_workspaceManager.activeOnly() && \
|
||||
!this->isActive() && \
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
@@ -39,7 +40,7 @@ Workspaces::~Workspaces() {
|
||||
}
|
||||
|
||||
void Workspaces::init() {
|
||||
m_activeWorkspaceName = (m_ipc.getSocket1JsonReply("activeworkspace"))["name"].asString();
|
||||
m_activeWorkspaceId = m_ipc.getSocket1JsonReply("activeworkspace")["id"].asInt();
|
||||
|
||||
initializeWorkspaces();
|
||||
dp.emit();
|
||||
@@ -49,13 +50,12 @@ Json::Value Workspaces::createMonitorWorkspaceData(std::string const &name,
|
||||
std::string const &monitor) {
|
||||
spdlog::trace("Creating persistent workspace: {} on monitor {}", name, monitor);
|
||||
Json::Value workspaceData;
|
||||
try {
|
||||
// numbered persistent workspaces get the name as ID
|
||||
workspaceData["id"] = name == "special" ? -99 : std::stoi(name);
|
||||
} catch (const std::exception &e) {
|
||||
// named persistent workspaces start with ID=0
|
||||
workspaceData["id"] = 0;
|
||||
|
||||
auto workspaceId = parseWorkspaceId(name);
|
||||
if (!workspaceId.has_value()) {
|
||||
workspaceId = 0;
|
||||
}
|
||||
workspaceData["id"] = *workspaceId;
|
||||
workspaceData["name"] = name;
|
||||
workspaceData["monitor"] = monitor;
|
||||
workspaceData["windows"] = 0;
|
||||
@@ -68,9 +68,8 @@ void Workspaces::createWorkspace(Json::Value const &workspace_data,
|
||||
spdlog::debug("Creating workspace {}", workspaceName);
|
||||
|
||||
// avoid recreating existing workspaces
|
||||
auto workspace = std::find_if(
|
||||
m_workspaces.begin(), m_workspaces.end(),
|
||||
[workspaceName](std::unique_ptr<Workspace> const &w) {
|
||||
auto workspace =
|
||||
std::ranges::find_if(m_workspaces, [workspaceName](std::unique_ptr<Workspace> const &w) {
|
||||
return (workspaceName.starts_with("special:") && workspaceName.substr(8) == w->name()) ||
|
||||
workspaceName == w->name();
|
||||
});
|
||||
@@ -80,14 +79,14 @@ void Workspaces::createWorkspace(Json::Value const &workspace_data,
|
||||
const auto keys = workspace_data.getMemberNames();
|
||||
|
||||
const auto *k = "persistent-rule";
|
||||
if (std::find(keys.begin(), keys.end(), k) != keys.end()) {
|
||||
if (std::ranges::find(keys, k) != keys.end()) {
|
||||
spdlog::debug("Set dynamic persistency of workspace {} to: {}", workspaceName,
|
||||
workspace_data[k].asBool() ? "true" : "false");
|
||||
(*workspace)->setPersistentRule(workspace_data[k].asBool());
|
||||
}
|
||||
|
||||
k = "persistent-config";
|
||||
if (std::find(keys.begin(), keys.end(), k) != keys.end()) {
|
||||
if (std::ranges::find(keys, k) != keys.end()) {
|
||||
spdlog::debug("Set config persistency of workspace {} to: {}", workspaceName,
|
||||
workspace_data[k].asBool() ? "true" : "false");
|
||||
(*workspace)->setPersistentConfig(workspace_data[k].asBool());
|
||||
@@ -158,18 +157,18 @@ std::string Workspaces::getRewrite(std::string window_class, std::string window_
|
||||
fmt::arg("title", window_title));
|
||||
}
|
||||
|
||||
std::vector<std::string> Workspaces::getVisibleWorkspaces() {
|
||||
std::vector<std::string> visibleWorkspaces;
|
||||
std::vector<int> Workspaces::getVisibleWorkspaces() {
|
||||
std::vector<int> visibleWorkspaces;
|
||||
auto monitors = IPC::inst().getSocket1JsonReply("monitors");
|
||||
for (const auto &monitor : monitors) {
|
||||
auto ws = monitor["activeWorkspace"];
|
||||
if (ws.isObject() && ws["name"].isString()) {
|
||||
visibleWorkspaces.push_back(ws["name"].asString());
|
||||
if (ws.isObject() && ws["id"].isInt()) {
|
||||
visibleWorkspaces.push_back(ws["id"].asInt());
|
||||
}
|
||||
auto sws = monitor["specialWorkspace"];
|
||||
auto name = sws["name"].asString();
|
||||
if (sws.isObject() && sws["name"].isString() && !name.empty()) {
|
||||
visibleWorkspaces.push_back(!name.starts_with("special:") ? name : name.substr(8));
|
||||
if (sws.isObject() && sws["id"].isInt() && !name.empty()) {
|
||||
visibleWorkspaces.push_back(sws["id"].asInt());
|
||||
}
|
||||
}
|
||||
return visibleWorkspaces;
|
||||
@@ -180,7 +179,7 @@ void Workspaces::initializeWorkspaces() {
|
||||
|
||||
// if the workspace rules changed since last initialization, make sure we reset everything:
|
||||
for (auto &workspace : m_workspaces) {
|
||||
m_workspacesToRemove.push_back(workspace->name());
|
||||
m_workspacesToRemove.push_back(std::to_string(workspace->id()));
|
||||
}
|
||||
|
||||
// get all current workspaces
|
||||
@@ -232,7 +231,7 @@ void Workspaces::loadPersistentWorkspacesFromConfig(Json::Value const &clientsJs
|
||||
std::vector<std::string> persistentWorkspacesToCreate;
|
||||
|
||||
const std::string currentMonitor = m_bar.output->name;
|
||||
const bool monitorInConfig = std::find(keys.begin(), keys.end(), currentMonitor) != keys.end();
|
||||
const bool monitorInConfig = std::ranges::find(keys, currentMonitor) != keys.end();
|
||||
for (const std::string &key : keys) {
|
||||
// only add if either:
|
||||
// 1. key is the current monitor name
|
||||
@@ -247,7 +246,7 @@ void Workspaces::loadPersistentWorkspacesFromConfig(Json::Value const &clientsJs
|
||||
int amount = value.asInt();
|
||||
spdlog::debug("Creating {} persistent workspaces for monitor {}", amount, currentMonitor);
|
||||
for (int i = 0; i < amount; i++) {
|
||||
persistentWorkspacesToCreate.emplace_back(std::to_string(m_monitorId * amount + i + 1));
|
||||
persistentWorkspacesToCreate.emplace_back(std::to_string((m_monitorId * amount) + i + 1));
|
||||
}
|
||||
}
|
||||
} else if (value.isArray() && !value.empty()) {
|
||||
@@ -306,6 +305,7 @@ void Workspaces::loadPersistentWorkspacesFromWorkspaceRules(const Json::Value &c
|
||||
workspaceData["persistent-rule"] = true;
|
||||
m_workspacesToCreate.emplace_back(workspaceData, clientsJson);
|
||||
} else {
|
||||
// This can be any workspace selector.
|
||||
m_workspacesToRemove.emplace_back(workspace);
|
||||
}
|
||||
}
|
||||
@@ -316,29 +316,29 @@ void Workspaces::onEvent(const std::string &ev) {
|
||||
std::string eventName(begin(ev), begin(ev) + ev.find_first_of('>'));
|
||||
std::string payload = ev.substr(eventName.size() + 2);
|
||||
|
||||
if (eventName == "workspace") {
|
||||
if (eventName == "workspacev2") {
|
||||
onWorkspaceActivated(payload);
|
||||
} else if (eventName == "activespecial") {
|
||||
onSpecialWorkspaceActivated(payload);
|
||||
} else if (eventName == "destroyworkspace") {
|
||||
} else if (eventName == "destroyworkspacev2") {
|
||||
onWorkspaceDestroyed(payload);
|
||||
} else if (eventName == "createworkspace") {
|
||||
} else if (eventName == "createworkspacev2") {
|
||||
onWorkspaceCreated(payload);
|
||||
} else if (eventName == "focusedmon") {
|
||||
} else if (eventName == "focusedmonv2") {
|
||||
onMonitorFocused(payload);
|
||||
} else if (eventName == "moveworkspace") {
|
||||
} else if (eventName == "moveworkspacev2") {
|
||||
onWorkspaceMoved(payload);
|
||||
} else if (eventName == "openwindow") {
|
||||
onWindowOpened(payload);
|
||||
} else if (eventName == "closewindow") {
|
||||
onWindowClosed(payload);
|
||||
} else if (eventName == "movewindow") {
|
||||
} else if (eventName == "movewindowv2") {
|
||||
onWindowMoved(payload);
|
||||
} else if (eventName == "urgent") {
|
||||
setUrgentWorkspace(payload);
|
||||
} else if (eventName == "renameworkspace") {
|
||||
onWorkspaceRenamed(payload);
|
||||
} else if (eventName == "windowtitle") {
|
||||
} else if (eventName == "windowtitlev2") {
|
||||
onWindowTitleEvent(payload);
|
||||
} else if (eventName == "configreloaded") {
|
||||
onConfigReloaded();
|
||||
@@ -348,7 +348,11 @@ void Workspaces::onEvent(const std::string &ev) {
|
||||
}
|
||||
|
||||
void Workspaces::onWorkspaceActivated(std::string const &payload) {
|
||||
m_activeWorkspaceName = payload;
|
||||
const auto [workspaceIdStr, workspaceName] = splitDoublePayload(payload);
|
||||
const auto workspaceId = parseWorkspaceId(workspaceIdStr);
|
||||
if (workspaceId.has_value()) {
|
||||
m_activeWorkspaceId = *workspaceId;
|
||||
}
|
||||
}
|
||||
|
||||
void Workspaces::onSpecialWorkspaceActivated(std::string const &payload) {
|
||||
@@ -357,42 +361,55 @@ void Workspaces::onSpecialWorkspaceActivated(std::string const &payload) {
|
||||
}
|
||||
|
||||
void Workspaces::onWorkspaceDestroyed(std::string const &payload) {
|
||||
if (!isDoubleSpecial(payload)) {
|
||||
m_workspacesToRemove.push_back(payload);
|
||||
const auto [workspaceId, workspaceName] = splitDoublePayload(payload);
|
||||
if (!isDoubleSpecial(workspaceName)) {
|
||||
m_workspacesToRemove.push_back(workspaceId);
|
||||
}
|
||||
}
|
||||
|
||||
void Workspaces::onWorkspaceCreated(std::string const &workspaceName,
|
||||
Json::Value const &clientsData) {
|
||||
spdlog::debug("Workspace created: {}", workspaceName);
|
||||
void Workspaces::onWorkspaceCreated(std::string const &payload, Json::Value const &clientsData) {
|
||||
spdlog::debug("Workspace created: {}", payload);
|
||||
|
||||
const auto [workspaceIdStr, _] = splitDoublePayload(payload);
|
||||
|
||||
const auto workspaceId = parseWorkspaceId(workspaceIdStr);
|
||||
if (!workspaceId.has_value()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto const workspaceRules = m_ipc.getSocket1JsonReply("workspacerules");
|
||||
auto const workspacesJson = m_ipc.getSocket1JsonReply("workspaces");
|
||||
|
||||
if (!isWorkspaceIgnored(workspaceName)) {
|
||||
auto const workspaceRules = m_ipc.getSocket1JsonReply("workspacerules");
|
||||
for (Json::Value workspaceJson : workspacesJson) {
|
||||
std::string name = workspaceJson["name"].asString();
|
||||
if (name == workspaceName) {
|
||||
if ((allOutputs() || m_bar.output->name == workspaceJson["monitor"].asString()) &&
|
||||
(showSpecial() || !name.starts_with("special")) && !isDoubleSpecial(workspaceName)) {
|
||||
for (Json::Value const &rule : workspaceRules) {
|
||||
auto ruleWorkspaceName = rule.isMember("defaultName")
|
||||
? rule["defaultName"].asString()
|
||||
: rule["workspaceString"].asString();
|
||||
if (ruleWorkspaceName == workspaceName) {
|
||||
workspaceJson["persistent-rule"] = rule["persistent"].asBool();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
m_workspacesToCreate.emplace_back(workspaceJson, clientsData);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
extendOrphans(workspaceJson["id"].asInt(), clientsData);
|
||||
for (Json::Value workspaceJson : workspacesJson) {
|
||||
const auto currentId = workspaceJson["id"].asInt();
|
||||
if (currentId == *workspaceId) {
|
||||
std::string workspaceName = workspaceJson["name"].asString();
|
||||
// This workspace name is more up-to-date than the one in the event payload.
|
||||
if (isWorkspaceIgnored(workspaceName)) {
|
||||
spdlog::trace("Not creating workspace because it is ignored: id={} name={}", *workspaceId,
|
||||
workspaceName);
|
||||
break;
|
||||
}
|
||||
|
||||
if ((allOutputs() || m_bar.output->name == workspaceJson["monitor"].asString()) &&
|
||||
(showSpecial() || !workspaceName.starts_with("special")) &&
|
||||
!isDoubleSpecial(workspaceName)) {
|
||||
for (Json::Value const &rule : workspaceRules) {
|
||||
auto ruleWorkspaceName = rule.isMember("defaultName")
|
||||
? rule["defaultName"].asString()
|
||||
: rule["workspaceString"].asString();
|
||||
if (ruleWorkspaceName == workspaceName) {
|
||||
workspaceJson["persistent-rule"] = rule["persistent"].asBool();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
m_workspacesToCreate.emplace_back(workspaceJson, clientsData);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
extendOrphans(*workspaceId, clientsData);
|
||||
}
|
||||
} else {
|
||||
spdlog::trace("Not creating workspace because it is ignored: {}", workspaceName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -400,32 +417,34 @@ void Workspaces::onWorkspaceMoved(std::string const &payload) {
|
||||
spdlog::debug("Workspace moved: {}", payload);
|
||||
|
||||
// Update active workspace
|
||||
m_activeWorkspaceName = (m_ipc.getSocket1JsonReply("activeworkspace"))["name"].asString();
|
||||
m_activeWorkspaceId = (m_ipc.getSocket1JsonReply("activeworkspace"))["id"].asInt();
|
||||
|
||||
if (allOutputs()) return;
|
||||
|
||||
std::string workspaceName = payload.substr(0, payload.find(','));
|
||||
std::string monitorName = payload.substr(payload.find(',') + 1);
|
||||
const auto [workspaceIdStr, workspaceName, monitorName] = splitTriplePayload(payload);
|
||||
|
||||
const auto subPayload = makePayload(workspaceIdStr, workspaceName);
|
||||
|
||||
if (m_bar.output->name == monitorName) {
|
||||
Json::Value clientsData = m_ipc.getSocket1JsonReply("clients");
|
||||
onWorkspaceCreated(workspaceName, clientsData);
|
||||
onWorkspaceCreated(subPayload, clientsData);
|
||||
} else {
|
||||
spdlog::debug("Removing workspace because it was moved to another monitor: {}");
|
||||
onWorkspaceDestroyed(workspaceName);
|
||||
spdlog::debug("Removing workspace because it was moved to another monitor: {}", subPayload);
|
||||
onWorkspaceDestroyed(subPayload);
|
||||
}
|
||||
}
|
||||
|
||||
void Workspaces::onWorkspaceRenamed(std::string const &payload) {
|
||||
spdlog::debug("Workspace renamed: {}", payload);
|
||||
std::string workspaceIdStr = payload.substr(0, payload.find(','));
|
||||
int workspaceId = workspaceIdStr == "special" ? -99 : std::stoi(workspaceIdStr);
|
||||
std::string newName = payload.substr(payload.find(',') + 1);
|
||||
const auto [workspaceIdStr, newName] = splitDoublePayload(payload);
|
||||
|
||||
const auto workspaceId = parseWorkspaceId(workspaceIdStr);
|
||||
if (!workspaceId.has_value()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto &workspace : m_workspaces) {
|
||||
if (workspace->id() == workspaceId) {
|
||||
if (workspace->name() == m_activeWorkspaceName) {
|
||||
m_activeWorkspaceName = newName;
|
||||
}
|
||||
if (workspace->id() == *workspaceId) {
|
||||
workspace->setName(newName);
|
||||
break;
|
||||
}
|
||||
@@ -435,11 +454,19 @@ void Workspaces::onWorkspaceRenamed(std::string const &payload) {
|
||||
|
||||
void Workspaces::onMonitorFocused(std::string const &payload) {
|
||||
spdlog::trace("Monitor focused: {}", payload);
|
||||
m_activeWorkspaceName = payload.substr(payload.find(',') + 1);
|
||||
|
||||
const auto [monitorName, workspaceIdStr] = splitDoublePayload(payload);
|
||||
|
||||
const auto workspaceId = parseWorkspaceId(workspaceIdStr);
|
||||
if (!workspaceId.has_value()) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_activeWorkspaceId = *workspaceId;
|
||||
|
||||
for (Json::Value &monitor : m_ipc.getSocket1JsonReply("monitors")) {
|
||||
if (monitor["name"].asString() == payload.substr(0, payload.find(','))) {
|
||||
auto name = monitor["specialWorkspace"]["name"].asString();
|
||||
if (monitor["name"].asString() == monitorName) {
|
||||
const auto name = monitor["specialWorkspace"]["name"].asString();
|
||||
m_activeSpecialWorkspaceName = !name.starts_with("special:") ? name : name.substr(8);
|
||||
}
|
||||
}
|
||||
@@ -478,11 +505,7 @@ void Workspaces::onWindowClosed(std::string const &addr) {
|
||||
void Workspaces::onWindowMoved(std::string const &payload) {
|
||||
spdlog::trace("Window moved: {}", payload);
|
||||
updateWindowCount();
|
||||
size_t lastCommaIdx = 0;
|
||||
size_t nextCommaIdx = payload.find(',');
|
||||
std::string windowAddress = payload.substr(lastCommaIdx, nextCommaIdx - lastCommaIdx);
|
||||
|
||||
std::string workspaceName = payload.substr(nextCommaIdx + 1, payload.length() - nextCommaIdx);
|
||||
auto [windowAddress, _, workspaceName] = splitTriplePayload(payload);
|
||||
|
||||
std::string windowRepr;
|
||||
|
||||
@@ -490,7 +513,7 @@ void Workspaces::onWindowMoved(std::string const &payload) {
|
||||
// and exit
|
||||
for (auto &window : m_windowsToCreate) {
|
||||
if (window.getAddress() == windowAddress) {
|
||||
window.moveToWorksace(workspaceName);
|
||||
window.moveToWorkspace(workspaceName);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -518,13 +541,15 @@ void Workspaces::onWindowTitleEvent(std::string const &payload) {
|
||||
spdlog::trace("Window title changed: {}", payload);
|
||||
std::optional<std::function<void(WindowCreationPayload)>> inserter;
|
||||
|
||||
const auto [windowAddress, _] = splitDoublePayload(payload);
|
||||
|
||||
// If the window was an orphan, rename it at the orphan's vector
|
||||
if (m_orphanWindowMap.contains(payload)) {
|
||||
if (m_orphanWindowMap.contains(windowAddress)) {
|
||||
inserter = [this](WindowCreationPayload wcp) { this->registerOrphanWindow(std::move(wcp)); };
|
||||
} else {
|
||||
auto windowWorkspace =
|
||||
std::find_if(m_workspaces.begin(), m_workspaces.end(),
|
||||
[payload](auto &workspace) { return workspace->containsWindow(payload); });
|
||||
auto windowWorkspace = std::ranges::find_if(m_workspaces, [windowAddress](auto &workspace) {
|
||||
return workspace->containsWindow(windowAddress);
|
||||
});
|
||||
|
||||
// If the window exists on a workspace, rename it at the workspace's window
|
||||
// map
|
||||
@@ -533,9 +558,9 @@ void Workspaces::onWindowTitleEvent(std::string const &payload) {
|
||||
(*windowWorkspace)->insertWindow(std::move(wcp));
|
||||
};
|
||||
} else {
|
||||
auto queuedWindow = std::find_if(
|
||||
m_windowsToCreate.begin(), m_windowsToCreate.end(),
|
||||
[payload](auto &windowPayload) { return windowPayload.getAddress() == payload; });
|
||||
auto queuedWindow = std::ranges::find_if(m_windowsToCreate, [payload](auto &windowPayload) {
|
||||
return windowPayload.getAddress() == payload;
|
||||
});
|
||||
|
||||
// If the window was queued, rename it in the queue
|
||||
if (queuedWindow != m_windowsToCreate.end()) {
|
||||
@@ -548,12 +573,11 @@ void Workspaces::onWindowTitleEvent(std::string const &payload) {
|
||||
Json::Value clientsData = m_ipc.getSocket1JsonReply("clients");
|
||||
std::string jsonWindowAddress = fmt::format("0x{}", payload);
|
||||
|
||||
auto client =
|
||||
std::find_if(clientsData.begin(), clientsData.end(), [jsonWindowAddress](auto &client) {
|
||||
return client["address"].asString() == jsonWindowAddress;
|
||||
});
|
||||
auto client = std::ranges::find_if(clientsData, [jsonWindowAddress](auto &client) {
|
||||
return client["address"].asString() == jsonWindowAddress;
|
||||
});
|
||||
|
||||
if (!client->empty()) {
|
||||
if (client != clientsData.end() && !client->empty()) {
|
||||
(*inserter)({*client});
|
||||
}
|
||||
}
|
||||
@@ -576,6 +600,7 @@ auto Workspaces::parseConfig(const Json::Value &config) -> void {
|
||||
populateBoolConfig(config, "all-outputs", m_allOutputs);
|
||||
populateBoolConfig(config, "show-special", m_showSpecial);
|
||||
populateBoolConfig(config, "special-visible-only", m_specialVisibleOnly);
|
||||
populateBoolConfig(config, "persistent-only", m_persistentOnly);
|
||||
populateBoolConfig(config, "active-only", m_activeOnly);
|
||||
populateBoolConfig(config, "move-to-monitor", m_moveToMonitor);
|
||||
|
||||
@@ -663,40 +688,58 @@ void Workspaces::registerOrphanWindow(WindowCreationPayload create_window_payloa
|
||||
}
|
||||
|
||||
auto Workspaces::registerIpc() -> void {
|
||||
m_ipc.registerForIPC("workspace", this);
|
||||
m_ipc.registerForIPC("workspacev2", this);
|
||||
m_ipc.registerForIPC("activespecial", this);
|
||||
m_ipc.registerForIPC("createworkspace", this);
|
||||
m_ipc.registerForIPC("destroyworkspace", this);
|
||||
m_ipc.registerForIPC("focusedmon", this);
|
||||
m_ipc.registerForIPC("moveworkspace", this);
|
||||
m_ipc.registerForIPC("createworkspacev2", this);
|
||||
m_ipc.registerForIPC("destroyworkspacev2", this);
|
||||
m_ipc.registerForIPC("focusedmonv2", this);
|
||||
m_ipc.registerForIPC("moveworkspacev2", this);
|
||||
m_ipc.registerForIPC("renameworkspace", this);
|
||||
m_ipc.registerForIPC("openwindow", this);
|
||||
m_ipc.registerForIPC("closewindow", this);
|
||||
m_ipc.registerForIPC("movewindow", this);
|
||||
m_ipc.registerForIPC("movewindowv2", this);
|
||||
m_ipc.registerForIPC("urgent", this);
|
||||
m_ipc.registerForIPC("configreloaded", this);
|
||||
|
||||
if (windowRewriteConfigUsesTitle()) {
|
||||
spdlog::info(
|
||||
"Registering for Hyprland's 'windowtitle' events because a user-defined window "
|
||||
"Registering for Hyprland's 'windowtitlev2' events because a user-defined window "
|
||||
"rewrite rule uses the 'title' field.");
|
||||
m_ipc.registerForIPC("windowtitle", this);
|
||||
m_ipc.registerForIPC("windowtitlev2", this);
|
||||
}
|
||||
}
|
||||
|
||||
void Workspaces::removeWorkspacesToRemove() {
|
||||
for (const auto &workspaceName : m_workspacesToRemove) {
|
||||
removeWorkspace(workspaceName);
|
||||
for (const auto &workspaceString : m_workspacesToRemove) {
|
||||
removeWorkspace(workspaceString);
|
||||
}
|
||||
m_workspacesToRemove.clear();
|
||||
}
|
||||
|
||||
void Workspaces::removeWorkspace(std::string const &name) {
|
||||
spdlog::debug("Removing workspace {}", name);
|
||||
auto workspace =
|
||||
std::find_if(m_workspaces.begin(), m_workspaces.end(), [&](std::unique_ptr<Workspace> &x) {
|
||||
return (name.starts_with("special:") && name.substr(8) == x->name()) || name == x->name();
|
||||
});
|
||||
void Workspaces::removeWorkspace(std::string const &workspaceString) {
|
||||
spdlog::debug("Removing workspace {}", workspaceString);
|
||||
|
||||
// If this succeeds, we have a workspace ID.
|
||||
const auto workspaceId = parseWorkspaceId(workspaceString);
|
||||
|
||||
std::string name;
|
||||
// TODO: At some point we want to support all workspace selectors
|
||||
// This is just a subset.
|
||||
// https://wiki.hyprland.org/Configuring/Workspace-Rules/#workspace-selectors
|
||||
if (workspaceString.starts_with("special:")) {
|
||||
name = workspaceString.substr(8);
|
||||
} else if (workspaceString.starts_with("name:")) {
|
||||
name = workspaceString.substr(5);
|
||||
} else {
|
||||
name = workspaceString;
|
||||
}
|
||||
|
||||
const auto workspace = std::ranges::find_if(m_workspaces, [&](std::unique_ptr<Workspace> &x) {
|
||||
if (workspaceId.has_value()) {
|
||||
return *workspaceId == x->id();
|
||||
}
|
||||
return name == x->name();
|
||||
});
|
||||
|
||||
if (workspace == m_workspaces.end()) {
|
||||
// happens when a workspace on another monitor is destroyed
|
||||
@@ -704,7 +747,8 @@ void Workspaces::removeWorkspace(std::string const &name) {
|
||||
}
|
||||
|
||||
if ((*workspace)->isPersistentConfig()) {
|
||||
spdlog::trace("Not removing config persistent workspace {}", name);
|
||||
spdlog::trace("Not removing config persistent workspace id={} name={}", (*workspace)->id(),
|
||||
(*workspace)->name());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -716,9 +760,9 @@ void Workspaces::setCurrentMonitorId() {
|
||||
// get monitor ID from name (used by persistent workspaces)
|
||||
m_monitorId = 0;
|
||||
auto monitors = m_ipc.getSocket1JsonReply("monitors");
|
||||
auto currentMonitor = std::find_if(
|
||||
monitors.begin(), monitors.end(),
|
||||
[this](const Json::Value &m) { return m["name"].asString() == m_bar.output->name; });
|
||||
auto currentMonitor = std::ranges::find_if(monitors, [this](const Json::Value &m) {
|
||||
return m["name"].asString() == m_bar.output->name;
|
||||
});
|
||||
if (currentMonitor == monitors.end()) {
|
||||
spdlog::error("Monitor '{}' does not have an ID? Using 0", m_bar.output->name);
|
||||
} else {
|
||||
@@ -727,63 +771,101 @@ void Workspaces::setCurrentMonitorId() {
|
||||
}
|
||||
}
|
||||
|
||||
void Workspaces::sortSpecialCentered() {
|
||||
std::vector<std::unique_ptr<Workspace>> specialWorkspaces;
|
||||
std::vector<std::unique_ptr<Workspace>> hiddenWorkspaces;
|
||||
std::vector<std::unique_ptr<Workspace>> normalWorkspaces;
|
||||
|
||||
for (auto &workspace : m_workspaces) {
|
||||
if (workspace->isSpecial()) {
|
||||
specialWorkspaces.push_back(std::move(workspace));
|
||||
} else {
|
||||
if (workspace->button().is_visible()) {
|
||||
normalWorkspaces.push_back(std::move(workspace));
|
||||
} else {
|
||||
hiddenWorkspaces.push_back(std::move(workspace));
|
||||
}
|
||||
}
|
||||
}
|
||||
m_workspaces.clear();
|
||||
|
||||
size_t center = normalWorkspaces.size() / 2;
|
||||
|
||||
m_workspaces.insert(m_workspaces.end(), std::make_move_iterator(normalWorkspaces.begin()),
|
||||
std::make_move_iterator(normalWorkspaces.begin() + center));
|
||||
|
||||
m_workspaces.insert(m_workspaces.end(), std::make_move_iterator(specialWorkspaces.begin()),
|
||||
std::make_move_iterator(specialWorkspaces.end()));
|
||||
|
||||
m_workspaces.insert(m_workspaces.end(),
|
||||
std::make_move_iterator(normalWorkspaces.begin() + center),
|
||||
std::make_move_iterator(normalWorkspaces.end()));
|
||||
|
||||
m_workspaces.insert(m_workspaces.end(), std::make_move_iterator(hiddenWorkspaces.begin()),
|
||||
std::make_move_iterator(hiddenWorkspaces.end()));
|
||||
}
|
||||
|
||||
void Workspaces::sortWorkspaces() {
|
||||
std::sort(m_workspaces.begin(), m_workspaces.end(),
|
||||
[&](std::unique_ptr<Workspace> &a, std::unique_ptr<Workspace> &b) {
|
||||
// Helper comparisons
|
||||
auto isIdLess = a->id() < b->id();
|
||||
auto isNameLess = a->name() < b->name();
|
||||
std::ranges::sort( //
|
||||
m_workspaces, [&](std::unique_ptr<Workspace> &a, std::unique_ptr<Workspace> &b) {
|
||||
// Helper comparisons
|
||||
auto isIdLess = a->id() < b->id();
|
||||
auto isNameLess = a->name() < b->name();
|
||||
|
||||
switch (m_sortBy) {
|
||||
case SortMethod::ID:
|
||||
return isIdLess;
|
||||
case SortMethod::NAME:
|
||||
return isNameLess;
|
||||
case SortMethod::NUMBER:
|
||||
try {
|
||||
return std::stoi(a->name()) < std::stoi(b->name());
|
||||
} catch (const std::invalid_argument &) {
|
||||
// Handle the exception if necessary.
|
||||
break;
|
||||
}
|
||||
case SortMethod::DEFAULT:
|
||||
default:
|
||||
// Handle the default case here.
|
||||
// normal -> named persistent -> named -> special -> named special
|
||||
switch (m_sortBy) {
|
||||
case SortMethod::ID:
|
||||
return isIdLess;
|
||||
case SortMethod::NAME:
|
||||
return isNameLess;
|
||||
case SortMethod::NUMBER:
|
||||
try {
|
||||
return std::stoi(a->name()) < std::stoi(b->name());
|
||||
} catch (const std::invalid_argument &) {
|
||||
// Handle the exception if necessary.
|
||||
break;
|
||||
}
|
||||
case SortMethod::DEFAULT:
|
||||
default:
|
||||
// Handle the default case here.
|
||||
// normal -> named persistent -> named -> special -> named special
|
||||
|
||||
// both normal (includes numbered persistent) => sort by ID
|
||||
if (a->id() > 0 && b->id() > 0) {
|
||||
return isIdLess;
|
||||
}
|
||||
// both normal (includes numbered persistent) => sort by ID
|
||||
if (a->id() > 0 && b->id() > 0) {
|
||||
return isIdLess;
|
||||
}
|
||||
|
||||
// one normal, one special => normal first
|
||||
if ((a->isSpecial()) ^ (b->isSpecial())) {
|
||||
return b->isSpecial();
|
||||
}
|
||||
// one normal, one special => normal first
|
||||
if ((a->isSpecial()) ^ (b->isSpecial())) {
|
||||
return b->isSpecial();
|
||||
}
|
||||
|
||||
// only one normal, one named
|
||||
if ((a->id() > 0) ^ (b->id() > 0)) {
|
||||
return a->id() > 0;
|
||||
}
|
||||
// only one normal, one named
|
||||
if ((a->id() > 0) ^ (b->id() > 0)) {
|
||||
return a->id() > 0;
|
||||
}
|
||||
|
||||
// both special
|
||||
if (a->isSpecial() && b->isSpecial()) {
|
||||
// if one is -99 => put it last
|
||||
if (a->id() == -99 || b->id() == -99) {
|
||||
return b->id() == -99;
|
||||
}
|
||||
// both are 0 (not yet named persistents) / named specials (-98 <= ID <= -1)
|
||||
return isNameLess;
|
||||
}
|
||||
|
||||
// sort non-special named workspaces by name (ID <= -1377)
|
||||
return isNameLess;
|
||||
break;
|
||||
// both special
|
||||
if (a->isSpecial() && b->isSpecial()) {
|
||||
// if one is -99 => put it last
|
||||
if (a->id() == -99 || b->id() == -99) {
|
||||
return b->id() == -99;
|
||||
}
|
||||
// both are 0 (not yet named persistents) / named specials
|
||||
// (-98 <= ID <= -1)
|
||||
return isNameLess;
|
||||
}
|
||||
|
||||
// Return a default value if none of the cases match.
|
||||
return isNameLess; // You can adjust this to your specific needs.
|
||||
});
|
||||
// sort non-special named workspaces by name (ID <= -1377)
|
||||
return isNameLess;
|
||||
break;
|
||||
}
|
||||
|
||||
// Return a default value if none of the cases match.
|
||||
return isNameLess; // You can adjust this to your specific needs.
|
||||
});
|
||||
if (m_sortBy == SortMethod::SPECIAL_CENTERED) {
|
||||
this->sortSpecialCentered();
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < m_workspaces.size(); ++i) {
|
||||
m_box.reorder_child(m_workspaces[i]->button(), i);
|
||||
@@ -801,9 +883,9 @@ void Workspaces::setUrgentWorkspace(std::string const &windowaddress) {
|
||||
}
|
||||
}
|
||||
|
||||
auto workspace =
|
||||
std::find_if(m_workspaces.begin(), m_workspaces.end(),
|
||||
[workspaceId](std::unique_ptr<Workspace> &x) { return x->id() == workspaceId; });
|
||||
auto workspace = std::ranges::find_if(m_workspaces, [workspaceId](std::unique_ptr<Workspace> &x) {
|
||||
return x->id() == workspaceId;
|
||||
});
|
||||
if (workspace != m_workspaces.end()) {
|
||||
workspace->get()->setUrgent();
|
||||
}
|
||||
@@ -817,11 +899,10 @@ auto Workspaces::update() -> void {
|
||||
void Workspaces::updateWindowCount() {
|
||||
const Json::Value workspacesJson = m_ipc.getSocket1JsonReply("workspaces");
|
||||
for (auto &workspace : m_workspaces) {
|
||||
auto workspaceJson =
|
||||
std::find_if(workspacesJson.begin(), workspacesJson.end(), [&](Json::Value const &x) {
|
||||
return x["name"].asString() == workspace->name() ||
|
||||
(workspace->isSpecial() && x["name"].asString() == "special:" + workspace->name());
|
||||
});
|
||||
auto workspaceJson = std::ranges::find_if(workspacesJson, [&](Json::Value const &x) {
|
||||
return x["name"].asString() == workspace->name() ||
|
||||
(workspace->isSpecial() && x["name"].asString() == "special:" + workspace->name());
|
||||
});
|
||||
uint32_t count = 0;
|
||||
if (workspaceJson != workspacesJson.end()) {
|
||||
try {
|
||||
@@ -861,26 +942,26 @@ bool Workspaces::updateWindowsToCreate() {
|
||||
}
|
||||
|
||||
void Workspaces::updateWorkspaceStates() {
|
||||
const std::vector<std::string> visibleWorkspaces = getVisibleWorkspaces();
|
||||
const std::vector<int> visibleWorkspaces = getVisibleWorkspaces();
|
||||
auto updatedWorkspaces = m_ipc.getSocket1JsonReply("workspaces");
|
||||
for (auto &workspace : m_workspaces) {
|
||||
workspace->setActive(workspace->name() == m_activeWorkspaceName ||
|
||||
workspace->name() == m_activeSpecialWorkspaceName);
|
||||
workspace->setActive(
|
||||
workspace->id() == m_activeWorkspaceId ||
|
||||
(workspace->isSpecial() && workspace->name() == m_activeSpecialWorkspaceName));
|
||||
if (workspace->isActive() && workspace->isUrgent()) {
|
||||
workspace->setUrgent(false);
|
||||
}
|
||||
workspace->setVisible(std::find(visibleWorkspaces.begin(), visibleWorkspaces.end(),
|
||||
workspace->name()) != visibleWorkspaces.end());
|
||||
workspace->setVisible(std::ranges::find(visibleWorkspaces, workspace->id()) !=
|
||||
visibleWorkspaces.end());
|
||||
std::string &workspaceIcon = m_iconsMap[""];
|
||||
if (m_withIcon) {
|
||||
workspaceIcon = workspace->selectIcon(m_iconsMap);
|
||||
}
|
||||
auto updatedWorkspace = std::find_if(
|
||||
updatedWorkspaces.begin(), updatedWorkspaces.end(), [&workspace](const auto &w) {
|
||||
auto wNameRaw = w["name"].asString();
|
||||
auto wName = wNameRaw.starts_with("special:") ? wNameRaw.substr(8) : wNameRaw;
|
||||
return wName == workspace->name();
|
||||
});
|
||||
auto updatedWorkspace = std::ranges::find_if(updatedWorkspaces, [&workspace](const auto &w) {
|
||||
auto wNameRaw = w["name"].asString();
|
||||
auto wName = wNameRaw.starts_with("special:") ? wNameRaw.substr(8) : wNameRaw;
|
||||
return wName == workspace->name();
|
||||
});
|
||||
if (updatedWorkspace != updatedWorkspaces.end()) {
|
||||
workspace->setOutput((*updatedWorkspace)["monitor"].asString());
|
||||
}
|
||||
@@ -908,4 +989,39 @@ int Workspaces::windowRewritePriorityFunction(std::string const &window_rule) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
std::string Workspaces::makePayload(Args const &...args) {
|
||||
std::ostringstream result;
|
||||
bool first = true;
|
||||
((result << (first ? "" : ",") << args, first = false), ...);
|
||||
return result.str();
|
||||
}
|
||||
|
||||
std::pair<std::string, std::string> Workspaces::splitDoublePayload(std::string const &payload) {
|
||||
const std::string part1 = payload.substr(0, payload.find(','));
|
||||
const std::string part2 = payload.substr(part1.size() + 1);
|
||||
return {part1, part2};
|
||||
}
|
||||
|
||||
std::tuple<std::string, std::string, std::string> Workspaces::splitTriplePayload(
|
||||
std::string const &payload) {
|
||||
const size_t firstComma = payload.find(',');
|
||||
const size_t secondComma = payload.find(',', firstComma + 1);
|
||||
|
||||
const std::string part1 = payload.substr(0, firstComma);
|
||||
const std::string part2 = payload.substr(firstComma + 1, secondComma - (firstComma + 1));
|
||||
const std::string part3 = payload.substr(secondComma + 1);
|
||||
|
||||
return {part1, part2, part3};
|
||||
}
|
||||
|
||||
std::optional<int> Workspaces::parseWorkspaceId(std::string const &workspaceIdStr) {
|
||||
try {
|
||||
return workspaceIdStr == "special" ? -99 : std::stoi(workspaceIdStr);
|
||||
} catch (std::exception const &e) {
|
||||
spdlog::error("Failed to parse workspace ID: {}", e.what());
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace waybar::modules::hyprland
|
||||
|
||||
@@ -60,6 +60,7 @@ auto waybar::modules::Memory::update() -> void {
|
||||
fmt::arg("icon", getIcon(used_ram_percentage, icons)),
|
||||
fmt::arg("total", total_ram_gigabytes), fmt::arg("swapTotal", total_swap_gigabytes),
|
||||
fmt::arg("percentage", used_ram_percentage),
|
||||
fmt::arg("swapState", swaptotal == 0 ? "Off" : "On"),
|
||||
fmt::arg("swapPercentage", used_swap_percentage), fmt::arg("used", used_ram_gigabytes),
|
||||
fmt::arg("swapUsed", used_swap_gigabytes), fmt::arg("avail", available_ram_gigabytes),
|
||||
fmt::arg("swapAvail", available_swap_gigabytes)));
|
||||
@@ -72,6 +73,7 @@ auto waybar::modules::Memory::update() -> void {
|
||||
fmt::runtime(tooltip_format), used_ram_percentage,
|
||||
fmt::arg("total", total_ram_gigabytes), fmt::arg("swapTotal", total_swap_gigabytes),
|
||||
fmt::arg("percentage", used_ram_percentage),
|
||||
fmt::arg("swapState", swaptotal == 0 ? "Off" : "On"),
|
||||
fmt::arg("swapPercentage", used_swap_percentage), fmt::arg("used", used_ram_gigabytes),
|
||||
fmt::arg("swapUsed", used_swap_gigabytes), fmt::arg("avail", available_ram_gigabytes),
|
||||
fmt::arg("swapAvail", available_swap_gigabytes)));
|
||||
|
||||
@@ -14,7 +14,6 @@ extern "C" {
|
||||
|
||||
#include <glib.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
namespace waybar::modules::mpris {
|
||||
|
||||
const std::string DEFAULT_FORMAT = "{player} ({status}): {dynamic}";
|
||||
@@ -425,9 +424,11 @@ auto Mpris::onPlayerNameVanished(PlayerctlPlayerManager* manager, PlayerctlPlaye
|
||||
auto* mpris = static_cast<Mpris*>(data);
|
||||
if (!mpris) return;
|
||||
|
||||
spdlog::debug("mpris: player-vanished callback: {}", player_name->name);
|
||||
spdlog::debug("mpris: name-vanished callback: {}", player_name->name);
|
||||
|
||||
if (std::string(player_name->name) == mpris->player_) {
|
||||
if (mpris->player_ == "playerctld") {
|
||||
mpris->dp.emit();
|
||||
} else if (mpris->player_ == player_name->name) {
|
||||
mpris->player = nullptr;
|
||||
mpris->event_box_.set_visible(false);
|
||||
mpris->dp.emit();
|
||||
@@ -498,7 +499,10 @@ auto Mpris::getPlayerInfo() -> std::optional<PlayerInfo> {
|
||||
// > get the list of players [..] in order of activity
|
||||
// https://github.com/altdesktop/playerctl/blob/b19a71cb9dba635df68d271bd2b3f6a99336a223/playerctl/playerctl-common.c#L248-L249
|
||||
players = g_list_first(players);
|
||||
if (players) player_name = static_cast<PlayerctlPlayerName*>(players->data)->name;
|
||||
if (players)
|
||||
player_name = static_cast<PlayerctlPlayerName*>(players->data)->name;
|
||||
else
|
||||
return std::nullopt; // no players found, hide the widget
|
||||
}
|
||||
|
||||
if (std::any_of(ignored_players_.begin(), ignored_players_.end(),
|
||||
@@ -584,38 +588,45 @@ errorexit:
|
||||
}
|
||||
|
||||
bool Mpris::handleToggle(GdkEventButton* const& e) {
|
||||
if (!e || e->type != GdkEventType::GDK_BUTTON_PRESS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto info = getPlayerInfo();
|
||||
if (!info) return false;
|
||||
|
||||
struct ButtonAction {
|
||||
guint button;
|
||||
const char* config_key;
|
||||
std::function<void()> builtin_action;
|
||||
};
|
||||
|
||||
GError* error = nullptr;
|
||||
waybar::util::ScopeGuard error_deleter([error]() {
|
||||
waybar::util::ScopeGuard error_deleter([&error]() {
|
||||
if (error) {
|
||||
g_error_free(error);
|
||||
}
|
||||
});
|
||||
|
||||
auto info = getPlayerInfo();
|
||||
if (!info) return false;
|
||||
// Command pattern: encapsulate each button's action
|
||||
const ButtonAction actions[] = {
|
||||
{1, "on-click", [&]() { playerctl_player_play_pause(player, &error); }},
|
||||
{2, "on-click-middle", [&]() { playerctl_player_previous(player, &error); }},
|
||||
{3, "on-click-right", [&]() { playerctl_player_next(player, &error); }},
|
||||
{8, "on-click-backward", [&]() { playerctl_player_previous(player, &error); }},
|
||||
{9, "on-click-forward", [&]() { playerctl_player_next(player, &error); }},
|
||||
};
|
||||
|
||||
if (e->type == GdkEventType::GDK_BUTTON_PRESS) {
|
||||
switch (e->button) {
|
||||
case 1: // left-click
|
||||
if (config_["on-click"].isString()) {
|
||||
return ALabel::handleToggle(e);
|
||||
}
|
||||
playerctl_player_play_pause(player, &error);
|
||||
break;
|
||||
case 2: // middle-click
|
||||
if (config_["on-click-middle"].isString()) {
|
||||
return ALabel::handleToggle(e);
|
||||
}
|
||||
playerctl_player_previous(player, &error);
|
||||
break;
|
||||
case 3: // right-click
|
||||
if (config_["on-click-right"].isString()) {
|
||||
return ALabel::handleToggle(e);
|
||||
}
|
||||
playerctl_player_next(player, &error);
|
||||
break;
|
||||
for (const auto& action : actions) {
|
||||
if (e->button == action.button) {
|
||||
if (config_[action.config_key].isString()) {
|
||||
return ALabel::handleToggle(e);
|
||||
}
|
||||
action.builtin_action();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (error) {
|
||||
spdlog::error("mpris[{}]: error running builtin on-click action: {}", (*info).name,
|
||||
error->message);
|
||||
|
||||
@@ -80,6 +80,7 @@ waybar::modules::Network::readBandwidthUsage() {
|
||||
waybar::modules::Network::Network(const std::string &id, const Json::Value &config)
|
||||
: ALabel(config, "network", id, DEFAULT_FORMAT, 60),
|
||||
ifid_(-1),
|
||||
addr_pref_(IPV4),
|
||||
efd_(-1),
|
||||
ev_fd_(-1),
|
||||
want_route_dump_(false),
|
||||
@@ -88,6 +89,7 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf
|
||||
dump_in_progress_(false),
|
||||
is_p2p_(false),
|
||||
cidr_(0),
|
||||
cidr6_(0),
|
||||
signal_strength_dbm_(0),
|
||||
signal_strength_(0),
|
||||
#ifdef WANT_RFKILL
|
||||
@@ -101,6 +103,12 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf
|
||||
// the module start with no text, but the event_box_ is shown.
|
||||
label_.set_markup("<s></s>");
|
||||
|
||||
if (config_["family"] == "ipv6") {
|
||||
addr_pref_ = IPV6;
|
||||
} else if (config["family"] == "ipv4_6") {
|
||||
addr_pref_ = IPV4_6;
|
||||
}
|
||||
|
||||
auto bandwidth = readBandwidthUsage();
|
||||
if (bandwidth.has_value()) {
|
||||
bandwidth_down_total_ = (*bandwidth).first;
|
||||
@@ -215,8 +223,8 @@ void waybar::modules::Network::worker() {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (ifid_ > 0) {
|
||||
getInfo();
|
||||
dp.emit();
|
||||
}
|
||||
dp.emit();
|
||||
}
|
||||
thread_timer_.sleep_for(interval_);
|
||||
};
|
||||
@@ -263,14 +271,14 @@ void waybar::modules::Network::worker() {
|
||||
}
|
||||
|
||||
const std::string waybar::modules::Network::getNetworkState() const {
|
||||
if (ifid_ == -1) {
|
||||
#ifdef WANT_RFKILL
|
||||
if (rfkill_.getState()) return "disabled";
|
||||
if (rfkill_.getState()) return "disabled";
|
||||
#endif
|
||||
if (ifid_ == -1) {
|
||||
return "disconnected";
|
||||
}
|
||||
if (!carrier_) return "disconnected";
|
||||
if (ipaddr_.empty()) return "linked";
|
||||
if (ipaddr_.empty() && ipaddr6_.empty()) return "linked";
|
||||
if (essid_.empty()) return "ethernet";
|
||||
return "wifi";
|
||||
}
|
||||
@@ -316,12 +324,24 @@ auto waybar::modules::Network::update() -> void {
|
||||
}
|
||||
getState(signal_strength_);
|
||||
|
||||
std::string final_ipaddr_;
|
||||
if (addr_pref_ == ip_addr_pref::IPV4) {
|
||||
final_ipaddr_ = ipaddr_;
|
||||
} else if (addr_pref_ == ip_addr_pref::IPV6) {
|
||||
final_ipaddr_ = ipaddr6_;
|
||||
} else if (addr_pref_ == ip_addr_pref::IPV4_6) {
|
||||
final_ipaddr_ = ipaddr_;
|
||||
final_ipaddr_ += '\n';
|
||||
final_ipaddr_ += ipaddr6_;
|
||||
}
|
||||
|
||||
auto text = fmt::format(
|
||||
fmt::runtime(format_), fmt::arg("essid", essid_), fmt::arg("bssid", bssid_),
|
||||
fmt::arg("signaldBm", signal_strength_dbm_), fmt::arg("signalStrength", signal_strength_),
|
||||
fmt::arg("signalStrengthApp", signal_strength_app_), fmt::arg("ifname", ifname_),
|
||||
fmt::arg("netmask", netmask_), fmt::arg("ipaddr", ipaddr_), fmt::arg("gwaddr", gwaddr_),
|
||||
fmt::arg("cidr", cidr_), fmt::arg("frequency", fmt::format("{:.1f}", frequency_)),
|
||||
fmt::arg("netmask", netmask_), fmt::arg("netmask6", netmask6_),
|
||||
fmt::arg("ipaddr", final_ipaddr_), fmt::arg("gwaddr", gwaddr_), fmt::arg("cidr", cidr_),
|
||||
fmt::arg("cidr6", cidr6_), fmt::arg("frequency", fmt::format("{:.1f}", frequency_)),
|
||||
fmt::arg("icon", getIcon(signal_strength_, state_)),
|
||||
fmt::arg("bandwidthDownBits", pow_format(bandwidth_down * 8ull / interval_.count(), "b/s")),
|
||||
fmt::arg("bandwidthUpBits", pow_format(bandwidth_up * 8ull / interval_.count(), "b/s")),
|
||||
@@ -352,8 +372,9 @@ auto waybar::modules::Network::update() -> void {
|
||||
fmt::runtime(tooltip_format), fmt::arg("essid", essid_), fmt::arg("bssid", bssid_),
|
||||
fmt::arg("signaldBm", signal_strength_dbm_), fmt::arg("signalStrength", signal_strength_),
|
||||
fmt::arg("signalStrengthApp", signal_strength_app_), fmt::arg("ifname", ifname_),
|
||||
fmt::arg("netmask", netmask_), fmt::arg("ipaddr", ipaddr_), fmt::arg("gwaddr", gwaddr_),
|
||||
fmt::arg("cidr", cidr_), fmt::arg("frequency", fmt::format("{:.1f}", frequency_)),
|
||||
fmt::arg("netmask", netmask_), fmt::arg("netmask6", netmask6_),
|
||||
fmt::arg("ipaddr", final_ipaddr_), fmt::arg("gwaddr", gwaddr_), fmt::arg("cidr", cidr_),
|
||||
fmt::arg("cidr6", cidr6_), fmt::arg("frequency", fmt::format("{:.1f}", frequency_)),
|
||||
fmt::arg("icon", getIcon(signal_strength_, state_)),
|
||||
fmt::arg("bandwidthDownBits",
|
||||
pow_format(bandwidth_down * 8ull / interval_.count(), "b/s")),
|
||||
@@ -394,10 +415,13 @@ void waybar::modules::Network::clearIface() {
|
||||
essid_.clear();
|
||||
bssid_.clear();
|
||||
ipaddr_.clear();
|
||||
ipaddr6_.clear();
|
||||
gwaddr_.clear();
|
||||
netmask_.clear();
|
||||
netmask6_.clear();
|
||||
carrier_ = false;
|
||||
cidr_ = 0;
|
||||
cidr6_ = 0;
|
||||
signal_strength_dbm_ = 0;
|
||||
signal_strength_ = 0;
|
||||
signal_strength_app_.clear();
|
||||
@@ -521,7 +545,6 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
|
||||
if (ifa->ifa_scope >= RT_SCOPE_LINK) {
|
||||
return NL_OK;
|
||||
}
|
||||
|
||||
for (; RTA_OK(ifa_rta, attrlen); ifa_rta = RTA_NEXT(ifa_rta, attrlen)) {
|
||||
switch (ifa_rta->rta_type) {
|
||||
case IFA_ADDRESS:
|
||||
@@ -529,8 +552,20 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
|
||||
case IFA_LOCAL:
|
||||
char ipaddr[INET6_ADDRSTRLEN];
|
||||
if (!is_del_event) {
|
||||
net->ipaddr_ = inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta), ipaddr, sizeof(ipaddr));
|
||||
net->cidr_ = ifa->ifa_prefixlen;
|
||||
if ((net->addr_pref_ == ip_addr_pref::IPV4 ||
|
||||
net->addr_pref_ == ip_addr_pref::IPV4_6) &&
|
||||
net->cidr_ == 0 && ifa->ifa_family == AF_INET) {
|
||||
net->ipaddr_ =
|
||||
inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta), ipaddr, sizeof(ipaddr));
|
||||
net->cidr_ = ifa->ifa_prefixlen;
|
||||
} else if ((net->addr_pref_ == ip_addr_pref::IPV6 ||
|
||||
net->addr_pref_ == ip_addr_pref::IPV4_6) &&
|
||||
net->cidr6_ == 0 && ifa->ifa_family == AF_INET6) {
|
||||
net->ipaddr6_ =
|
||||
inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta), ipaddr, sizeof(ipaddr));
|
||||
net->cidr6_ = ifa->ifa_prefixlen;
|
||||
}
|
||||
|
||||
switch (ifa->ifa_family) {
|
||||
case AF_INET: {
|
||||
struct in_addr netmask;
|
||||
@@ -538,21 +573,24 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
|
||||
net->netmask_ = inet_ntop(ifa->ifa_family, &netmask, ipaddr, sizeof(ipaddr));
|
||||
}
|
||||
case AF_INET6: {
|
||||
struct in6_addr netmask;
|
||||
struct in6_addr netmask6;
|
||||
for (int i = 0; i < 16; i++) {
|
||||
int v = (i + 1) * 8 - ifa->ifa_prefixlen;
|
||||
if (v < 0) v = 0;
|
||||
if (v > 8) v = 8;
|
||||
netmask.s6_addr[i] = ~0 << v;
|
||||
netmask6.s6_addr[i] = ~0 << v;
|
||||
}
|
||||
net->netmask_ = inet_ntop(ifa->ifa_family, &netmask, ipaddr, sizeof(ipaddr));
|
||||
net->netmask6_ = inet_ntop(ifa->ifa_family, &netmask6, ipaddr, sizeof(ipaddr));
|
||||
}
|
||||
}
|
||||
spdlog::debug("network: {}, new addr {}/{}", net->ifname_, net->ipaddr_, net->cidr_);
|
||||
} else {
|
||||
net->ipaddr_.clear();
|
||||
net->ipaddr6_.clear();
|
||||
net->cidr_ = 0;
|
||||
net->cidr6_ = 0;
|
||||
net->netmask_.clear();
|
||||
net->netmask6_.clear();
|
||||
spdlog::debug("network: {} addr deleted {}/{}", net->ifname_,
|
||||
inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta), ipaddr, sizeof(ipaddr)),
|
||||
ifa->ifa_prefixlen);
|
||||
|
||||
@@ -147,6 +147,17 @@ void IPC::parseIPC(const std::string &line) {
|
||||
} else {
|
||||
spdlog::error("Active window changed on unknown workspace");
|
||||
}
|
||||
} else if (const auto &payload = ev["WorkspaceUrgencyChanged"]) {
|
||||
const auto id = payload["id"].asUInt64();
|
||||
const auto urgent = payload["urgent"].asBool();
|
||||
auto it = std::find_if(workspaces_.begin(), workspaces_.end(),
|
||||
[id](const auto &ws) { return ws["id"].asUInt64() == id; });
|
||||
if (it != workspaces_.end()) {
|
||||
auto &ws = *it;
|
||||
ws["is_urgent"] = urgent;
|
||||
} else {
|
||||
spdlog::error("Urgency changed for unknown workspace");
|
||||
}
|
||||
} else if (const auto &payload = ev["KeyboardLayoutsChanged"]) {
|
||||
const auto &layouts = payload["keyboard_layouts"];
|
||||
const auto &names = layouts["names"];
|
||||
|
||||
@@ -20,6 +20,7 @@ Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value
|
||||
gIPC->registerForIPC("WorkspacesChanged", this);
|
||||
gIPC->registerForIPC("WorkspaceActivated", this);
|
||||
gIPC->registerForIPC("WorkspaceActiveWindowChanged", this);
|
||||
gIPC->registerForIPC("WorkspaceUrgencyChanged", this);
|
||||
|
||||
dp.emit();
|
||||
}
|
||||
@@ -67,6 +68,11 @@ void Workspaces::doUpdate() {
|
||||
else
|
||||
style_context->remove_class("active");
|
||||
|
||||
if (ws["is_urgent"].asBool())
|
||||
style_context->add_class("urgent");
|
||||
else
|
||||
style_context->remove_class("urgent");
|
||||
|
||||
if (ws["output"]) {
|
||||
if (ws["output"].asString() == bar_.output->name)
|
||||
style_context->add_class("current_output");
|
||||
@@ -166,6 +172,10 @@ std::string Workspaces::getIcon(const std::string &value, const Json::Value &ws)
|
||||
const auto &icons = config_["format-icons"];
|
||||
if (!icons) return value;
|
||||
|
||||
if (ws["is_urgent"].asBool() && icons["urgent"]) return icons["urgent"].asString();
|
||||
|
||||
if (ws["active_window_id"].isNull() && icons["empty"]) return icons["empty"].asString();
|
||||
|
||||
if (ws["is_focused"].asBool() && icons["focused"]) return icons["focused"].asString();
|
||||
|
||||
if (ws["is_active"].asBool() && icons["active"]) return icons["active"].asString();
|
||||
|
||||
@@ -29,14 +29,14 @@ PowerProfilesDaemon::PowerProfilesDaemon(const std::string& id, const Json::Valu
|
||||
// method on the proxy to see whether or not something's responding
|
||||
// on the other side.
|
||||
|
||||
// NOTE: the DBus adresses are under migration. They should be
|
||||
// NOTE: the DBus addresses are under migration. They should be
|
||||
// changed to org.freedesktop.UPower.PowerProfiles at some point.
|
||||
//
|
||||
// See
|
||||
// https://gitlab.freedesktop.org/upower/power-profiles-daemon/-/releases/0.20
|
||||
//
|
||||
// The old name is still announced for now. Let's rather use the old
|
||||
// adresses for compatibility sake.
|
||||
// addresses for compatibility sake.
|
||||
//
|
||||
// Revisit this in 2026, systems should be updated by then.
|
||||
|
||||
|
||||
@@ -15,13 +15,14 @@ using util::PipewireBackend::PRIVACY_NODE_TYPE_AUDIO_OUTPUT;
|
||||
using util::PipewireBackend::PRIVACY_NODE_TYPE_NONE;
|
||||
using util::PipewireBackend::PRIVACY_NODE_TYPE_VIDEO_INPUT;
|
||||
|
||||
Privacy::Privacy(const std::string& id, const Json::Value& config, const std::string& pos)
|
||||
Privacy::Privacy(const std::string& id, const Json::Value& config, Gtk::Orientation orientation,
|
||||
const std::string& pos)
|
||||
: AModule(config, "privacy", id),
|
||||
nodes_screenshare(),
|
||||
nodes_audio_in(),
|
||||
nodes_audio_out(),
|
||||
visibility_conn(),
|
||||
box_(Gtk::ORIENTATION_HORIZONTAL, 0) {
|
||||
box_(orientation, 0) {
|
||||
box_.set_name(name_);
|
||||
|
||||
event_box_.add(box_);
|
||||
@@ -67,12 +68,30 @@ Privacy::Privacy(const std::string& id, const Json::Value& config, const std::st
|
||||
auto iter = typeMap.find(type);
|
||||
if (iter != typeMap.end()) {
|
||||
auto& [nodePtr, nodeType] = iter->second;
|
||||
auto* item = Gtk::make_managed<PrivacyItem>(module, nodeType, nodePtr, pos, iconSize,
|
||||
transition_duration);
|
||||
auto* item = Gtk::make_managed<PrivacyItem>(module, nodeType, nodePtr, orientation, pos,
|
||||
iconSize, transition_duration);
|
||||
box_.add(*item);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& ignore_item : config_["ignore"]) {
|
||||
if (!ignore_item.isObject() || !ignore_item["type"].isString() ||
|
||||
!ignore_item["name"].isString())
|
||||
continue;
|
||||
const std::string type = ignore_item["type"].asString();
|
||||
const std::string name = ignore_item["name"].asString();
|
||||
|
||||
auto iter = typeMap.find(type);
|
||||
if (iter != typeMap.end()) {
|
||||
auto& [_, nodeType] = iter->second;
|
||||
ignore.emplace(nodeType, std::move(name));
|
||||
}
|
||||
}
|
||||
|
||||
if (config_["ignore-monitor"].isBool()) {
|
||||
ignore_monitor = config_["ignore-monitor"].asBool();
|
||||
}
|
||||
|
||||
backend = util::PipewireBackend::PipewireBackend::getInstance();
|
||||
backend->privacy_nodes_changed_signal_event.connect(
|
||||
sigc::mem_fun(*this, &Privacy::onPrivacyNodesChanged));
|
||||
@@ -87,6 +106,11 @@ void Privacy::onPrivacyNodesChanged() {
|
||||
nodes_screenshare.clear();
|
||||
|
||||
for (auto& node : backend->privacy_nodes) {
|
||||
if (ignore_monitor && node.second->is_monitor) continue;
|
||||
|
||||
auto iter = ignore.find(std::pair(node.second->type, node.second->node_name));
|
||||
if (iter != ignore.end()) continue;
|
||||
|
||||
switch (node.second->state) {
|
||||
case PW_NODE_STATE_RUNNING:
|
||||
switch (node.second->type) {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "modules/privacy/privacy_item.hpp"
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "glibmm/main.h"
|
||||
@@ -11,8 +13,9 @@
|
||||
namespace waybar::modules::privacy {
|
||||
|
||||
PrivacyItem::PrivacyItem(const Json::Value &config_, enum PrivacyNodeType privacy_type_,
|
||||
std::list<PrivacyNodeInfo *> *nodes_, const std::string &pos,
|
||||
const uint icon_size, const uint transition_duration)
|
||||
std::list<PrivacyNodeInfo *> *nodes_, Gtk::Orientation orientation,
|
||||
const std::string &pos, const uint icon_size,
|
||||
const uint transition_duration)
|
||||
: Gtk::Revealer(),
|
||||
privacy_type(privacy_type_),
|
||||
nodes(nodes_),
|
||||
@@ -40,16 +43,24 @@ PrivacyItem::PrivacyItem(const Json::Value &config_, enum PrivacyNodeType privac
|
||||
|
||||
// Set the reveal transition to not look weird when sliding in
|
||||
if (pos == "modules-left") {
|
||||
set_transition_type(Gtk::REVEALER_TRANSITION_TYPE_SLIDE_RIGHT);
|
||||
set_transition_type(orientation == Gtk::ORIENTATION_HORIZONTAL
|
||||
? Gtk::REVEALER_TRANSITION_TYPE_SLIDE_RIGHT
|
||||
: Gtk::REVEALER_TRANSITION_TYPE_SLIDE_DOWN);
|
||||
} else if (pos == "modules-center") {
|
||||
set_transition_type(Gtk::REVEALER_TRANSITION_TYPE_CROSSFADE);
|
||||
} else if (pos == "modules-right") {
|
||||
set_transition_type(Gtk::REVEALER_TRANSITION_TYPE_SLIDE_LEFT);
|
||||
set_transition_type(orientation == Gtk::ORIENTATION_HORIZONTAL
|
||||
? Gtk::REVEALER_TRANSITION_TYPE_SLIDE_LEFT
|
||||
: Gtk::REVEALER_TRANSITION_TYPE_SLIDE_UP);
|
||||
}
|
||||
set_transition_duration(transition_duration);
|
||||
|
||||
box_.set_name("privacy-item");
|
||||
box_.add(icon_);
|
||||
|
||||
// We use `set_center_widget` instead of `add` to make sure the icon is
|
||||
// centered even if the orientation is vertical
|
||||
box_.set_center_widget(icon_);
|
||||
|
||||
icon_.set_pixel_size(icon_size);
|
||||
add(box_);
|
||||
|
||||
@@ -87,20 +98,22 @@ PrivacyItem::PrivacyItem(const Json::Value &config_, enum PrivacyNodeType privac
|
||||
void PrivacyItem::update_tooltip() {
|
||||
// Removes all old nodes
|
||||
for (auto *child : tooltip_window.get_children()) {
|
||||
tooltip_window.remove(*child);
|
||||
// despite the remove, still needs a delete to prevent memory leak. Speculating that this might
|
||||
// work differently in GTK4.
|
||||
delete child;
|
||||
}
|
||||
|
||||
for (auto *node : *nodes) {
|
||||
Gtk::Box *box = new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 4);
|
||||
auto *box = Gtk::make_managed<Gtk::Box>(Gtk::ORIENTATION_HORIZONTAL, 4);
|
||||
|
||||
// Set device icon
|
||||
Gtk::Image *node_icon = new Gtk::Image();
|
||||
auto *node_icon = Gtk::make_managed<Gtk::Image>();
|
||||
node_icon->set_pixel_size(tooltipIconSize);
|
||||
node_icon->set_from_icon_name(node->getIconName(), Gtk::ICON_SIZE_INVALID);
|
||||
box->add(*node_icon);
|
||||
|
||||
// Set model
|
||||
auto *nodeName = new Gtk::Label(node->getName());
|
||||
auto *nodeName = Gtk::make_managed<Gtk::Label>(node->getName());
|
||||
box->add(*nodeName);
|
||||
|
||||
tooltip_window.add(*box);
|
||||
|
||||
@@ -5,10 +5,12 @@
|
||||
#include <gtkmm/tooltip.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
|
||||
#include "gdk/gdk.h"
|
||||
#include "modules/sni/icon_manager.hpp"
|
||||
#include "util/format.hpp"
|
||||
#include "util/gtk_icon.hpp"
|
||||
|
||||
@@ -138,6 +140,7 @@ void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) {
|
||||
category = get_variant<std::string>(value);
|
||||
} else if (name == "Id") {
|
||||
id = get_variant<std::string>(value);
|
||||
setCustomIcon(id);
|
||||
} else if (name == "Title") {
|
||||
title = get_variant<std::string>(value);
|
||||
if (tooltip.text.empty()) {
|
||||
@@ -199,6 +202,19 @@ void Item::setStatus(const Glib::ustring& value) {
|
||||
style->add_class(lower);
|
||||
}
|
||||
|
||||
void Item::setCustomIcon(const std::string& id) {
|
||||
std::string custom_icon = IconManager::instance().getIconForApp(id);
|
||||
if (!custom_icon.empty()) {
|
||||
if (std::filesystem::exists(custom_icon)) {
|
||||
Glib::RefPtr<Gdk::Pixbuf> custom_pixbuf = Gdk::Pixbuf::create_from_file(custom_icon);
|
||||
icon_name = ""; // icon_name has priority over pixmap
|
||||
icon_pixmap = custom_pixbuf;
|
||||
} else { // if file doesn't exist it's most likely an icon_name
|
||||
icon_name = custom_icon;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Item::getUpdatedProperties() {
|
||||
auto params = Glib::VariantContainerBase::create_tuple(
|
||||
{Glib::Variant<Glib::ustring>::create(SNI_INTERFACE_NAME)});
|
||||
@@ -372,14 +388,17 @@ Glib::RefPtr<Gdk::Pixbuf> Item::getIconPixbuf() {
|
||||
Glib::RefPtr<Gdk::Pixbuf> Item::getIconByName(const std::string& name, int request_size) {
|
||||
icon_theme->rescan_if_needed();
|
||||
|
||||
if (!icon_theme_path.empty() &&
|
||||
icon_theme->lookup_icon(name.c_str(), request_size,
|
||||
Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE)) {
|
||||
return icon_theme->load_icon(name.c_str(), request_size,
|
||||
Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE);
|
||||
if (!icon_theme_path.empty()) {
|
||||
auto icon_info = icon_theme->lookup_icon(name.c_str(), request_size,
|
||||
Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE);
|
||||
if (icon_info) {
|
||||
bool is_sym = false;
|
||||
return icon_info.load_symbolic(event_box.get_style_context(), is_sym);
|
||||
}
|
||||
}
|
||||
return DefaultGtkIconThemeWrapper::load_icon(name.c_str(), request_size,
|
||||
Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE);
|
||||
Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE,
|
||||
event_box.get_style_context());
|
||||
}
|
||||
|
||||
double Item::getScaledIconSize() {
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include "modules/sni/icon_manager.hpp"
|
||||
|
||||
namespace waybar::modules::SNI {
|
||||
|
||||
Tray::Tray(const std::string& id, const Bar& bar, const Json::Value& config)
|
||||
@@ -20,6 +22,9 @@ Tray::Tray(const std::string& id, const Bar& bar, const Json::Value& config)
|
||||
box_.set_spacing(config_["spacing"].asUInt());
|
||||
}
|
||||
nb_hosts_ += 1;
|
||||
if (config_["icons"].isObject()) {
|
||||
IconManager::instance().setIconsConfig(config_["icons"]);
|
||||
}
|
||||
dp.emit();
|
||||
}
|
||||
|
||||
|
||||
@@ -22,10 +22,10 @@ Language::Language(const std::string& id, const Json::Value& config)
|
||||
hide_single_ = config["hide-single-layout"].isBool() && config["hide-single-layout"].asBool();
|
||||
is_variant_displayed = format_.find("{variant}") != std::string::npos;
|
||||
if (format_.find("{}") != std::string::npos || format_.find("{short}") != std::string::npos) {
|
||||
displayed_short_flag |= static_cast<std::byte>(DispayedShortFlag::ShortName);
|
||||
displayed_short_flag |= static_cast<std::byte>(DisplayedShortFlag::ShortName);
|
||||
}
|
||||
if (format_.find("{shortDescription}") != std::string::npos) {
|
||||
displayed_short_flag |= static_cast<std::byte>(DispayedShortFlag::ShortDescription);
|
||||
displayed_short_flag |= static_cast<std::byte>(DisplayedShortFlag::ShortDescription);
|
||||
}
|
||||
if (config.isMember("tooltip-format")) {
|
||||
tooltip_format_ = config["tooltip-format"].asString();
|
||||
|
||||
@@ -41,8 +41,8 @@ void Window::onCmd(const struct Ipc::ipc_response& res) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
auto payload = parser_.parse(res.payload);
|
||||
auto output = payload["output"].isString() ? payload["output"].asString() : "";
|
||||
std::tie(app_nb_, floating_count_, windowId_, window_, app_id_, app_class_, shell_, layout_) =
|
||||
getFocusedNode(payload["nodes"], output);
|
||||
std::tie(app_nb_, floating_count_, windowId_, window_, app_id_, app_class_, shell_, layout_,
|
||||
marks_) = getFocusedNode(payload["nodes"], output);
|
||||
updateAppIconName(app_id_, app_class_);
|
||||
dp.emit();
|
||||
} catch (const std::exception& e) {
|
||||
@@ -96,7 +96,7 @@ auto Window::update() -> void {
|
||||
|
||||
label_.set_markup(waybar::util::rewriteString(
|
||||
fmt::format(fmt::runtime(format_), fmt::arg("title", window_), fmt::arg("app_id", app_id_),
|
||||
fmt::arg("shell", shell_)),
|
||||
fmt::arg("shell", shell_), fmt::arg("marks", marks_)),
|
||||
config_["rewrite"]));
|
||||
if (tooltipEnabled()) {
|
||||
label_.set_tooltip_text(window_);
|
||||
@@ -108,7 +108,7 @@ auto Window::update() -> void {
|
||||
AAppIconLabel::update();
|
||||
}
|
||||
|
||||
void Window::setClass(std::string classname, bool enable) {
|
||||
void Window::setClass(const std::string& classname, bool enable) {
|
||||
if (enable) {
|
||||
if (!bar_.window.get_style_context()->has_class(classname)) {
|
||||
bar_.window.get_style_context()->add_class(classname);
|
||||
@@ -169,17 +169,31 @@ std::optional<std::reference_wrapper<const Json::Value>> getSingleChildNode(
|
||||
return {getSingleChildNode(child)};
|
||||
}
|
||||
|
||||
std::tuple<std::string, std::string, std::string> getWindowInfo(const Json::Value& node) {
|
||||
std::tuple<std::string, std::string, std::string, std::string> getWindowInfo(
|
||||
const Json::Value& node, bool showHidden) {
|
||||
const auto app_id = node["app_id"].isString() ? node["app_id"].asString()
|
||||
: node["window_properties"]["instance"].asString();
|
||||
const auto app_class = node["window_properties"]["class"].isString()
|
||||
? node["window_properties"]["class"].asString()
|
||||
: "";
|
||||
const auto shell = node["shell"].isString() ? node["shell"].asString() : "";
|
||||
return {app_id, app_class, shell};
|
||||
std::string marks = "";
|
||||
if (node["marks"].isArray()) {
|
||||
for (const auto& m : node["marks"]) {
|
||||
if (!m.isString() || (!showHidden && m.asString().at(0) == '_')) {
|
||||
continue;
|
||||
}
|
||||
if (!marks.empty()) {
|
||||
marks += ',';
|
||||
}
|
||||
marks += m.asString();
|
||||
}
|
||||
}
|
||||
return {app_id, app_class, shell, marks};
|
||||
}
|
||||
|
||||
std::tuple<std::size_t, int, int, std::string, std::string, std::string, std::string, std::string>
|
||||
std::tuple<std::size_t, int, int, std::string, std::string, std::string, std::string, std::string,
|
||||
std::string>
|
||||
gfnWithWorkspace(const Json::Value& nodes, std::string& output, const Json::Value& config_,
|
||||
const Bar& bar_, Json::Value& parentWorkspace,
|
||||
const Json::Value& immediateParent) {
|
||||
@@ -207,7 +221,8 @@ gfnWithWorkspace(const Json::Value& nodes, std::string& output, const Json::Valu
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
node["layout"].asString()};
|
||||
node["layout"].asString(),
|
||||
""};
|
||||
}
|
||||
parentWorkspace = node;
|
||||
} else if ((node["type"].asString() == "con" || node["type"].asString() == "floating_con") &&
|
||||
@@ -215,7 +230,8 @@ gfnWithWorkspace(const Json::Value& nodes, std::string& output, const Json::Valu
|
||||
// found node
|
||||
spdlog::trace("actual output {}, output found {}, node (focused) found {}", bar_.output->name,
|
||||
output, node["name"].asString());
|
||||
const auto [app_id, app_class, shell] = getWindowInfo(node);
|
||||
const auto [app_id, app_class, shell, marks] =
|
||||
getWindowInfo(node, config_["show-hidden-marks"].asBool());
|
||||
int nb = node.size();
|
||||
int floating_count = 0;
|
||||
std::string workspace_layout = "";
|
||||
@@ -232,20 +248,21 @@ gfnWithWorkspace(const Json::Value& nodes, std::string& output, const Json::Valu
|
||||
app_id,
|
||||
app_class,
|
||||
shell,
|
||||
workspace_layout};
|
||||
workspace_layout,
|
||||
marks};
|
||||
}
|
||||
|
||||
// iterate
|
||||
auto [nb, f, id, name, app_id, app_class, shell, workspace_layout] =
|
||||
auto [nb, f, id, name, app_id, app_class, shell, workspace_layout, marks] =
|
||||
gfnWithWorkspace(node["nodes"], output, config_, bar_, parentWorkspace, node);
|
||||
auto [nb2, f2, id2, name2, app_id2, app_class2, shell2, workspace_layout2] =
|
||||
auto [nb2, f2, id2, name2, app_id2, app_class2, shell2, workspace_layout2, marks2] =
|
||||
gfnWithWorkspace(node["floating_nodes"], output, config_, bar_, parentWorkspace, node);
|
||||
|
||||
// if ((id > 0 || ((id2 < 0 || name2.empty()) && id > -1)) && !name.empty()) {
|
||||
if ((id > 0) || (id2 < 0 && id > -1)) {
|
||||
return {nb, f, id, name, app_id, app_class, shell, workspace_layout};
|
||||
return {nb, f, id, name, app_id, app_class, shell, workspace_layout, marks};
|
||||
} else if (id2 > 0 && !name2.empty()) {
|
||||
return {nb2, f2, id2, name2, app_id2, app_class, shell2, workspace_layout2};
|
||||
return {nb2, f2, id2, name2, app_id2, app_class, shell2, workspace_layout2, marks2};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,10 +275,12 @@ gfnWithWorkspace(const Json::Value& nodes, std::string& output, const Json::Valu
|
||||
std::string app_id = "";
|
||||
std::string app_class = "";
|
||||
std::string workspace_layout = "";
|
||||
std::string marks = "";
|
||||
if (all_leaf_nodes.first == 1) {
|
||||
const auto single_child = getSingleChildNode(immediateParent);
|
||||
if (single_child.has_value()) {
|
||||
std::tie(app_id, app_class, workspace_layout) = getWindowInfo(single_child.value());
|
||||
std::tie(app_id, app_class, workspace_layout, marks) =
|
||||
getWindowInfo(single_child.value(), config_["show-hidden-marks"].asBool());
|
||||
}
|
||||
}
|
||||
return {all_leaf_nodes.first,
|
||||
@@ -273,13 +292,15 @@ gfnWithWorkspace(const Json::Value& nodes, std::string& output, const Json::Valu
|
||||
app_id,
|
||||
app_class,
|
||||
workspace_layout,
|
||||
immediateParent["layout"].asString()};
|
||||
immediateParent["layout"].asString(),
|
||||
marks};
|
||||
}
|
||||
|
||||
return {0, 0, -1, "", "", "", "", ""};
|
||||
return {0, 0, -1, "", "", "", "", "", ""};
|
||||
}
|
||||
|
||||
std::tuple<std::size_t, int, int, std::string, std::string, std::string, std::string, std::string>
|
||||
std::tuple<std::size_t, int, int, std::string, std::string, std::string, std::string, std::string,
|
||||
std::string>
|
||||
Window::getFocusedNode(const Json::Value& nodes, std::string& output) {
|
||||
Json::Value placeholder = Json::Value::null;
|
||||
return gfnWithWorkspace(nodes, output, config_, bar_, placeholder, placeholder);
|
||||
|
||||
@@ -57,9 +57,9 @@ Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value
|
||||
box_.get_style_context()->add_class(MODULE_CLASS);
|
||||
event_box_.add(box_);
|
||||
if (config_["format-window-separator"].isString()) {
|
||||
m_formatWindowSeperator = config_["format-window-separator"].asString();
|
||||
m_formatWindowSeparator = config_["format-window-separator"].asString();
|
||||
} else {
|
||||
m_formatWindowSeperator = " ";
|
||||
m_formatWindowSeparator = " ";
|
||||
}
|
||||
const Json::Value &windowRewrite = config["window-rewrite"];
|
||||
if (windowRewrite.isObject()) {
|
||||
@@ -271,7 +271,7 @@ void Workspaces::updateWindows(const Json::Value &node, std::string &windows) {
|
||||
window = fmt::format(fmt::runtime(window), fmt::arg("name", title),
|
||||
fmt::arg("class", windowClass));
|
||||
windows.append(window);
|
||||
windows.append(m_formatWindowSeperator);
|
||||
windows.append(m_formatWindowSeparator);
|
||||
}
|
||||
}
|
||||
for (const Json::Value &child : node["nodes"]) {
|
||||
@@ -340,7 +340,7 @@ auto Workspaces::update() -> void {
|
||||
fmt::runtime(format), fmt::arg("icon", getIcon(output, *it)), fmt::arg("value", output),
|
||||
fmt::arg("name", trimWorkspaceName(output)), fmt::arg("index", (*it)["num"].asString()),
|
||||
fmt::arg("windows",
|
||||
windows.substr(0, windows.length() - m_formatWindowSeperator.length())),
|
||||
windows.substr(0, windows.length() - m_formatWindowSeparator.length())),
|
||||
fmt::arg("output", (*it)["output"].asString()));
|
||||
}
|
||||
if (!config_["disable-markup"].asBool()) {
|
||||
@@ -494,16 +494,34 @@ std::string Workspaces::trimWorkspaceName(std::string name) {
|
||||
return name;
|
||||
}
|
||||
|
||||
bool is_focused_recursive(const Json::Value &node) {
|
||||
// If a workspace has a focused container then get_tree will say
|
||||
// that the workspace itself isn't focused. Therefore we need to
|
||||
// check if any of its nodes are focused as well.
|
||||
// some layouts like tabbed have many nested nodes
|
||||
// all nested nodes must be checked for focused flag
|
||||
if (node["focused"].asBool()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const auto &child : node["nodes"]) {
|
||||
if (is_focused_recursive(child)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &child : node["floating_nodes"]) {
|
||||
if (is_focused_recursive(child)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Workspaces::onButtonReady(const Json::Value &node, Gtk::Button &button) {
|
||||
if (config_["current-only"].asBool()) {
|
||||
// If a workspace has a focused container then get_tree will say
|
||||
// that the workspace itself isn't focused. Therefore we need to
|
||||
// check if any of its nodes are focused as well.
|
||||
bool focused = node["focused"].asBool() ||
|
||||
std::any_of(node["nodes"].begin(), node["nodes"].end(),
|
||||
[](const auto &child) { return child["focused"].asBool(); });
|
||||
|
||||
if (focused) {
|
||||
if (is_focused_recursive(node)) {
|
||||
button.show();
|
||||
} else {
|
||||
button.hide();
|
||||
|
||||
@@ -16,6 +16,7 @@ SystemdFailedUnits::SystemdFailedUnits(const std::string& id, const Json::Value&
|
||||
update_pending(false),
|
||||
nr_failed_system(0),
|
||||
nr_failed_user(0),
|
||||
nr_failed(0),
|
||||
last_status() {
|
||||
if (config["hide-on-ok"].isBool()) {
|
||||
hide_on_ok = config["hide-on-ok"].asBool();
|
||||
@@ -67,11 +68,38 @@ auto SystemdFailedUnits::notify_cb(const Glib::ustring& sender_name,
|
||||
}
|
||||
}
|
||||
|
||||
void SystemdFailedUnits::updateData() {
|
||||
update_pending = false;
|
||||
void SystemdFailedUnits::RequestSystemState() {
|
||||
auto load = [](const char* kind, Glib::RefPtr<Gio::DBus::Proxy>& proxy) -> std::string {
|
||||
try {
|
||||
if (!proxy) return "unknown";
|
||||
auto parameters = Glib::VariantContainerBase(
|
||||
g_variant_new("(ss)", "org.freedesktop.systemd1.Manager", "SystemState"));
|
||||
Glib::VariantContainerBase data = proxy->call_sync("Get", parameters);
|
||||
if (data && data.is_of_type(Glib::VariantType("(v)"))) {
|
||||
Glib::VariantBase variant;
|
||||
g_variant_get(data.gobj_copy(), "(v)", &variant);
|
||||
if (variant && variant.is_of_type(Glib::VARIANT_TYPE_STRING)) {
|
||||
return g_variant_get_string(variant.gobj_copy(), NULL);
|
||||
}
|
||||
}
|
||||
} catch (Glib::Error& e) {
|
||||
spdlog::error("Failed to get {} state: {}", kind, e.what().c_str());
|
||||
}
|
||||
return "unknown";
|
||||
};
|
||||
|
||||
system_state = load("systemwide", system_proxy);
|
||||
user_state = load("user", user_proxy);
|
||||
if (system_state == "running" && user_state == "running")
|
||||
overall_state = "ok";
|
||||
else
|
||||
overall_state = "degraded";
|
||||
}
|
||||
|
||||
void SystemdFailedUnits::RequestFailedUnits() {
|
||||
auto load = [](const char* kind, Glib::RefPtr<Gio::DBus::Proxy>& proxy) -> uint32_t {
|
||||
try {
|
||||
if (!proxy) return 0;
|
||||
auto parameters = Glib::VariantContainerBase(
|
||||
g_variant_new("(ss)", "org.freedesktop.systemd1.Manager", "NFailedUnits"));
|
||||
Glib::VariantContainerBase data = proxy->call_sync("Get", parameters);
|
||||
@@ -79,9 +107,7 @@ void SystemdFailedUnits::updateData() {
|
||||
Glib::VariantBase variant;
|
||||
g_variant_get(data.gobj_copy(), "(v)", &variant);
|
||||
if (variant && variant.is_of_type(Glib::VARIANT_TYPE_UINT32)) {
|
||||
uint32_t value = 0;
|
||||
g_variant_get(variant.gobj_copy(), "u", &value);
|
||||
return value;
|
||||
return g_variant_get_uint32(variant.gobj_copy());
|
||||
}
|
||||
}
|
||||
} catch (Glib::Error& e) {
|
||||
@@ -90,40 +116,46 @@ void SystemdFailedUnits::updateData() {
|
||||
return 0;
|
||||
};
|
||||
|
||||
if (system_proxy) {
|
||||
nr_failed_system = load("systemwide", system_proxy);
|
||||
}
|
||||
if (user_proxy) {
|
||||
nr_failed_user = load("user", user_proxy);
|
||||
}
|
||||
nr_failed_system = load("systemwide", system_proxy);
|
||||
nr_failed_user = load("user", user_proxy);
|
||||
nr_failed = nr_failed_system + nr_failed_user;
|
||||
}
|
||||
|
||||
void SystemdFailedUnits::updateData() {
|
||||
update_pending = false;
|
||||
|
||||
RequestSystemState();
|
||||
if (overall_state == "degraded") RequestFailedUnits();
|
||||
|
||||
dp.emit();
|
||||
}
|
||||
|
||||
auto SystemdFailedUnits::update() -> void {
|
||||
uint32_t nr_failed = nr_failed_system + nr_failed_user;
|
||||
if (last_status == overall_state) return;
|
||||
|
||||
// Hide if needed.
|
||||
if (nr_failed == 0 && hide_on_ok) {
|
||||
if (overall_state == "ok" && hide_on_ok) {
|
||||
event_box_.set_visible(false);
|
||||
return;
|
||||
}
|
||||
if (!event_box_.get_visible()) {
|
||||
event_box_.set_visible(true);
|
||||
}
|
||||
|
||||
event_box_.set_visible(true);
|
||||
|
||||
// Set state class.
|
||||
const std::string status = nr_failed == 0 ? "ok" : "degraded";
|
||||
if (!last_status.empty() && label_.get_style_context()->has_class(last_status)) {
|
||||
label_.get_style_context()->remove_class(last_status);
|
||||
}
|
||||
if (!label_.get_style_context()->has_class(status)) {
|
||||
label_.get_style_context()->add_class(status);
|
||||
if (!label_.get_style_context()->has_class(overall_state)) {
|
||||
label_.get_style_context()->add_class(overall_state);
|
||||
}
|
||||
last_status = status;
|
||||
|
||||
last_status = overall_state;
|
||||
|
||||
label_.set_markup(fmt::format(
|
||||
fmt::runtime(nr_failed == 0 ? format_ok : format_), fmt::arg("nr_failed", nr_failed),
|
||||
fmt::arg("nr_failed_system", nr_failed_system), fmt::arg("nr_failed_user", nr_failed_user)));
|
||||
fmt::arg("nr_failed_system", nr_failed_system), fmt::arg("nr_failed_user", nr_failed_user),
|
||||
fmt::arg("system_state", system_state), fmt::arg("user_state", user_state),
|
||||
fmt::arg("overall_state", overall_state)));
|
||||
ALabel::update();
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ waybar::modules::Temperature::Temperature(const std::string& id, const Json::Val
|
||||
file_path_ = fmt::format("/sys/class/thermal/thermal_zone{}/temp", zone);
|
||||
}
|
||||
|
||||
// check if file_path_ can be used to retrive the temperature
|
||||
// check if file_path_ can be used to retrieve the temperature
|
||||
std::ifstream temp(file_path_);
|
||||
if (!temp.is_open()) {
|
||||
throw std::runtime_error("Can't open " + file_path_);
|
||||
@@ -114,13 +114,17 @@ float waybar::modules::Temperature::getTemperature() {
|
||||
|
||||
auto zone = config_["thermal-zone"].isInt() ? config_["thermal-zone"].asInt() : 0;
|
||||
|
||||
if (sysctlbyname(fmt::format("hw.acpi.thermal.tz{}.temperature", zone).c_str(), &temp, &size,
|
||||
NULL, 0) != 0) {
|
||||
throw std::runtime_error(fmt::format(
|
||||
"sysctl hw.acpi.thermal.tz{}.temperature or dev.cpu.{}.temperature failed", zone, zone));
|
||||
// First, try with dev.cpu
|
||||
if ((sysctlbyname(fmt::format("dev.cpu.{}.temperature", zone).c_str(), &temp, &size, NULL, 0) ==
|
||||
0) ||
|
||||
(sysctlbyname(fmt::format("hw.acpi.thermal.tz{}.temperature", zone).c_str(), &temp, &size,
|
||||
NULL, 0) == 0)) {
|
||||
auto temperature_c = ((float)temp - 2732) / 10;
|
||||
return temperature_c;
|
||||
}
|
||||
auto temperature_c = ((float)temp - 2732) / 10;
|
||||
return temperature_c;
|
||||
|
||||
throw std::runtime_error(fmt::format(
|
||||
"sysctl hw.acpi.thermal.tz{}.temperature and dev.cpu.{}.temperature failed", zone, zone));
|
||||
|
||||
#else // Linux
|
||||
std::ifstream temp(file_path_);
|
||||
@@ -148,4 +152,4 @@ bool waybar::modules::Temperature::isWarning(uint16_t temperature_c) {
|
||||
bool waybar::modules::Temperature::isCritical(uint16_t temperature_c) {
|
||||
return config_["critical-threshold"].isInt() &&
|
||||
temperature_c >= config_["critical-threshold"].asInt();
|
||||
}
|
||||
}
|
||||
|
||||
445
src/modules/wayfire/backend.cpp
Normal file
445
src/modules/wayfire/backend.cpp
Normal file
@@ -0,0 +1,445 @@
|
||||
#include "modules/wayfire/backend.hpp"
|
||||
|
||||
#include <json/json.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <bit>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <exception>
|
||||
#include <ranges>
|
||||
#include <thread>
|
||||
|
||||
namespace waybar::modules::wayfire {
|
||||
|
||||
std::weak_ptr<IPC> IPC::instance;
|
||||
|
||||
// C++23: std::byteswap
|
||||
inline auto byteswap(uint32_t x) -> uint32_t {
|
||||
return (x & 0xff000000) >> 24 | (x & 0x00ff0000) >> 8 | (x & 0x0000ff00) << 8 |
|
||||
(x & 0x000000ff) << 24;
|
||||
}
|
||||
|
||||
auto pack_and_write(Sock& sock, std::string&& buf) -> void {
|
||||
uint32_t len = buf.size();
|
||||
if constexpr (std::endian::native != std::endian::little) len = byteswap(len);
|
||||
(void)write(sock.fd, &len, 4);
|
||||
(void)write(sock.fd, buf.data(), buf.size());
|
||||
}
|
||||
|
||||
auto read_exact(Sock& sock, size_t n) -> std::string {
|
||||
auto buf = std::string(n, 0);
|
||||
for (size_t i = 0; i < n;) i += read(sock.fd, &buf[i], n - i);
|
||||
return buf;
|
||||
}
|
||||
|
||||
// https://github.com/WayfireWM/pywayfire/blob/69b7c21/wayfire/ipc.py#L438
|
||||
inline auto is_mapped_toplevel_view(const Json::Value& view) -> bool {
|
||||
return view["mapped"].asBool() && view["role"] != "desktop-environment" &&
|
||||
view["pid"].asInt() != -1;
|
||||
}
|
||||
|
||||
auto State::Wset::count_ws(const Json::Value& pos) -> Workspace& {
|
||||
auto x = pos["x"].asInt();
|
||||
auto y = pos["y"].asInt();
|
||||
return wss.at(ws_w * y + x);
|
||||
}
|
||||
|
||||
auto State::Wset::locate_ws(const Json::Value& geo) -> Workspace& {
|
||||
return const_cast<Workspace&>(std::as_const(*this).locate_ws(geo));
|
||||
}
|
||||
|
||||
auto State::Wset::locate_ws(const Json::Value& geo) const -> const Workspace& {
|
||||
const auto& out = output.value().get();
|
||||
auto [qx, rx] = std::div(geo["x"].asInt(), out.w);
|
||||
auto [qy, ry] = std::div(geo["y"].asInt(), out.h);
|
||||
auto x = std::max(0, (int)ws_x + qx - int{rx < 0});
|
||||
auto y = std::max(0, (int)ws_y + qy - int{ry < 0});
|
||||
return wss.at(ws_w * y + x);
|
||||
}
|
||||
|
||||
auto State::update_view(const Json::Value& view) -> void {
|
||||
auto id = view["id"].asUInt();
|
||||
|
||||
// erase old view information
|
||||
if (views.contains(id)) {
|
||||
auto& old_view = views.at(id);
|
||||
auto& ws = wsets.at(old_view["wset-index"].asUInt()).locate_ws(old_view["geometry"]);
|
||||
ws.num_views--;
|
||||
if (old_view["sticky"].asBool()) ws.num_sticky_views--;
|
||||
views.erase(id);
|
||||
}
|
||||
|
||||
// insert or assign new view information
|
||||
if (is_mapped_toplevel_view(view)) {
|
||||
try {
|
||||
// view["wset-index"] could be messed up
|
||||
auto& ws = wsets.at(view["wset-index"].asUInt()).locate_ws(view["geometry"]);
|
||||
ws.num_views++;
|
||||
if (view["sticky"].asBool()) ws.num_sticky_views++;
|
||||
views.emplace(id, view);
|
||||
} catch (const std::exception&) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto IPC::get_instance() -> std::shared_ptr<IPC> {
|
||||
auto p = instance.lock();
|
||||
if (!p) instance = p = std::shared_ptr<IPC>(new IPC);
|
||||
return p;
|
||||
}
|
||||
|
||||
auto IPC::connect() -> Sock {
|
||||
auto* path = std::getenv("WAYFIRE_SOCKET");
|
||||
if (path == nullptr) {
|
||||
throw std::runtime_error{"Wayfire IPC: ipc not available"};
|
||||
}
|
||||
|
||||
auto sock = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (sock == -1) {
|
||||
throw std::runtime_error{"Wayfire IPC: socket() failed"};
|
||||
}
|
||||
|
||||
auto addr = sockaddr_un{.sun_family = AF_UNIX};
|
||||
std::strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
|
||||
addr.sun_path[sizeof(addr.sun_path) - 1] = 0;
|
||||
|
||||
if (::connect(sock, (const sockaddr*)&addr, sizeof(addr)) == -1) {
|
||||
close(sock);
|
||||
throw std::runtime_error{"Wayfire IPC: connect() failed"};
|
||||
}
|
||||
|
||||
return {sock};
|
||||
}
|
||||
|
||||
auto IPC::receive(Sock& sock) -> Json::Value {
|
||||
auto len = *reinterpret_cast<uint32_t*>(read_exact(sock, 4).data());
|
||||
if constexpr (std::endian::native != std::endian::little) len = byteswap(len);
|
||||
auto buf = read_exact(sock, len);
|
||||
|
||||
Json::Value json;
|
||||
std::string err;
|
||||
auto* reader = reader_builder.newCharReader();
|
||||
if (!reader->parse(&*buf.begin(), &*buf.end(), &json, &err)) {
|
||||
throw std::runtime_error{"Wayfire IPC: parse json failed: " + err};
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
auto IPC::send(const std::string& method, Json::Value&& data) -> Json::Value {
|
||||
spdlog::debug("Wayfire IPC: send method \"{}\"", method);
|
||||
auto sock = connect();
|
||||
|
||||
Json::Value json;
|
||||
json["method"] = method;
|
||||
json["data"] = std::move(data);
|
||||
|
||||
pack_and_write(sock, Json::writeString(writer_builder, json));
|
||||
auto res = receive(sock);
|
||||
root_event_handler(method, res);
|
||||
return res;
|
||||
}
|
||||
|
||||
auto IPC::start() -> void {
|
||||
spdlog::info("Wayfire IPC: starting");
|
||||
|
||||
// init state
|
||||
send("window-rules/list-outputs", {});
|
||||
send("window-rules/list-wsets", {});
|
||||
send("window-rules/list-views", {});
|
||||
send("window-rules/get-focused-view", {});
|
||||
send("window-rules/get-focused-output", {});
|
||||
|
||||
std::thread([&] {
|
||||
auto sock = connect();
|
||||
|
||||
{
|
||||
Json::Value json;
|
||||
json["method"] = "window-rules/events/watch";
|
||||
|
||||
pack_and_write(sock, Json::writeString(writer_builder, json));
|
||||
if (receive(sock)["result"] != "ok") {
|
||||
spdlog::error(
|
||||
"Wayfire IPC: method \"window-rules/events/watch\""
|
||||
" have failed");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
while (auto json = receive(sock)) {
|
||||
auto ev = json["event"].asString();
|
||||
spdlog::debug("Wayfire IPC: received event \"{}\"", ev);
|
||||
root_event_handler(ev, json);
|
||||
}
|
||||
}).detach();
|
||||
}
|
||||
|
||||
auto IPC::register_handler(const std::string& event, const EventHandler& handler) -> void {
|
||||
auto _ = std::lock_guard{handlers_mutex};
|
||||
handlers.emplace_back(event, handler);
|
||||
}
|
||||
|
||||
auto IPC::unregister_handler(EventHandler& handler) -> void {
|
||||
auto _ = std::lock_guard{handlers_mutex};
|
||||
handlers.remove_if([&](auto& e) { return &e.second.get() == &handler; });
|
||||
}
|
||||
|
||||
auto IPC::root_event_handler(const std::string& event, const Json::Value& data) -> void {
|
||||
bool new_output_detected;
|
||||
{
|
||||
auto _ = lock_state();
|
||||
update_state_handler(event, data);
|
||||
new_output_detected = state.new_output_detected;
|
||||
state.new_output_detected = false;
|
||||
}
|
||||
if (new_output_detected) {
|
||||
send("window-rules/list-outputs", {});
|
||||
send("window-rules/list-wsets", {});
|
||||
}
|
||||
{
|
||||
auto _ = std::lock_guard{handlers_mutex};
|
||||
for (const auto& [_event, handler] : handlers)
|
||||
if (_event == event) handler(event);
|
||||
}
|
||||
}
|
||||
|
||||
auto IPC::update_state_handler(const std::string& event, const Json::Value& data) -> void {
|
||||
// IPC events
|
||||
// https://github.com/WayfireWM/wayfire/blob/053b222/plugins/ipc-rules/ipc-events.hpp#L108-L125
|
||||
/*
|
||||
[x] view-mapped
|
||||
[x] view-unmapped
|
||||
[-] view-set-output // for detect new output
|
||||
[ ] view-geometry-changed // -> view-workspace-changed
|
||||
[x] view-wset-changed
|
||||
[x] view-focused
|
||||
[x] view-title-changed
|
||||
[x] view-app-id-changed
|
||||
[x] plugin-activation-state-changed
|
||||
[x] output-gain-focus
|
||||
|
||||
[ ] view-tiled
|
||||
[ ] view-minimized
|
||||
[ ] view-fullscreened
|
||||
[x] view-sticky
|
||||
[x] view-workspace-changed
|
||||
[x] output-wset-changed
|
||||
[x] wset-workspace-changed
|
||||
*/
|
||||
|
||||
if (event == "view-mapped") {
|
||||
// data: { event, view }
|
||||
state.update_view(data["view"]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (event == "view-unmapped") {
|
||||
// data: { event, view }
|
||||
try {
|
||||
// data["view"]["wset-index"] could be messed up
|
||||
state.update_view(data["view"]);
|
||||
state.maybe_empty_focus_wset_idx = data["view"]["wset-index"].asUInt();
|
||||
} catch (const std::exception&) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (event == "view-set-output") {
|
||||
// data: { event, output?, view }
|
||||
// new output event
|
||||
if (!state.outputs.contains(data["view"]["output-name"].asString())) {
|
||||
state.new_output_detected = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (event == "view-wset-changed") {
|
||||
// data: { event, old-wset: wset, new-wset: wset, view }
|
||||
state.maybe_empty_focus_wset_idx = data["old-wset"]["index"].asUInt();
|
||||
state.update_view(data["view"]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (event == "view-focused") {
|
||||
// data: { event, view? }
|
||||
if (const auto& view = data["view"]) {
|
||||
try {
|
||||
// view["wset-index"] could be messed up
|
||||
auto& wset = state.wsets.at(view["wset-index"].asUInt());
|
||||
wset.focused_view_id = view["id"].asUInt();
|
||||
} catch (const std::exception&) {
|
||||
}
|
||||
} else {
|
||||
// focused to null
|
||||
if (state.wsets.contains(state.maybe_empty_focus_wset_idx))
|
||||
state.wsets.at(state.maybe_empty_focus_wset_idx).focused_view_id = {};
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (event == "view-title-changed" || event == "view-app-id-changed" || event == "view-sticky") {
|
||||
// data: { event, view }
|
||||
state.update_view(data["view"]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (event == "plugin-activation-state-changed") {
|
||||
// data: { event, plugin: name, state: bool, output: id, output-data: output }
|
||||
auto plugin = data["plugin"].asString();
|
||||
auto plugin_state = data["state"].asBool();
|
||||
|
||||
if (plugin == "vswitch") {
|
||||
state.vswitching = plugin_state;
|
||||
if (plugin_state) {
|
||||
state.maybe_empty_focus_wset_idx = data["output-data"]["wset-index"].asUInt();
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (event == "output-gain-focus") {
|
||||
// data: { event, output }
|
||||
state.focused_output_name = data["output"]["name"].asString();
|
||||
return;
|
||||
}
|
||||
|
||||
if (event == "view-workspace-changed") {
|
||||
// data: { event, from: point, to: point, view }
|
||||
if (state.vswitching) {
|
||||
if (state.vswitch_sticky_view_id == 0) {
|
||||
auto& wset = state.wsets.at(data["view"]["wset-index"].asUInt());
|
||||
auto& old_ws = wset.locate_ws(state.views.at(data["view"]["id"].asUInt())["geometry"]);
|
||||
auto& new_ws = wset.count_ws(data["to"]);
|
||||
old_ws.num_views--;
|
||||
new_ws.num_views++;
|
||||
if (data["view"]["sticky"].asBool()) {
|
||||
old_ws.num_sticky_views--;
|
||||
new_ws.num_sticky_views++;
|
||||
}
|
||||
state.update_view(data["view"]);
|
||||
state.vswitch_sticky_view_id = data["view"]["id"].asUInt();
|
||||
} else {
|
||||
state.vswitch_sticky_view_id = {};
|
||||
}
|
||||
return;
|
||||
}
|
||||
state.update_view(data["view"]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (event == "output-wset-changed") {
|
||||
// data: { event, new-wset: wset.name, output: id, new-wset-data: wset, output-data: output }
|
||||
auto& output = state.outputs.at(data["output-data"]["name"].asString());
|
||||
auto wset_idx = data["new-wset-data"]["index"].asUInt();
|
||||
state.wsets.at(wset_idx).output = output;
|
||||
output.wset_idx = wset_idx;
|
||||
return;
|
||||
}
|
||||
|
||||
if (event == "wset-workspace-changed") {
|
||||
// data: { event, previous-workspace: point, new-workspace: point,
|
||||
// output: id, wset: wset.name, output-data: output, wset-data: wset }
|
||||
auto wset_idx = data["wset-data"]["index"].asUInt();
|
||||
auto& wset = state.wsets.at(wset_idx);
|
||||
wset.ws_x = data["new-workspace"]["x"].asUInt();
|
||||
wset.ws_y = data["new-workspace"]["y"].asUInt();
|
||||
|
||||
// correct existing views geometry
|
||||
auto& out = wset.output.value().get();
|
||||
auto dx = (int)out.w * ((int)wset.ws_x - data["previous-workspace"]["x"].asInt());
|
||||
auto dy = (int)out.h * ((int)wset.ws_y - data["previous-workspace"]["y"].asInt());
|
||||
for (auto& [_, view] : state.views) {
|
||||
if (view["wset-index"].asUInt() == wset_idx &&
|
||||
view["id"].asUInt() != state.vswitch_sticky_view_id) {
|
||||
view["geometry"]["x"] = view["geometry"]["x"].asInt() - dx;
|
||||
view["geometry"]["y"] = view["geometry"]["y"].asInt() - dy;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// IPC responses
|
||||
// https://github.com/WayfireWM/wayfire/blob/053b222/plugins/ipc-rules/ipc-rules.cpp#L27-L37
|
||||
|
||||
if (event == "window-rules/list-views") {
|
||||
// data: [ view ]
|
||||
state.views.clear();
|
||||
for (auto& [_, wset] : state.wsets) std::ranges::fill(wset.wss, State::Workspace{});
|
||||
for (const auto& view : data | std::views::filter(is_mapped_toplevel_view)) {
|
||||
state.update_view(view);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (event == "window-rules/list-outputs") {
|
||||
// data: [ output ]
|
||||
state.outputs.clear();
|
||||
for (const auto& output_data : data) {
|
||||
state.outputs.emplace(output_data["name"].asString(),
|
||||
State::Output{
|
||||
.id = output_data["id"].asUInt(),
|
||||
.w = output_data["geometry"]["width"].asUInt(),
|
||||
.h = output_data["geometry"]["height"].asUInt(),
|
||||
.wset_idx = output_data["wset-index"].asUInt(),
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (event == "window-rules/list-wsets") {
|
||||
// data: [ wset ]
|
||||
std::unordered_map<size_t, State::Wset> wsets;
|
||||
for (const auto& wset_data : data) {
|
||||
auto wset_idx = wset_data["index"].asUInt();
|
||||
|
||||
auto output_name = wset_data["output-name"].asString();
|
||||
auto output = state.outputs.contains(output_name)
|
||||
? std::optional{std::ref(state.outputs.at(output_name))}
|
||||
: std::nullopt;
|
||||
|
||||
const auto& ws_data = wset_data["workspace"];
|
||||
auto ws_w = ws_data["grid_width"].asUInt();
|
||||
auto ws_h = ws_data["grid_height"].asUInt();
|
||||
|
||||
wsets.emplace(wset_idx, State::Wset{
|
||||
.output = output,
|
||||
.wss = std::vector<State::Workspace>(ws_w * ws_h),
|
||||
.ws_w = ws_w,
|
||||
.ws_h = ws_h,
|
||||
.ws_x = ws_data["x"].asUInt(),
|
||||
.ws_y = ws_data["y"].asUInt(),
|
||||
});
|
||||
|
||||
if (state.wsets.contains(wset_idx)) {
|
||||
auto& old_wset = state.wsets.at(wset_idx);
|
||||
auto& new_wset = wsets.at(wset_idx);
|
||||
new_wset.wss = std::move(old_wset.wss);
|
||||
new_wset.focused_view_id = old_wset.focused_view_id;
|
||||
}
|
||||
}
|
||||
state.wsets = std::move(wsets);
|
||||
return;
|
||||
}
|
||||
|
||||
if (event == "window-rules/get-focused-view") {
|
||||
// data: { ok, info: view? }
|
||||
if (const auto& view = data["info"]) {
|
||||
auto& wset = state.wsets.at(view["wset-index"].asUInt());
|
||||
wset.focused_view_id = view["id"].asUInt();
|
||||
state.update_view(view);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (event == "window-rules/get-focused-output") {
|
||||
// data: { ok, info: output }
|
||||
state.focused_output_name = data["info"]["name"].asString();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace waybar::modules::wayfire
|
||||
77
src/modules/wayfire/window.cpp
Normal file
77
src/modules/wayfire/window.cpp
Normal file
@@ -0,0 +1,77 @@
|
||||
#include "modules/wayfire/window.hpp"
|
||||
|
||||
#include <gtkmm/button.h>
|
||||
#include <gtkmm/label.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include "util/rewrite_string.hpp"
|
||||
#include "util/sanitize_str.hpp"
|
||||
|
||||
namespace waybar::modules::wayfire {
|
||||
|
||||
Window::Window(const std::string& id, const Bar& bar, const Json::Value& config)
|
||||
: AAppIconLabel(config, "window", id, "{title}", 0, true),
|
||||
ipc{IPC::get_instance()},
|
||||
handler{[this](const auto&) { dp.emit(); }},
|
||||
bar_{bar} {
|
||||
ipc->register_handler("view-unmapped", handler);
|
||||
ipc->register_handler("view-focused", handler);
|
||||
ipc->register_handler("view-title-changed", handler);
|
||||
ipc->register_handler("view-app-id-changed", handler);
|
||||
|
||||
ipc->register_handler("window-rules/get-focused-view", handler);
|
||||
|
||||
dp.emit();
|
||||
}
|
||||
|
||||
Window::~Window() { ipc->unregister_handler(handler); }
|
||||
|
||||
auto Window::update() -> void {
|
||||
update_icon_label();
|
||||
AAppIconLabel::update();
|
||||
}
|
||||
|
||||
auto Window::update_icon_label() -> void {
|
||||
auto _ = ipc->lock_state();
|
||||
|
||||
const auto& output = ipc->get_outputs().at(bar_.output->name);
|
||||
const auto& wset = ipc->get_wsets().at(output.wset_idx);
|
||||
const auto& views = ipc->get_views();
|
||||
auto ctx = bar_.window.get_style_context();
|
||||
|
||||
if (views.contains(wset.focused_view_id)) {
|
||||
const auto& view = views.at(wset.focused_view_id);
|
||||
auto title = view["title"].asString();
|
||||
auto app_id = view["app-id"].asString();
|
||||
|
||||
// update label
|
||||
label_.set_markup(waybar::util::rewriteString(
|
||||
fmt::format(fmt::runtime(format_), fmt::arg("title", waybar::util::sanitize_string(title)),
|
||||
fmt::arg("app_id", waybar::util::sanitize_string(app_id))),
|
||||
config_["rewrite"]));
|
||||
|
||||
// update window#waybar.solo
|
||||
if (wset.locate_ws(view["geometry"]).num_views > 1)
|
||||
ctx->remove_class("solo");
|
||||
else
|
||||
ctx->add_class("solo");
|
||||
|
||||
// update window#waybar.<app_id>
|
||||
ctx->remove_class(old_app_id_);
|
||||
ctx->add_class(old_app_id_ = app_id);
|
||||
|
||||
// update window#waybar.empty
|
||||
ctx->remove_class("empty");
|
||||
|
||||
//
|
||||
updateAppIconName(app_id, "");
|
||||
label_.show();
|
||||
} else {
|
||||
ctx->add_class("empty");
|
||||
|
||||
updateAppIconName("", "");
|
||||
label_.hide();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace waybar::modules::wayfire
|
||||
183
src/modules/wayfire/workspaces.cpp
Normal file
183
src/modules/wayfire/workspaces.cpp
Normal file
@@ -0,0 +1,183 @@
|
||||
#include "modules/wayfire/workspaces.hpp"
|
||||
|
||||
#include <gtkmm/button.h>
|
||||
#include <gtkmm/label.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "modules/wayfire/backend.hpp"
|
||||
|
||||
namespace waybar::modules::wayfire {
|
||||
|
||||
Workspaces::Workspaces(const std::string& id, const Bar& bar, const Json::Value& config)
|
||||
: AModule{config, "workspaces", id, false, !config["disable-scroll"].asBool()},
|
||||
ipc{IPC::get_instance()},
|
||||
handler{[this](const auto&) { dp.emit(); }},
|
||||
bar_{bar} {
|
||||
// init box_
|
||||
box_.set_name("workspaces");
|
||||
if (!id.empty()) box_.get_style_context()->add_class(id);
|
||||
box_.get_style_context()->add_class(MODULE_CLASS);
|
||||
event_box_.add(box_);
|
||||
|
||||
// scroll events
|
||||
if (!config_["disable-scroll"].asBool()) {
|
||||
auto& target = config_["enable-bar-scroll"].asBool() ? const_cast<Bar&>(bar_).window
|
||||
: dynamic_cast<Gtk::Widget&>(box_);
|
||||
target.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);
|
||||
target.signal_scroll_event().connect(sigc::mem_fun(*this, &Workspaces::handleScroll));
|
||||
}
|
||||
|
||||
// listen events
|
||||
ipc->register_handler("view-mapped", handler);
|
||||
ipc->register_handler("view-unmapped", handler);
|
||||
ipc->register_handler("view-wset-changed", handler);
|
||||
ipc->register_handler("output-gain-focus", handler);
|
||||
ipc->register_handler("view-sticky", handler);
|
||||
ipc->register_handler("view-workspace-changed", handler);
|
||||
ipc->register_handler("output-wset-changed", handler);
|
||||
ipc->register_handler("wset-workspace-changed", handler);
|
||||
|
||||
ipc->register_handler("window-rules/list-views", handler);
|
||||
ipc->register_handler("window-rules/list-outputs", handler);
|
||||
ipc->register_handler("window-rules/list-wsets", handler);
|
||||
ipc->register_handler("window-rules/get-focused-output", handler);
|
||||
|
||||
// initial render
|
||||
dp.emit();
|
||||
}
|
||||
|
||||
Workspaces::~Workspaces() { ipc->unregister_handler(handler); }
|
||||
|
||||
auto Workspaces::handleScroll(GdkEventScroll* e) -> bool {
|
||||
// Ignore emulated scroll events on window
|
||||
if (gdk_event_get_pointer_emulated((GdkEvent*)e) != 0) return false;
|
||||
|
||||
auto dir = AModule::getScrollDir(e);
|
||||
if (dir == SCROLL_DIR::NONE) return true;
|
||||
|
||||
int delta;
|
||||
if (dir == SCROLL_DIR::DOWN || dir == SCROLL_DIR::RIGHT)
|
||||
delta = 1;
|
||||
else if (dir == SCROLL_DIR::UP || dir == SCROLL_DIR::LEFT)
|
||||
delta = -1;
|
||||
else
|
||||
return true;
|
||||
|
||||
// cycle workspace
|
||||
Json::Value data;
|
||||
{
|
||||
auto _ = ipc->lock_state();
|
||||
const auto& output = ipc->get_outputs().at(bar_.output->name);
|
||||
const auto& wset = ipc->get_wsets().at(output.wset_idx);
|
||||
auto n = wset.ws_w * wset.ws_h;
|
||||
auto i = (wset.ws_idx() + delta + n) % n;
|
||||
data["x"] = i % wset.ws_w;
|
||||
data["y"] = i / wset.ws_h;
|
||||
data["output-id"] = output.id;
|
||||
}
|
||||
ipc->send("vswitch/set-workspace", std::move(data));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto Workspaces::update() -> void {
|
||||
update_box();
|
||||
AModule::update();
|
||||
}
|
||||
|
||||
auto Workspaces::update_box() -> void {
|
||||
auto _ = ipc->lock_state();
|
||||
|
||||
const auto& output_name = bar_.output->name;
|
||||
const auto& output = ipc->get_outputs().at(output_name);
|
||||
const auto& wset = ipc->get_wsets().at(output.wset_idx);
|
||||
|
||||
auto output_focused = ipc->get_focused_output_name() == output_name;
|
||||
auto ws_w = wset.ws_w;
|
||||
auto ws_h = wset.ws_h;
|
||||
auto num_wss = ws_w * ws_h;
|
||||
|
||||
// add buttons for new workspaces
|
||||
for (auto i = buttons_.size(); i < num_wss; i++) {
|
||||
auto& btn = buttons_.emplace_back("");
|
||||
box_.pack_start(btn, false, false, 0);
|
||||
btn.set_relief(Gtk::RELIEF_NONE);
|
||||
if (!config_["disable-click"].asBool()) {
|
||||
btn.signal_pressed().connect([=, this] {
|
||||
Json::Value data;
|
||||
data["x"] = i % ws_w;
|
||||
data["y"] = i / ws_h;
|
||||
data["output-id"] = output.id;
|
||||
ipc->send("vswitch/set-workspace", std::move(data));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// remove buttons for removed workspaces
|
||||
buttons_.resize(num_wss);
|
||||
|
||||
// update buttons
|
||||
for (size_t i = 0; i < num_wss; i++) {
|
||||
const auto& ws = wset.wss[i];
|
||||
auto& btn = buttons_[i];
|
||||
auto ctx = btn.get_style_context();
|
||||
auto ws_focused = i == wset.ws_idx();
|
||||
auto ws_empty = ws.num_views == 0;
|
||||
|
||||
// update #workspaces button.focused
|
||||
if (ws_focused)
|
||||
ctx->add_class("focused");
|
||||
else
|
||||
ctx->remove_class("focused");
|
||||
|
||||
// update #workspaces button.empty
|
||||
if (ws_empty)
|
||||
ctx->add_class("empty");
|
||||
else
|
||||
ctx->remove_class("empty");
|
||||
|
||||
// update #workspaces button.current_output
|
||||
if (output_focused)
|
||||
ctx->add_class("current_output");
|
||||
else
|
||||
ctx->remove_class("current_output");
|
||||
|
||||
// update label
|
||||
auto label = std::to_string(i + 1);
|
||||
if (config_["format"].isString()) {
|
||||
auto format = config_["format"].asString();
|
||||
auto ws_idx = std::to_string(i + 1);
|
||||
|
||||
const auto& icons = config_["format-icons"];
|
||||
std::string icon;
|
||||
if (!icons)
|
||||
icon = ws_idx;
|
||||
else if (ws_focused && icons["focused"])
|
||||
icon = icons["focused"].asString();
|
||||
else if (icons[ws_idx])
|
||||
icon = icons[ws_idx].asString();
|
||||
else if (icons["default"])
|
||||
icon = icons["default"].asString();
|
||||
else
|
||||
icon = ws_idx;
|
||||
|
||||
label = fmt::format(fmt::runtime(format), fmt::arg("icon", icon), fmt::arg("index", ws_idx),
|
||||
fmt::arg("output", output_name));
|
||||
}
|
||||
if (!config_["disable-markup"].asBool())
|
||||
static_cast<Gtk::Label*>(btn.get_children()[0])->set_markup(label);
|
||||
else
|
||||
btn.set_label(label);
|
||||
|
||||
//
|
||||
if (config_["current-only"].asBool() && i != wset.ws_idx())
|
||||
btn.hide();
|
||||
else
|
||||
btn.show();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace waybar::modules::wayfire
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
bool isValidNodeId(uint32_t id) { return id > 0 && id < G_MAXUINT32; }
|
||||
|
||||
std::list<waybar::modules::Wireplumber*> waybar::modules::Wireplumber::modules;
|
||||
|
||||
waybar::modules::Wireplumber::Wireplumber(const std::string& id, const Json::Value& config)
|
||||
: ALabel(config, "wireplumber", id, "{volume}%"),
|
||||
wp_core_(nullptr),
|
||||
@@ -16,22 +18,28 @@ waybar::modules::Wireplumber::Wireplumber(const std::string& id, const Json::Val
|
||||
muted_(false),
|
||||
volume_(0.0),
|
||||
min_step_(0.0),
|
||||
node_id_(0) {
|
||||
node_id_(0),
|
||||
type_(nullptr) {
|
||||
waybar::modules::Wireplumber::modules.push_back(this);
|
||||
|
||||
wp_init(WP_INIT_PIPEWIRE);
|
||||
wp_core_ = wp_core_new(nullptr, nullptr, nullptr);
|
||||
apis_ = g_ptr_array_new_with_free_func(g_object_unref);
|
||||
om_ = wp_object_manager_new();
|
||||
|
||||
prepare();
|
||||
type_ = g_strdup(config_["node-type"].isString() ? config_["node-type"].asString().c_str()
|
||||
: "Audio/Sink");
|
||||
|
||||
spdlog::debug("[{}]: connecting to pipewire...", name_);
|
||||
prepare(this);
|
||||
|
||||
spdlog::debug("[{}]: connecting to pipewire: '{}'...", name_, type_);
|
||||
|
||||
if (wp_core_connect(wp_core_) == 0) {
|
||||
spdlog::error("[{}]: Could not connect to PipeWire", name_);
|
||||
spdlog::error("[{}]: Could not connect to PipeWire: '{}'", name_, type_);
|
||||
throw std::runtime_error("Could not connect to PipeWire\n");
|
||||
}
|
||||
|
||||
spdlog::debug("[{}]: connected!", name_);
|
||||
spdlog::debug("[{}]: {} connected!", name_, type_);
|
||||
|
||||
g_signal_connect_swapped(om_, "installed", (GCallback)onObjectManagerInstalled, this);
|
||||
|
||||
@@ -39,6 +47,7 @@ waybar::modules::Wireplumber::Wireplumber(const std::string& id, const Json::Val
|
||||
}
|
||||
|
||||
waybar::modules::Wireplumber::~Wireplumber() {
|
||||
waybar::modules::Wireplumber::modules.remove(this);
|
||||
wp_core_disconnect(wp_core_);
|
||||
g_clear_pointer(&apis_, g_ptr_array_unref);
|
||||
g_clear_object(&om_);
|
||||
@@ -46,13 +55,15 @@ waybar::modules::Wireplumber::~Wireplumber() {
|
||||
g_clear_object(&mixer_api_);
|
||||
g_clear_object(&def_nodes_api_);
|
||||
g_free(default_node_name_);
|
||||
g_free(type_);
|
||||
}
|
||||
|
||||
void waybar::modules::Wireplumber::updateNodeName(waybar::modules::Wireplumber* self, uint32_t id) {
|
||||
spdlog::debug("[{}]: updating node name with node.id {}", self->name_, id);
|
||||
spdlog::debug("[{}]: updating '{}' node name with node.id {}", self->name_, self->type_, id);
|
||||
|
||||
if (!isValidNodeId(id)) {
|
||||
spdlog::warn("[{}]: '{}' is not a valid node ID. Ignoring node name update.", self->name_, id);
|
||||
spdlog::warn("[{}]: '{}' is not a valid node ID. Ignoring '{}' node name update.", self->name_,
|
||||
id, self->type_);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -80,7 +91,7 @@ void waybar::modules::Wireplumber::updateNodeName(waybar::modules::Wireplumber*
|
||||
self->node_name_ = nick != nullptr ? nick
|
||||
: description != nullptr ? description
|
||||
: "Unknown node name";
|
||||
spdlog::debug("[{}]: Updating node name to: {}", self->name_, self->node_name_);
|
||||
spdlog::debug("[{}]: Updating '{}' node name to: {}", self->name_, self->type_, self->node_name_);
|
||||
}
|
||||
|
||||
void waybar::modules::Wireplumber::updateVolume(waybar::modules::Wireplumber* self, uint32_t id) {
|
||||
@@ -88,7 +99,8 @@ void waybar::modules::Wireplumber::updateVolume(waybar::modules::Wireplumber* se
|
||||
GVariant* variant = nullptr;
|
||||
|
||||
if (!isValidNodeId(id)) {
|
||||
spdlog::error("[{}]: '{}' is not a valid node ID. Ignoring volume update.", self->name_, id);
|
||||
spdlog::error("[{}]: '{}' is not a valid '{}' node ID. Ignoring volume update.", self->name_,
|
||||
id, self->type_);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -109,13 +121,22 @@ void waybar::modules::Wireplumber::updateVolume(waybar::modules::Wireplumber* se
|
||||
}
|
||||
|
||||
void waybar::modules::Wireplumber::onMixerChanged(waybar::modules::Wireplumber* self, uint32_t id) {
|
||||
spdlog::debug("[{}]: (onMixerChanged) - id: {}", self->name_, id);
|
||||
|
||||
g_autoptr(WpNode) node = static_cast<WpNode*>(wp_object_manager_lookup(
|
||||
self->om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_G_PROPERTY, "bound-id", "=u", id, nullptr));
|
||||
|
||||
if (node == nullptr) {
|
||||
spdlog::warn("[{}]: (onMixerChanged) - Object with id {} not found", self->name_, id);
|
||||
// log a warning only if no other widget is targeting the id.
|
||||
// this reduces log spam when multiple instances of the module are used on different node types.
|
||||
if (id != self->node_id_) {
|
||||
for (auto const& module : waybar::modules::Wireplumber::modules) {
|
||||
if (module->node_id_ == id) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
spdlog::warn("[{}]: (onMixerChanged: {}) - Object with id {} not found", self->name_,
|
||||
self->type_, id);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -123,26 +144,27 @@ void waybar::modules::Wireplumber::onMixerChanged(waybar::modules::Wireplumber*
|
||||
|
||||
if (self->node_id_ != id) {
|
||||
spdlog::debug(
|
||||
"[{}]: (onMixerChanged) - ignoring mixer update for node: id: {}, name: {} as it is not "
|
||||
"the default node: {} with id: {}",
|
||||
self->name_, id, name, self->default_node_name_, self->node_id_);
|
||||
"[{}]: (onMixerChanged: {}) - ignoring mixer update for node: id: {}, name: {} as it is "
|
||||
"not the default node: {} with id: {}",
|
||||
self->name_, self->type_, id, name, self->default_node_name_, self->node_id_);
|
||||
return;
|
||||
}
|
||||
|
||||
spdlog::debug("[{}]: (onMixerChanged) - Need to update volume for node with id {} and name {}",
|
||||
self->name_, id, name);
|
||||
spdlog::debug(
|
||||
"[{}]: (onMixerChanged: {}) - Need to update volume for node with id {} and name {}",
|
||||
self->name_, self->type_, id, name);
|
||||
updateVolume(self, id);
|
||||
}
|
||||
|
||||
void waybar::modules::Wireplumber::onDefaultNodesApiChanged(waybar::modules::Wireplumber* self) {
|
||||
spdlog::debug("[{}]: (onDefaultNodesApiChanged)", self->name_);
|
||||
spdlog::debug("[{}]: (onDefaultNodesApiChanged: {})", self->name_, self->type_);
|
||||
|
||||
uint32_t defaultNodeId;
|
||||
g_signal_emit_by_name(self->def_nodes_api_, "get-default-node", "Audio/Sink", &defaultNodeId);
|
||||
g_signal_emit_by_name(self->def_nodes_api_, "get-default-node", self->type_, &defaultNodeId);
|
||||
|
||||
if (!isValidNodeId(defaultNodeId)) {
|
||||
spdlog::warn("[{}]: '{}' is not a valid node ID. Ignoring node change.", self->name_,
|
||||
defaultNodeId);
|
||||
spdlog::warn("[{}]: '{}' is not a valid node ID. Ignoring '{}' node change.", self->name_,
|
||||
defaultNodeId, self->type_);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -151,8 +173,8 @@ void waybar::modules::Wireplumber::onDefaultNodesApiChanged(waybar::modules::Wir
|
||||
"=u", defaultNodeId, nullptr));
|
||||
|
||||
if (node == nullptr) {
|
||||
spdlog::warn("[{}]: (onDefaultNodesApiChanged) - Object with id {} not found", self->name_,
|
||||
defaultNodeId);
|
||||
spdlog::warn("[{}]: (onDefaultNodesApiChanged: {}) - Object with id {} not found", self->name_,
|
||||
self->type_, defaultNodeId);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -160,21 +182,22 @@ void waybar::modules::Wireplumber::onDefaultNodesApiChanged(waybar::modules::Wir
|
||||
wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(node), "node.name");
|
||||
|
||||
spdlog::debug(
|
||||
"[{}]: (onDefaultNodesApiChanged) - got the following default node: Node(name: {}, id: {})",
|
||||
self->name_, defaultNodeName, defaultNodeId);
|
||||
"[{}]: (onDefaultNodesApiChanged: {}) - got the following default node: Node(name: {}, id: "
|
||||
"{})",
|
||||
self->name_, self->type_, defaultNodeName, defaultNodeId);
|
||||
|
||||
if (g_strcmp0(self->default_node_name_, defaultNodeName) == 0 &&
|
||||
self->node_id_ == defaultNodeId) {
|
||||
spdlog::debug(
|
||||
"[{}]: (onDefaultNodesApiChanged) - Default node has not changed. Node(name: {}, id: {}). "
|
||||
"Ignoring.",
|
||||
self->name_, self->default_node_name_, defaultNodeId);
|
||||
"[{}]: (onDefaultNodesApiChanged: {}) - Default node has not changed. Node(name: {}, id: "
|
||||
"{}). Ignoring.",
|
||||
self->name_, self->type_, self->default_node_name_, defaultNodeId);
|
||||
return;
|
||||
}
|
||||
|
||||
spdlog::debug(
|
||||
"[{}]: (onDefaultNodesApiChanged) - Default node changed to -> Node(name: {}, id: {})",
|
||||
self->name_, defaultNodeName, defaultNodeId);
|
||||
"[{}]: (onDefaultNodesApiChanged: {}) - Default node changed to -> Node(name: {}, id: {})",
|
||||
self->name_, self->type_, defaultNodeName, defaultNodeId);
|
||||
|
||||
g_free(self->default_node_name_);
|
||||
self->default_node_name_ = g_strdup(defaultNodeName);
|
||||
@@ -200,13 +223,14 @@ void waybar::modules::Wireplumber::onObjectManagerInstalled(waybar::modules::Wir
|
||||
throw std::runtime_error("Mixer api is not loaded\n");
|
||||
}
|
||||
|
||||
g_signal_emit_by_name(self->def_nodes_api_, "get-default-configured-node-name", "Audio/Sink",
|
||||
g_signal_emit_by_name(self->def_nodes_api_, "get-default-configured-node-name", self->type_,
|
||||
&self->default_node_name_);
|
||||
g_signal_emit_by_name(self->def_nodes_api_, "get-default-node", "Audio/Sink", &self->node_id_);
|
||||
g_signal_emit_by_name(self->def_nodes_api_, "get-default-node", self->type_, &self->node_id_);
|
||||
|
||||
if (self->default_node_name_ != nullptr) {
|
||||
spdlog::debug("[{}]: (onObjectManagerInstalled) - default configured node name: {} and id: {}",
|
||||
self->name_, self->default_node_name_, self->node_id_);
|
||||
spdlog::debug(
|
||||
"[{}]: (onObjectManagerInstalled: {}) - default configured node name: {} and id: {}",
|
||||
self->name_, self->type_, self->default_node_name_, self->node_id_);
|
||||
}
|
||||
|
||||
updateVolume(self, self->node_id_);
|
||||
@@ -243,10 +267,10 @@ void waybar::modules::Wireplumber::activatePlugins() {
|
||||
}
|
||||
}
|
||||
|
||||
void waybar::modules::Wireplumber::prepare() {
|
||||
spdlog::debug("[{}]: preparing object manager", name_);
|
||||
void waybar::modules::Wireplumber::prepare(waybar::modules::Wireplumber* self) {
|
||||
spdlog::debug("[{}]: preparing object manager: '{}'", name_, self->type_);
|
||||
wp_object_manager_add_interest(om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_PW_PROPERTY, "media.class",
|
||||
"=s", "Audio/Sink", nullptr);
|
||||
"=s", self->type_, nullptr);
|
||||
}
|
||||
|
||||
void waybar::modules::Wireplumber::onDefaultNodesApiLoaded(WpObject* p, GAsyncResult* res,
|
||||
@@ -275,7 +299,7 @@ void waybar::modules::Wireplumber::onMixerApiLoaded(WpObject* p, GAsyncResult* r
|
||||
gboolean success = FALSE;
|
||||
g_autoptr(GError) error = nullptr;
|
||||
|
||||
success = wp_core_load_component_finish(self->wp_core_, res, nullptr);
|
||||
success = wp_core_load_component_finish(self->wp_core_, res, &error);
|
||||
|
||||
if (success == FALSE) {
|
||||
spdlog::error("[{}]: mixer API load failed", self->name_);
|
||||
|
||||
@@ -383,8 +383,38 @@ std::string Task::state_string(bool shortened) const {
|
||||
}
|
||||
|
||||
void Task::handle_title(const char *title) {
|
||||
if (title_.empty()) {
|
||||
spdlog::debug(fmt::format("Task ({}) setting title to {}", id_, title_));
|
||||
} else {
|
||||
spdlog::debug(fmt::format("Task ({}) overwriting title '{}' with '{}'", id_, title_, title));
|
||||
}
|
||||
title_ = title;
|
||||
hide_if_ignored();
|
||||
|
||||
if (!with_icon_ && !with_name_ || app_info_) {
|
||||
return;
|
||||
}
|
||||
|
||||
set_app_info_from_app_id_list(title_);
|
||||
name_ = app_info_ ? app_info_->get_display_name() : title;
|
||||
|
||||
if (!with_icon_) {
|
||||
return;
|
||||
}
|
||||
|
||||
int icon_size = config_["icon-size"].isInt() ? config_["icon-size"].asInt() : 16;
|
||||
bool found = false;
|
||||
for (auto &icon_theme : tbar_->icon_themes()) {
|
||||
if (image_load_icon(icon_, icon_theme, app_info_, icon_size)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (found)
|
||||
icon_.show();
|
||||
else
|
||||
spdlog::debug("Couldn't find icon for {}", title_);
|
||||
}
|
||||
|
||||
void Task::set_minimize_hint() {
|
||||
@@ -682,6 +712,9 @@ void Task::update() {
|
||||
fmt::format(fmt::runtime(format_tooltip_), fmt::arg("title", title), fmt::arg("name", name),
|
||||
fmt::arg("app_id", app_id), fmt::arg("state", state_string()),
|
||||
fmt::arg("short_state", state_string(true)));
|
||||
|
||||
txt = waybar::util::rewriteString(txt, config_["rewrite"]);
|
||||
|
||||
if (markup)
|
||||
button.set_tooltip_markup(txt);
|
||||
else
|
||||
|
||||
@@ -24,6 +24,8 @@ AudioBackend::AudioBackend(std::function<void()> on_updated_cb, private_construc
|
||||
source_volume_(0),
|
||||
source_muted_(false),
|
||||
on_updated_cb_(std::move(on_updated_cb)) {
|
||||
// Initialize pa_volume_ with safe defaults
|
||||
pa_cvolume_init(&pa_volume_);
|
||||
mainloop_ = pa_threaded_mainloop_new();
|
||||
if (mainloop_ == nullptr) {
|
||||
throw std::runtime_error("pa_mainloop_new() failed.");
|
||||
@@ -131,7 +133,12 @@ void AudioBackend::subscribeCb(pa_context *context, pa_subscription_event_type_t
|
||||
void AudioBackend::volumeModifyCb(pa_context *c, int success, void *data) {
|
||||
auto *backend = static_cast<AudioBackend *>(data);
|
||||
if (success != 0) {
|
||||
pa_context_get_sink_info_by_index(backend->context_, backend->sink_idx_, sinkInfoCb, data);
|
||||
if ((backend->context_ != nullptr) &&
|
||||
pa_context_get_state(backend->context_) == PA_CONTEXT_READY) {
|
||||
pa_context_get_sink_info_by_index(backend->context_, backend->sink_idx_, sinkInfoCb, data);
|
||||
}
|
||||
} else {
|
||||
spdlog::debug("Volume modification failed");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,11 +187,20 @@ void AudioBackend::sinkInfoCb(pa_context * /*context*/, const pa_sink_info *i, i
|
||||
}
|
||||
|
||||
if (backend->current_sink_name_ == i->name) {
|
||||
backend->pa_volume_ = i->volume;
|
||||
float volume =
|
||||
static_cast<float>(pa_cvolume_avg(&(backend->pa_volume_))) / float{PA_VOLUME_NORM};
|
||||
backend->sink_idx_ = i->index;
|
||||
backend->volume_ = std::round(volume * 100.0F);
|
||||
// Safely copy the volume structure
|
||||
if (pa_cvolume_valid(&i->volume) != 0) {
|
||||
backend->pa_volume_ = i->volume;
|
||||
float volume =
|
||||
static_cast<float>(pa_cvolume_avg(&(backend->pa_volume_))) / float{PA_VOLUME_NORM};
|
||||
backend->sink_idx_ = i->index;
|
||||
backend->volume_ = std::round(volume * 100.0F);
|
||||
} else {
|
||||
spdlog::error("Invalid volume structure received from PulseAudio");
|
||||
// Initialize with safe defaults
|
||||
pa_cvolume_init(&backend->pa_volume_);
|
||||
backend->volume_ = 0;
|
||||
}
|
||||
|
||||
backend->muted_ = i->mute != 0;
|
||||
backend->desc_ = i->description;
|
||||
backend->monitor_ = i->monitor_source_name;
|
||||
@@ -230,43 +246,109 @@ void AudioBackend::serverInfoCb(pa_context *context, const pa_server_info *i, vo
|
||||
}
|
||||
|
||||
void AudioBackend::changeVolume(uint16_t volume, uint16_t min_volume, uint16_t max_volume) {
|
||||
double volume_tick = static_cast<double>(PA_VOLUME_NORM) / 100;
|
||||
pa_cvolume pa_volume = pa_volume_;
|
||||
// Early return if context is not ready
|
||||
if ((context_ == nullptr) || pa_context_get_state(context_) != PA_CONTEXT_READY) {
|
||||
spdlog::error("PulseAudio context not ready");
|
||||
return;
|
||||
}
|
||||
|
||||
// Prepare volume structure
|
||||
pa_cvolume pa_volume;
|
||||
|
||||
pa_cvolume_init(&pa_volume);
|
||||
|
||||
// Use existing volume structure if valid, otherwise create a safe default
|
||||
if ((pa_cvolume_valid(&pa_volume_) != 0) && (pa_channels_valid(pa_volume_.channels) != 0)) {
|
||||
pa_volume = pa_volume_;
|
||||
} else {
|
||||
// Set stereo as a safe default
|
||||
pa_volume.channels = 2;
|
||||
spdlog::debug("Using default stereo volume structure");
|
||||
}
|
||||
|
||||
// Set the volume safely
|
||||
volume = std::clamp(volume, min_volume, max_volume);
|
||||
pa_cvolume_set(&pa_volume, pa_volume_.channels, volume * volume_tick);
|
||||
pa_volume_t vol = volume * (static_cast<double>(PA_VOLUME_NORM) / 100);
|
||||
|
||||
// Set all channels to the same volume manually to avoid pa_cvolume_set
|
||||
for (uint8_t i = 0; i < pa_volume.channels; i++) {
|
||||
pa_volume.values[i] = vol;
|
||||
}
|
||||
|
||||
// Apply the volume change
|
||||
pa_threaded_mainloop_lock(mainloop_);
|
||||
pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume, volumeModifyCb, this);
|
||||
pa_threaded_mainloop_unlock(mainloop_);
|
||||
}
|
||||
|
||||
void AudioBackend::changeVolume(ChangeType change_type, double step, uint16_t max_volume) {
|
||||
double volume_tick = static_cast<double>(PA_VOLUME_NORM) / 100;
|
||||
pa_volume_t change = volume_tick;
|
||||
pa_cvolume pa_volume = pa_volume_;
|
||||
// Early return if context is not ready
|
||||
if ((context_ == nullptr) || pa_context_get_state(context_) != PA_CONTEXT_READY) {
|
||||
spdlog::error("PulseAudio context not ready");
|
||||
return;
|
||||
}
|
||||
|
||||
// Prepare volume structure
|
||||
pa_cvolume pa_volume;
|
||||
pa_cvolume_init(&pa_volume);
|
||||
|
||||
// Use existing volume structure if valid, otherwise create a safe default
|
||||
if ((pa_cvolume_valid(&pa_volume_) != 0) && (pa_channels_valid(pa_volume_.channels) != 0)) {
|
||||
pa_volume = pa_volume_;
|
||||
} else {
|
||||
// Set stereo as a safe default
|
||||
pa_volume.channels = 2;
|
||||
spdlog::debug("Using default stereo volume structure");
|
||||
|
||||
// Initialize all channels to current volume level
|
||||
double volume_tick = static_cast<double>(PA_VOLUME_NORM) / 100;
|
||||
pa_volume_t vol = volume_ * volume_tick;
|
||||
for (uint8_t i = 0; i < pa_volume.channels; i++) {
|
||||
pa_volume.values[i] = vol;
|
||||
}
|
||||
|
||||
// No need to continue with volume change if we had to create a new structure
|
||||
pa_threaded_mainloop_lock(mainloop_);
|
||||
pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume, volumeModifyCb, this);
|
||||
pa_threaded_mainloop_unlock(mainloop_);
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate volume change
|
||||
double volume_tick = static_cast<double>(PA_VOLUME_NORM) / 100;
|
||||
pa_volume_t change;
|
||||
max_volume = std::min(max_volume, static_cast<uint16_t>(PA_VOLUME_UI_MAX));
|
||||
|
||||
if (change_type == ChangeType::Increase) {
|
||||
if (volume_ < max_volume) {
|
||||
if (volume_ + step > max_volume) {
|
||||
change = round((max_volume - volume_) * volume_tick);
|
||||
} else {
|
||||
change = round(step * volume_tick);
|
||||
}
|
||||
pa_cvolume_inc(&pa_volume, change);
|
||||
if (change_type == ChangeType::Increase && volume_ < max_volume) {
|
||||
// Calculate how much to increase
|
||||
if (volume_ + step > max_volume) {
|
||||
change = round((max_volume - volume_) * volume_tick);
|
||||
} else {
|
||||
change = round(step * volume_tick);
|
||||
}
|
||||
} else if (change_type == ChangeType::Decrease) {
|
||||
if (volume_ > 0) {
|
||||
if (volume_ - step < 0) {
|
||||
change = round(volume_ * volume_tick);
|
||||
} else {
|
||||
change = round(step * volume_tick);
|
||||
}
|
||||
pa_cvolume_dec(&pa_volume, change);
|
||||
|
||||
// Manually increase each channel's volume
|
||||
for (uint8_t i = 0; i < pa_volume.channels; i++) {
|
||||
pa_volume.values[i] = std::min(pa_volume.values[i] + change, PA_VOLUME_MAX);
|
||||
}
|
||||
} else if (change_type == ChangeType::Decrease && volume_ > 0) {
|
||||
// Calculate how much to decrease
|
||||
if (volume_ - step < 0) {
|
||||
change = round(volume_ * volume_tick);
|
||||
} else {
|
||||
change = round(step * volume_tick);
|
||||
}
|
||||
|
||||
// Manually decrease each channel's volume
|
||||
for (uint8_t i = 0; i < pa_volume.channels; i++) {
|
||||
pa_volume.values[i] = (pa_volume.values[i] > change) ? (pa_volume.values[i] - change) : 0;
|
||||
}
|
||||
} else {
|
||||
// No change needed
|
||||
return;
|
||||
}
|
||||
|
||||
// Apply the volume change
|
||||
pa_threaded_mainloop_lock(mainloop_);
|
||||
pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume, volumeModifyCb, this);
|
||||
pa_threaded_mainloop_unlock(mainloop_);
|
||||
|
||||
@@ -150,6 +150,7 @@ BacklightBackend::BacklightBackend(std::chrono::milliseconds interval,
|
||||
throw std::runtime_error("No backlight found");
|
||||
}
|
||||
|
||||
#ifdef HAVE_LOGIN_PROXY
|
||||
// Connect to the login interface
|
||||
login_proxy_ = Gio::DBus::Proxy::create_for_bus_sync(
|
||||
Gio::DBus::BusType::BUS_TYPE_SYSTEM, "org.freedesktop.login1",
|
||||
@@ -160,6 +161,7 @@ BacklightBackend::BacklightBackend(std::chrono::milliseconds interval,
|
||||
Gio::DBus::BusType::BUS_TYPE_SYSTEM, "org.freedesktop.login1",
|
||||
"/org/freedesktop/login1/session/self", "org.freedesktop.login1.Session");
|
||||
}
|
||||
#endif
|
||||
|
||||
udev_thread_ = [this] {
|
||||
std::unique_ptr<udev, UdevDeleter> udev{udev_new()};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user