Merge remote-tracking branch 'upstream/master'
commit
6f3b50fb60
|
@ -0,0 +1 @@
|
|||
use flake
|
|
@ -9,7 +9,7 @@ jobs:
|
|||
# https://github.com/actions/virtual-environments/issues/4060 - for lack of VirtualBox on MacOS 11 runners
|
||||
runs-on: macos-12
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Test in FreeBSD VM
|
||||
uses: vmactions/freebsd-vm@v0
|
||||
with:
|
||||
|
|
|
@ -6,7 +6,7 @@ jobs:
|
|||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- uses: DoozyX/clang-format-lint-action@v0.13
|
||||
with:
|
||||
source: '.'
|
||||
|
|
|
@ -13,16 +13,20 @@ jobs:
|
|||
- fedora
|
||||
- opensuse
|
||||
- gentoo
|
||||
cpp_std: [c++17]
|
||||
include:
|
||||
- distro: fedora
|
||||
cpp_std: c++20
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: alexays/waybar:${{ matrix.distro }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: configure
|
||||
run: meson -Dman-pages=enabled build
|
||||
run: meson -Dman-pages=enabled -Dcpp_std=${{matrix.cpp_std}} build
|
||||
- name: build
|
||||
run: ninja -C build
|
||||
- name: test
|
||||
run: meson test -C build --no-rebuild --print-errorlogs --suite waybar
|
||||
run: meson test -C build --no-rebuild --verbose --suite waybar
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
*~
|
||||
vgcore.*
|
||||
/.vscode
|
||||
/.idea
|
||||
/.cache
|
||||
*.swp
|
||||
packagecache
|
||||
/subprojects/**/
|
||||
|
@ -41,3 +43,4 @@ packagecache
|
|||
*.exe
|
||||
*.out
|
||||
*.app
|
||||
/.direnv/
|
||||
|
|
|
@ -2,4 +2,4 @@
|
|||
|
||||
FROM alpine:latest
|
||||
|
||||
RUN apk add --no-cache git meson alpine-sdk libinput-dev wayland-dev wayland-protocols mesa-dev libxkbcommon-dev eudev-dev pixman-dev gtkmm3-dev jsoncpp-dev pugixml-dev libnl3-dev pulseaudio-dev libmpdclient-dev sndio-dev scdoc libxkbcommon tzdata
|
||||
RUN apk add --no-cache git meson alpine-sdk libinput-dev wayland-dev wayland-protocols mesa-dev libxkbcommon-dev eudev-dev pixman-dev gtkmm3-dev jsoncpp-dev pugixml-dev libnl3-dev pulseaudio-dev libmpdclient-dev sndio-dev scdoc libxkbcommon tzdata playerctl-dev
|
||||
|
|
|
@ -3,4 +3,5 @@
|
|||
FROM archlinux:base-devel
|
||||
|
||||
RUN pacman -Syu --noconfirm && \
|
||||
pacman -S git meson base-devel libinput wayland wayland-protocols pixman libxkbcommon mesa gtkmm3 jsoncpp pugixml scdoc libpulse libdbusmenu-gtk3 libmpdclient gobject-introspection --noconfirm libxkbcommon
|
||||
pacman -S --noconfirm git meson base-devel libinput wayland wayland-protocols pixman libxkbcommon mesa gtkmm3 jsoncpp pugixml scdoc libpulse libdbusmenu-gtk3 libmpdclient gobject-introspection libxkbcommon playerctl && \
|
||||
sed -Ei 's/#(en_(US|GB)\.UTF)/\1/' /etc/locale.gen && locale-gen
|
||||
|
|
|
@ -3,5 +3,5 @@
|
|||
FROM debian:sid
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y build-essential meson ninja-build git pkg-config libinput10 libpugixml-dev libinput-dev wayland-protocols libwayland-client0 libwayland-cursor0 libwayland-dev libegl1-mesa-dev libgles2-mesa-dev libgbm-dev libxkbcommon-dev libudev-dev libpixman-1-dev libgtkmm-3.0-dev libjsoncpp-dev scdoc libdbusmenu-gtk3-dev libnl-3-dev libnl-genl-3-dev libpulse-dev libmpdclient-dev gobject-introspection libgirepository1.0-dev libxkbcommon-dev libxkbregistry-dev libxkbregistry0 && \
|
||||
apt-get install -y build-essential meson ninja-build git pkg-config libinput10 libpugixml-dev libinput-dev wayland-protocols libwayland-client0 libwayland-cursor0 libwayland-dev libegl1-mesa-dev libgles2-mesa-dev libgbm-dev libxkbcommon-dev libudev-dev libpixman-1-dev libgtkmm-3.0-dev libjsoncpp-dev scdoc libdbusmenu-gtk3-dev libnl-3-dev libnl-genl-3-dev libpulse-dev libmpdclient-dev gobject-introspection libgirepository1.0-dev libxkbcommon-dev libxkbregistry-dev libxkbregistry0 libplayerctl-dev && \
|
||||
apt-get clean
|
||||
|
|
|
@ -2,11 +2,33 @@
|
|||
|
||||
FROM fedora:latest
|
||||
|
||||
RUN dnf install -y @c-development git-core meson scdoc 'pkgconfig(date)' \
|
||||
'pkgconfig(dbusmenu-gtk3-0.4)' 'pkgconfig(fmt)' 'pkgconfig(gdk-pixbuf-2.0)' \
|
||||
'pkgconfig(gio-unix-2.0)' 'pkgconfig(gtk-layer-shell-0)' 'pkgconfig(gtkmm-3.0)' \
|
||||
'pkgconfig(jsoncpp)' 'pkgconfig(libinput)' 'pkgconfig(libmpdclient)' \
|
||||
'pkgconfig(libnl-3.0)' 'pkgconfig(libnl-genl-3.0)' 'pkgconfig(libpulse)' \
|
||||
'pkgconfig(libudev)' 'pkgconfig(pugixml)' 'pkgconfig(sigc++-2.0)' 'pkgconfig(spdlog)' \
|
||||
'pkgconfig(wayland-client)' 'pkgconfig(wayland-cursor)' 'pkgconfig(wayland-protocols)' 'pkgconfig(xkbregistry)' && \
|
||||
RUN dnf install -y @c-development \
|
||||
git-core glibc-langpack-en meson scdoc \
|
||||
'pkgconfig(catch2)' \
|
||||
'pkgconfig(date)' \
|
||||
'pkgconfig(dbusmenu-gtk3-0.4)' \
|
||||
'pkgconfig(fmt)' \
|
||||
'pkgconfig(gdk-pixbuf-2.0)' \
|
||||
'pkgconfig(gio-unix-2.0)' \
|
||||
'pkgconfig(gtk-layer-shell-0)' \
|
||||
'pkgconfig(gtkmm-3.0)' \
|
||||
'pkgconfig(jack)' \
|
||||
'pkgconfig(jsoncpp)' \
|
||||
'pkgconfig(libevdev)' \
|
||||
'pkgconfig(libinput)' \
|
||||
'pkgconfig(libmpdclient)' \
|
||||
'pkgconfig(libnl-3.0)' \
|
||||
'pkgconfig(libnl-genl-3.0)' \
|
||||
'pkgconfig(libpulse)' \
|
||||
'pkgconfig(libudev)' \
|
||||
'pkgconfig(playerctl)' \
|
||||
'pkgconfig(pugixml)' \
|
||||
'pkgconfig(sigc++-2.0)' \
|
||||
'pkgconfig(spdlog)' \
|
||||
'pkgconfig(upower-glib)' \
|
||||
'pkgconfig(wayland-client)' \
|
||||
'pkgconfig(wayland-cursor)' \
|
||||
'pkgconfig(wayland-protocols)' \
|
||||
'pkgconfig(wireplumber-0.4)' \
|
||||
'pkgconfig(xkbregistry)' && \
|
||||
dnf clean all -y
|
||||
|
|
|
@ -8,4 +8,4 @@ RUN export FEATURES="-ipc-sandbox -network-sandbox -pid-sandbox -sandbox -usersa
|
|||
emerge --verbose --update --deep --with-bdeps=y --backtrack=30 --newuse @world && \
|
||||
USE="wayland gtk3 gtk -doc X" emerge dev-vcs/git dev-libs/wayland dev-libs/wayland-protocols =dev-cpp/gtkmm-3.24.6 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/sndio gui-libs/gtk-layer-shell app-text/scdoc media-sound/playerctl
|
||||
|
|
|
@ -6,4 +6,4 @@ RUN zypper -n up && \
|
|||
zypper addrepo https://download.opensuse.org/repositories/X11:Wayland/openSUSE_Tumbleweed/X11:Wayland.repo | echo 'a' && \
|
||||
zypper -n refresh && \
|
||||
zypper -n install -t pattern devel_C_C++ && \
|
||||
zypper -n install git meson clang libinput10 libinput-devel pugixml-devel libwayland-client0 libwayland-cursor0 wayland-protocols-devel wayland-devel Mesa-libEGL-devel Mesa-libGLESv2-devel libgbm-devel libxkbcommon-devel libudev-devel libpixman-1-0-devel gtkmm3-devel jsoncpp-devel libxkbregistry-devel scdoc
|
||||
zypper -n install git meson clang libinput10 libinput-devel pugixml-devel libwayland-client0 libwayland-cursor0 wayland-protocols-devel wayland-devel Mesa-libEGL-devel Mesa-libGLESv2-devel libgbm-devel libxkbcommon-devel libudev-devel libpixman-1-0-devel gtkmm3-devel jsoncpp-devel libxkbregistry-devel scdoc playerctl-devel
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
{
|
||||
"nodes": {
|
||||
"devshell": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1667210711,
|
||||
"narHash": "sha256-IoErjXZAkzYWHEpQqwu/DeRNJGFdR7X2OGbkhMqMrpw=",
|
||||
"owner": "numtide",
|
||||
"repo": "devshell",
|
||||
"rev": "96a9dd12b8a447840cc246e17a47b81a4268bba7",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "devshell",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils": {
|
||||
"locked": {
|
||||
"lastModified": 1642700792,
|
||||
"narHash": "sha256-XqHrk7hFb+zBvRg6Ghl+AZDq03ov6OshJLiSWOoX5es=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "846b2ae0fc4cc943637d3d1def4454213e203cba",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils_2": {
|
||||
"locked": {
|
||||
"lastModified": 1667395993,
|
||||
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1643381941,
|
||||
"narHash": "sha256-pHTwvnN4tTsEKkWlXQ8JMY423epos8wUOhthpwJjtpc=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "5efc8ca954272c4376ac929f4c5ffefcc20551d5",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1670152712,
|
||||
"narHash": "sha256-LJttwIvJqsZIj8u1LxVRv82vwUtkzVqQVi7Wb8gxPS4=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "14ddeaebcbe9a25748221d1d7ecdf98e20e2325e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"devshell": "devshell",
|
||||
"flake-utils": "flake-utils_2",
|
||||
"nixpkgs": "nixpkgs_2"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
{
|
||||
description = "Highly customizable Wayland bar for Sway and Wlroots based compositors.";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
devshell.url = "github:numtide/devshell";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
};
|
||||
|
||||
outputs = { self, flake-utils, devshell, nixpkgs }:
|
||||
let
|
||||
inherit (nixpkgs) lib;
|
||||
genSystems = lib.genAttrs [
|
||||
"x86_64-linux"
|
||||
];
|
||||
|
||||
pkgsFor = genSystems (system:
|
||||
import nixpkgs {
|
||||
inherit system;
|
||||
});
|
||||
|
||||
mkDate = longDate: (lib.concatStringsSep "-" [
|
||||
(builtins.substring 0 4 longDate)
|
||||
(builtins.substring 4 2 longDate)
|
||||
(builtins.substring 6 2 longDate)
|
||||
]);
|
||||
in
|
||||
{
|
||||
overlays.default = _: prev: rec {
|
||||
waybar = prev.callPackage ./nix/default.nix {
|
||||
version = "0.9.16" + "+date=" + (mkDate (self.lastModifiedDate or "19700101")) + "_" + (self.shortRev or "dirty");
|
||||
};
|
||||
};
|
||||
packages = genSystems
|
||||
(system:
|
||||
(self.overlays.default null pkgsFor.${system})
|
||||
// {
|
||||
default = self.packages.${system}.waybar;
|
||||
});
|
||||
} //
|
||||
flake-utils.lib.eachDefaultSystem (system: {
|
||||
devShell =
|
||||
let pkgs = import nixpkgs {
|
||||
inherit system;
|
||||
|
||||
overlays = [ devshell.overlay ];
|
||||
};
|
||||
in
|
||||
pkgs.devshell.mkShell {
|
||||
imports = [ "${pkgs.devshell.extraModulesDir}/language/c.nix" ];
|
||||
commands = [
|
||||
{
|
||||
package = pkgs.devshell.cli;
|
||||
help = "Per project developer environments";
|
||||
}
|
||||
];
|
||||
devshell.packages = with pkgs; [
|
||||
clang-tools
|
||||
gdb
|
||||
];
|
||||
language.c.libraries = with pkgs; [
|
||||
];
|
||||
};
|
||||
});
|
||||
}
|
|
@ -15,6 +15,7 @@ class AModule : public IModule {
|
|||
bool enable_scroll = false);
|
||||
virtual ~AModule();
|
||||
virtual auto update() -> void;
|
||||
virtual auto refresh(int) -> void{};
|
||||
virtual operator Gtk::Widget &();
|
||||
|
||||
Glib::Dispatcher dp;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <json/json.h>
|
||||
#ifdef HAVE_LIBDATE
|
||||
#if defined(HAVE_CHRONO_TIMEZONES) || defined(HAVE_LIBDATE)
|
||||
#include "modules/clock.hpp"
|
||||
#else
|
||||
#include "modules/simpleclock.hpp"
|
||||
|
@ -25,6 +25,7 @@
|
|||
#ifdef HAVE_HYPRLAND
|
||||
#include "modules/hyprland/backend.hpp"
|
||||
#include "modules/hyprland/language.hpp"
|
||||
#include "modules/hyprland/submap.hpp"
|
||||
#include "modules/hyprland/window.hpp"
|
||||
#endif
|
||||
#if defined(__FreeBSD__) || (defined(__linux__) && !defined(NO_FILESYSTEM))
|
||||
|
@ -41,6 +42,9 @@
|
|||
#ifdef HAVE_DBUSMENU
|
||||
#include "modules/sni/tray.hpp"
|
||||
#endif
|
||||
#ifdef HAVE_MPRIS
|
||||
#include "modules/mpris/mpris.hpp"
|
||||
#endif
|
||||
#ifdef HAVE_LIBNL
|
||||
#include "modules/network.hpp"
|
||||
#endif
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include <vector>
|
||||
|
||||
#include "ALabel.hpp"
|
||||
#include "giomm/dbusproxy.h"
|
||||
#include "util/json.hpp"
|
||||
#include "util/sleeper_thread.hpp"
|
||||
|
||||
|
@ -50,6 +51,8 @@ class Backlight : public ALabel {
|
|||
template <class ForwardIt, class Inserter>
|
||||
static void enumerate_devices(ForwardIt first, ForwardIt last, Inserter inserter, udev *udev);
|
||||
|
||||
bool handleScroll(GdkEventScroll *e);
|
||||
|
||||
const std::string preferred_device_;
|
||||
static constexpr int EPOLL_MAX_EVENTS = 16;
|
||||
|
||||
|
@ -60,5 +63,7 @@ class Backlight : public ALabel {
|
|||
std::vector<BacklightDev> devices_;
|
||||
// thread must destruct before shared data
|
||||
util::SleeperThread udev_thread_;
|
||||
|
||||
Glib::RefPtr<Gio::DBus::Proxy> login_proxy_;
|
||||
};
|
||||
} // namespace waybar::modules
|
||||
|
|
|
@ -1,19 +1,22 @@
|
|||
#pragma once
|
||||
|
||||
#include <date/tz.h>
|
||||
|
||||
#include "ALabel.hpp"
|
||||
#include "util/date.hpp"
|
||||
#include "util/sleeper_thread.hpp"
|
||||
|
||||
namespace waybar {
|
||||
|
||||
struct waybar_time;
|
||||
|
||||
namespace modules {
|
||||
namespace waybar::modules {
|
||||
|
||||
const std::string kCalendarPlaceholder = "calendar";
|
||||
const std::string KTimezonedTimeListPlaceholder = "timezoned_time_list";
|
||||
|
||||
enum class WeeksSide {
|
||||
LEFT,
|
||||
RIGHT,
|
||||
HIDDEN,
|
||||
};
|
||||
|
||||
enum class CldMode { MONTH, YEAR };
|
||||
|
||||
class Clock : public ALabel {
|
||||
public:
|
||||
Clock(const std::string&, const Json::Value&);
|
||||
|
@ -22,23 +25,37 @@ class Clock : public ALabel {
|
|||
|
||||
private:
|
||||
util::SleeperThread thread_;
|
||||
std::map<std::pair<uint, GdkEventType>, void (waybar::modules::Clock::*)()> eventMap_;
|
||||
std::locale locale_;
|
||||
std::vector<const date::time_zone*> time_zones_;
|
||||
int current_time_zone_idx_;
|
||||
date::year_month_day calendar_cached_ymd_{date::January / 1 / 0};
|
||||
date::months calendar_shift_{0}, calendar_shift_init_{0};
|
||||
std::string calendar_cached_text_;
|
||||
bool is_calendar_in_tooltip_;
|
||||
bool is_timezoned_list_in_tooltip_;
|
||||
|
||||
bool handleScroll(GdkEventScroll* e);
|
||||
bool handleToggle(GdkEventButton* const& e);
|
||||
|
||||
auto calendar_text(const waybar_time& wtime) -> std::string;
|
||||
auto weekdays_header(const date::weekday& first_dow, std::ostream& os) -> void;
|
||||
auto first_day_of_week() -> date::weekday;
|
||||
const date::time_zone* current_timezone();
|
||||
bool is_timezone_fixed();
|
||||
auto timezones_text(std::chrono::system_clock::time_point* now) -> std::string;
|
||||
|
||||
/*Calendar properties*/
|
||||
WeeksSide cldWPos_{WeeksSide::HIDDEN};
|
||||
std::map<int, std::string const> fmtMap_;
|
||||
CldMode cldMode_{CldMode::MONTH};
|
||||
uint cldMonCols_{3}; // Count of the month in the row
|
||||
int cldMonColLen_{20}; // Length of the month column
|
||||
int cldWnLen_{3}; // Length of the week number
|
||||
date::year_month_day cldYearShift_;
|
||||
date::year_month cldMonShift_;
|
||||
date::months cldCurrShift_{0};
|
||||
date::months cldShift_{0};
|
||||
std::string cldYearCached_{};
|
||||
std::string cldMonCached_{};
|
||||
/*Calendar functions*/
|
||||
auto get_calendar(const date::zoned_seconds& now, const date::zoned_seconds& wtime)
|
||||
-> std::string;
|
||||
void cldModeSwitch();
|
||||
};
|
||||
} // namespace modules
|
||||
} // namespace waybar
|
||||
} // namespace waybar::modules
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
#include <fmt/format.h>
|
||||
|
||||
#include "ALabel.hpp"
|
||||
#include "bar.hpp"
|
||||
#include "modules/hyprland/backend.hpp"
|
||||
#include "util/json.hpp"
|
||||
|
||||
namespace waybar::modules::hyprland {
|
||||
|
||||
class Submap : public waybar::ALabel, public EventHandler {
|
||||
public:
|
||||
Submap(const std::string&, const waybar::Bar&, const Json::Value&);
|
||||
~Submap();
|
||||
|
||||
auto update() -> void;
|
||||
|
||||
private:
|
||||
void onEvent(const std::string&);
|
||||
|
||||
std::mutex mutex_;
|
||||
const Bar& bar_;
|
||||
util::JsonParser parser_;
|
||||
std::string submap_;
|
||||
};
|
||||
|
||||
} // namespace waybar::modules::hyprland
|
|
@ -7,6 +7,7 @@
|
|||
#include <string>
|
||||
|
||||
#include "ALabel.hpp"
|
||||
#include "gtkmm/box.h"
|
||||
#include "util/command.hpp"
|
||||
#include "util/json.hpp"
|
||||
#include "util/sleeper_thread.hpp"
|
||||
|
@ -15,7 +16,7 @@ namespace waybar::modules {
|
|||
|
||||
class Image : public AModule {
|
||||
public:
|
||||
Image(const std::string&, const std::string&, const Json::Value&);
|
||||
Image(const std::string&, const Json::Value&);
|
||||
auto update() -> void;
|
||||
void refresh(int /*signal*/);
|
||||
|
||||
|
@ -23,6 +24,7 @@ class Image : public AModule {
|
|||
void delayWorker();
|
||||
void handleEvent();
|
||||
|
||||
Gtk::Box box_;
|
||||
Gtk::Image image_;
|
||||
std::string path_;
|
||||
int size_;
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include "gtkmm/box.h"
|
||||
#include "gtkmm/label.h"
|
||||
|
||||
extern "C" {
|
||||
#include <playerctl/playerctl.h>
|
||||
}
|
||||
|
||||
#include "ALabel.hpp"
|
||||
#include "util/sleeper_thread.hpp"
|
||||
|
||||
namespace waybar::modules::mpris {
|
||||
|
||||
class Mpris : public AModule {
|
||||
public:
|
||||
Mpris(const std::string&, const Json::Value&);
|
||||
~Mpris();
|
||||
auto update() -> void;
|
||||
bool handleToggle(GdkEventButton* const&);
|
||||
|
||||
private:
|
||||
static auto onPlayerNameAppeared(PlayerctlPlayerManager*, PlayerctlPlayerName*, gpointer) -> void;
|
||||
static auto onPlayerNameVanished(PlayerctlPlayerManager*, PlayerctlPlayerName*, gpointer) -> void;
|
||||
static auto onPlayerPlay(PlayerctlPlayer*, gpointer) -> void;
|
||||
static auto onPlayerPause(PlayerctlPlayer*, gpointer) -> void;
|
||||
static auto onPlayerStop(PlayerctlPlayer*, gpointer) -> void;
|
||||
static auto onPlayerMetadata(PlayerctlPlayer*, GVariant*, gpointer) -> void;
|
||||
|
||||
struct PlayerInfo {
|
||||
std::string name;
|
||||
PlayerctlPlaybackStatus status;
|
||||
std::string status_string;
|
||||
|
||||
std::optional<std::string> artist;
|
||||
std::optional<std::string> album;
|
||||
std::optional<std::string> title;
|
||||
std::optional<std::string> length; // as HH:MM:SS
|
||||
};
|
||||
|
||||
auto getPlayerInfo() -> std::optional<PlayerInfo>;
|
||||
auto getIcon(const Json::Value&, const std::string&) -> std::string;
|
||||
|
||||
Gtk::Box box_;
|
||||
Gtk::Label label_;
|
||||
|
||||
// config
|
||||
std::string format_;
|
||||
std::string format_playing_;
|
||||
std::string format_paused_;
|
||||
std::string format_stopped_;
|
||||
std::chrono::seconds interval_;
|
||||
std::string player_;
|
||||
std::vector<std::string> ignored_players_;
|
||||
|
||||
PlayerctlPlayerManager* manager;
|
||||
PlayerctlPlayer* player;
|
||||
std::string lastStatus;
|
||||
std::string lastPlayer;
|
||||
|
||||
util::SleeperThread thread_;
|
||||
};
|
||||
|
||||
} // namespace waybar::modules::mpris
|
|
@ -8,6 +8,7 @@
|
|||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
|
||||
#include "ipc.hpp"
|
||||
#include "util/sleeper_thread.hpp"
|
||||
|
|
|
@ -19,10 +19,11 @@ class Window : public AIconLabel, public sigc::trackable {
|
|||
auto update() -> void;
|
||||
|
||||
private:
|
||||
void setClass(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, std::string, std::string, std::string, std::string> getFocusedNode(
|
||||
const Json::Value& nodes, std::string& output);
|
||||
std::tuple<std::size_t, int, int, std::string, std::string, std::string, std::string, std::string>
|
||||
getFocusedNode(const Json::Value& nodes, std::string& output);
|
||||
void getTree();
|
||||
void updateAppIconName();
|
||||
void updateAppIcon();
|
||||
|
@ -32,12 +33,14 @@ class Window : public AIconLabel, public sigc::trackable {
|
|||
int windowId_;
|
||||
std::string app_id_;
|
||||
std::string app_class_;
|
||||
std::string layout_;
|
||||
std::string old_app_id_;
|
||||
std::size_t app_nb_;
|
||||
std::string shell_;
|
||||
unsigned app_icon_size_{24};
|
||||
bool update_app_icon_{true};
|
||||
std::string app_icon_name_;
|
||||
int floating_count_;
|
||||
util::JsonParser parser_;
|
||||
std::mutex mutex_;
|
||||
Ipc ipc_;
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include <gtkmm/button.h>
|
||||
#include <gtkmm/label.h>
|
||||
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "AModule.hpp"
|
||||
|
@ -21,7 +22,9 @@ class Workspaces : public AModule, public sigc::trackable {
|
|||
auto update() -> void;
|
||||
|
||||
private:
|
||||
static inline const std::string workspace_switch_cmd_ = "workspace {} \"{}\"";
|
||||
static constexpr std::string_view workspace_switch_cmd_ = "workspace {} \"{}\"";
|
||||
static constexpr std::string_view persistent_workspace_switch_cmd_ =
|
||||
R"(workspace {} "{}"; move workspace to output "{}"; workspace {} "{}")";
|
||||
|
||||
static int convertWorkspaceNameToNum(std::string name);
|
||||
|
||||
|
|
|
@ -20,15 +20,19 @@ class Wireplumber : public ALabel {
|
|||
void loadRequiredApiModules();
|
||||
void prepare();
|
||||
void activatePlugins();
|
||||
static void updateVolume(waybar::modules::Wireplumber* self);
|
||||
static void updateNodeName(waybar::modules::Wireplumber* self);
|
||||
static uint32_t getDefaultNodeId(waybar::modules::Wireplumber* self);
|
||||
static void updateVolume(waybar::modules::Wireplumber* self, uint32_t id);
|
||||
static void updateNodeName(waybar::modules::Wireplumber* self, uint32_t id);
|
||||
static void onPluginActivated(WpObject* p, GAsyncResult* res, waybar::modules::Wireplumber* self);
|
||||
static void onObjectManagerInstalled(waybar::modules::Wireplumber* self);
|
||||
static void onMixerChanged(waybar::modules::Wireplumber* self, uint32_t id);
|
||||
static void onDefaultNodesApiChanged(waybar::modules::Wireplumber* self);
|
||||
|
||||
WpCore* wp_core_;
|
||||
GPtrArray* apis_;
|
||||
WpObjectManager* om_;
|
||||
WpPlugin* mixer_api_;
|
||||
WpPlugin* def_nodes_api_;
|
||||
gchar* default_node_name_;
|
||||
uint32_t pending_plugins_;
|
||||
bool muted_;
|
||||
double volume_;
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
#pragma once
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#if HAVE_CHRONO_TIMEZONES
|
||||
#include <chrono>
|
||||
#include <format>
|
||||
|
||||
/* Compatibility layer for <date/tz.h> on top of C++20 <chrono> */
|
||||
namespace date {
|
||||
|
||||
using namespace std::chrono;
|
||||
|
||||
namespace literals {
|
||||
using std::chrono::last;
|
||||
}
|
||||
|
||||
inline auto format(const std::string& spec, const auto& ztime) {
|
||||
return spec.empty() ? "" : std::vformat("{:L" + spec + "}", std::make_format_args(ztime));
|
||||
}
|
||||
|
||||
inline auto format(const std::locale& loc, const std::string& spec, const auto& ztime) {
|
||||
return spec.empty() ? "" : std::vformat(loc, "{:L" + spec + "}", std::make_format_args(ztime));
|
||||
}
|
||||
|
||||
} // namespace date
|
||||
|
||||
#else
|
||||
#include <date/tz.h>
|
||||
#endif
|
||||
|
||||
template <typename Duration, typename TimeZonePtr>
|
||||
struct fmt::formatter<date::zoned_time<Duration, TimeZonePtr>> {
|
||||
std::string_view specs;
|
||||
|
||||
template <typename ParseContext>
|
||||
constexpr auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
auto it = ctx.begin();
|
||||
if (it != ctx.end() && *it == ':') {
|
||||
++it;
|
||||
}
|
||||
auto end = it;
|
||||
while (end != ctx.end() && *end != '}') {
|
||||
++end;
|
||||
}
|
||||
if (end != it) {
|
||||
specs = {it, std::string_view::size_type(end - it)};
|
||||
}
|
||||
return end;
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const date::zoned_time<Duration, TimeZonePtr>& ztime, FormatContext& ctx) {
|
||||
if (ctx.locale()) {
|
||||
const auto loc = ctx.locale().template get<std::locale>();
|
||||
return fmt::format_to(ctx.out(), "{}", date::format(loc, fmt::to_string(specs), ztime));
|
||||
}
|
||||
return fmt::format_to(ctx.out(), "{}", date::format(fmt::to_string(specs), ztime));
|
||||
}
|
||||
};
|
|
@ -66,9 +66,9 @@ struct formatter<pow_format> {
|
|||
std::string string;
|
||||
switch (spec) {
|
||||
case '>':
|
||||
return format_to(ctx.out(), "{:>{}}", fmt::format("{}", s), max_width);
|
||||
return fmt::format_to(ctx.out(), "{:>{}}", fmt::format("{}", s), max_width);
|
||||
case '<':
|
||||
return format_to(ctx.out(), "{:<{}}", fmt::format("{}", s), max_width);
|
||||
return fmt::format_to(ctx.out(), "{:<{}}", fmt::format("{}", s), max_width);
|
||||
case '=':
|
||||
format = "{coefficient:<{number_width}.1f}{padding}{prefix}{unit}";
|
||||
break;
|
||||
|
@ -77,8 +77,8 @@ struct formatter<pow_format> {
|
|||
format = "{coefficient:.1f}{prefix}{unit}";
|
||||
break;
|
||||
}
|
||||
return format_to(
|
||||
ctx.out(), format, fmt::arg("coefficient", fraction),
|
||||
return fmt::format_to(
|
||||
ctx.out(), fmt::runtime(format), fmt::arg("coefficient", fraction),
|
||||
fmt::arg("number_width", number_width),
|
||||
fmt::arg("prefix", std::string() + units[pow] + ((s.binary_ && pow) ? "i" : "")),
|
||||
fmt::arg("unit", s.unit_),
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <date/tz.h>
|
||||
#include <fmt/format.h>
|
||||
|
||||
namespace waybar {
|
||||
|
||||
struct waybar_time {
|
||||
std::locale locale;
|
||||
date::zoned_seconds ztime;
|
||||
};
|
||||
|
||||
} // namespace waybar
|
||||
|
||||
template <>
|
||||
struct fmt::formatter<waybar::waybar_time> {
|
||||
std::string_view specs;
|
||||
|
||||
template <typename ParseContext>
|
||||
constexpr auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
auto it = ctx.begin();
|
||||
if (it != ctx.end() && *it == ':') {
|
||||
++it;
|
||||
}
|
||||
auto end = it;
|
||||
while (end != ctx.end() && *end != '}') {
|
||||
++end;
|
||||
}
|
||||
if (end != it) {
|
||||
specs = {it, std::string_view::size_type(end - it)};
|
||||
}
|
||||
return end;
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const waybar::waybar_time& t, FormatContext& ctx) {
|
||||
return format_to(ctx.out(), "{}", date::format(t.locale, fmt::to_string(specs), t.ztime));
|
||||
}
|
||||
};
|
|
@ -58,16 +58,25 @@ The *backlight* module displays the current backlight level.
|
|||
|
||||
*on-scroll-up*: ++
|
||||
typeof: string ++
|
||||
Command to execute when performing a scroll up on the module.
|
||||
Command to execute when performing a scroll up on the module. This replaces the default behaviour of brightness control.
|
||||
|
||||
*on-scroll-down*: ++
|
||||
typeof: string
|
||||
Command to execute when performing a scroll down on the module.
|
||||
Command to execute when performing a scroll down on the module. This replaces the default behaviour of brightness control.
|
||||
|
||||
*smooth-scrolling-threshold*: ++
|
||||
typeof: double
|
||||
Threshold to be used when scrolling.
|
||||
|
||||
*reverse-scrolling*: ++
|
||||
typeof: bool ++
|
||||
Option to reverse the scroll direction.
|
||||
|
||||
*scroll-step*: ++
|
||||
typeof: float ++
|
||||
default: 1.0 ++
|
||||
The speed in which to change the brightness when scrolling.
|
||||
|
||||
# EXAMPLE:
|
||||
|
||||
```
|
||||
|
|
|
@ -23,7 +23,7 @@ Addressed by *hyprland/language*
|
|||
|
||||
*keyboard-name*: ++
|
||||
typeof: string ++
|
||||
Specifies which keyboard to use from hyprctl devices output. Using the option that begins with "AT Translated set..." is recommended.
|
||||
Specifies which keyboard to use from hyprctl devices output. Using the option that begins with "at-translated-set..." is recommended.
|
||||
|
||||
|
||||
|
||||
|
@ -32,9 +32,9 @@ Addressed by *hyprland/language*
|
|||
```
|
||||
"hyprland/language": {
|
||||
"format": "Lang: {}"
|
||||
"format-us": "AMERICA, HELL YEAH!" // For American English
|
||||
"format-tr": "As bayrakları" // For Turkish
|
||||
"keyboard-name": "AT Translated Set 2 keyboard"
|
||||
"format-en": "AMERICA, HELL YEAH!"
|
||||
"format-tr": "As bayrakları"
|
||||
"keyboard-name": "at-translated-set-2-keyboard"
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
waybar-hyprland-submap(5)
|
||||
|
||||
# NAME
|
||||
|
||||
waybar - hyprland submap module
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
The *submap* module displays the currently active submap similar to *sway/mode*.
|
||||
|
||||
# CONFIGURATION
|
||||
|
||||
Addressed by *hyprland/submap*
|
||||
|
||||
*format*: ++
|
||||
typeof: string ++
|
||||
default: {} ++
|
||||
The format, how information should be displayed. On {} the currently active submap is displayed.
|
||||
|
||||
*rotate*: ++
|
||||
typeof: integer ++
|
||||
Positive value to rotate the text label.
|
||||
|
||||
*max-length*: ++
|
||||
typeof: integer ++
|
||||
The maximum length in character the module should display.
|
||||
|
||||
*min-length*: ++
|
||||
typeof: integer ++
|
||||
The minimum length in characters the module should take up.
|
||||
|
||||
*align*: ++
|
||||
typeof: float ++
|
||||
The alignment of the text, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text.
|
||||
|
||||
*on-click*: ++
|
||||
typeof: string ++
|
||||
Command to execute when clicked on the module.
|
||||
|
||||
*on-click-middle*: ++
|
||||
typeof: string ++
|
||||
Command to execute when middle-clicked on the module using mousewheel.
|
||||
|
||||
*on-click-right*: ++
|
||||
typeof: string ++
|
||||
Command to execute when you right clicked on the module.
|
||||
|
||||
*on-update*: ++
|
||||
typeof: string ++
|
||||
Command to execute when the module is updated.
|
||||
|
||||
*on-scroll-up*: ++
|
||||
typeof: string ++
|
||||
Command to execute when scrolling up on the module.
|
||||
|
||||
*on-scroll-down*: ++
|
||||
typeof: string ++
|
||||
Command to execute when scrolling down on the module.
|
||||
|
||||
*smooth-scrolling-threshold*: ++
|
||||
typeof: double ++
|
||||
Threshold to be used when scrolling.
|
||||
|
||||
*tooltip*: ++
|
||||
typeof: bool ++
|
||||
default: true ++
|
||||
Option to disable tooltip on hover.
|
||||
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
```
|
||||
"hyprland/submap": {
|
||||
"format": "✌️ {}",
|
||||
"max-length": 8,
|
||||
"tooltip": false
|
||||
}
|
||||
```
|
||||
|
||||
# STYLE
|
||||
|
||||
- *#submap*
|
|
@ -1,4 +1,4 @@
|
|||
waybar-custom(5)
|
||||
waybar-image(5)
|
||||
|
||||
# NAME
|
||||
|
||||
|
@ -10,12 +10,13 @@ The *image* module displays an image from a path.
|
|||
|
||||
# CONFIGURATION
|
||||
|
||||
Addressed by *custom/<name>*
|
||||
|
||||
*path*: ++
|
||||
typeof: string ++
|
||||
The path to the image.
|
||||
|
||||
*exec*: ++
|
||||
typeof: string ++
|
||||
The path to the script, which should return image path file
|
||||
it will only execute if the path is not set
|
||||
*size*: ++
|
||||
typeof: integer ++
|
||||
The width/height to render the image.
|
||||
|
@ -58,15 +59,15 @@ Addressed by *custom/<name>*
|
|||
|
||||
# EXAMPLES
|
||||
|
||||
## Spotify:
|
||||
|
||||
## mpd:
|
||||
|
||||
```
|
||||
"image/album-art": {
|
||||
"image#album-art": {
|
||||
"path": "/tmp/mpd_art",
|
||||
"size": 32,
|
||||
"interval": 5,
|
||||
"on-click": "mpc toggle"
|
||||
}
|
||||
```
|
||||
|
||||
# STYLE
|
||||
|
||||
- *#image*
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
waybar-mpris(5)
|
||||
|
||||
# NAME
|
||||
|
||||
waybar - MPRIS module
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
The *mpris* module displays currently playing media via libplayerctl.
|
||||
|
||||
# CONFIGURATION
|
||||
|
||||
*player*: ++
|
||||
typeof: string ++
|
||||
default: playerctld ++
|
||||
Name of the MPRIS player to attach to. Using the default value always
|
||||
follows the currenly active player.
|
||||
|
||||
*ignored-players*: ++
|
||||
typeof: []string ++
|
||||
Ignore updates of the listed players, when using playerctld.
|
||||
|
||||
*interval*: ++
|
||||
typeof: integer ++
|
||||
Refresh MPRIS information on a timer.
|
||||
|
||||
*format*: ++
|
||||
typeof: string ++
|
||||
default: {player} ({status}) {dynamic} ++
|
||||
The text format.
|
||||
|
||||
*format-[status]*: ++
|
||||
typeof: string ++
|
||||
The status-specific text format.
|
||||
|
||||
*on-click*: ++
|
||||
typeof: string ++
|
||||
default: play-pause ++
|
||||
Overwrite default action toggles.
|
||||
|
||||
*on-middle-click*: ++
|
||||
typeof: string ++
|
||||
default: previous track ++
|
||||
Overwrite default action toggles.
|
||||
|
||||
*on-right-click*: ++
|
||||
typeof: string ++
|
||||
default: next track ++
|
||||
Overwrite default action toggles.
|
||||
|
||||
*player-icons*: ++
|
||||
typeof: map[string]string
|
||||
Allows setting _{player-icon}_ based on player-name property.
|
||||
|
||||
*status-icons*: ++
|
||||
typeof: map[string]string
|
||||
Allows setting _{status-icon}_ based on player status (playing, paused,
|
||||
stopped).
|
||||
|
||||
|
||||
# FORMAT REPLACEMENTS
|
||||
|
||||
*{player}*: The name of the current media player
|
||||
|
||||
*{status}*: The current status (playing, paused, stopped)
|
||||
|
||||
*{artist}*: The artist of the current track
|
||||
|
||||
*{album}*: The album title of the current track
|
||||
|
||||
*{title}*: The title of the current track
|
||||
|
||||
*{length}*: Length of the track, formatted as HH:MM:SS
|
||||
|
||||
*{dynamic}*: Use _{artist}_, _{album}_, _{title}_ and _{length}_, automatically omit++
|
||||
empty values
|
||||
|
||||
*{player-icon}*: Chooses an icon from _player-icons_ based on _{player}_
|
||||
|
||||
*{status-icon}*: Chooses an icon from _status-icons_ based on _{status}_
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
```
|
||||
"mpris": {
|
||||
"format": "DEFAULT: {player_icon} {dynamic}",
|
||||
"format-paused": "DEFAULT: {status_icon} <i>{dynamic}</i>",
|
||||
"player-icons": {
|
||||
"default": "▶",
|
||||
"mpv": "🎵"
|
||||
},
|
||||
"status-icons": {
|
||||
"paused": "⏸"
|
||||
},
|
||||
// "ignored-players": ["firefox"]
|
||||
}
|
||||
```
|
||||
|
||||
# STYLE
|
||||
|
||||
- *#mpris*
|
||||
- *#mpris.${status}*
|
||||
- *#mpris.${player}*
|
|
@ -66,6 +66,25 @@ Addressed by *sway/window*
|
|||
default: true ++
|
||||
Option to disable tooltip on hover.
|
||||
|
||||
*all-outputs*: ++
|
||||
typeof: bool ++
|
||||
default: false ++
|
||||
Option to show the focused window along with its workspace styles on all outputs.
|
||||
|
||||
*offscreen-css*: ++
|
||||
typeof: bool ++
|
||||
default: false ++
|
||||
Only effective when all-outputs is true. Adds style according to present windows on unfocused outputs instead of showing the focused window and style.
|
||||
|
||||
*offscreen-css-text*: ++
|
||||
typeof: string ++
|
||||
Only effective when both all-outputs and offscreen-style are true. On screens currently not focused, show the given text along with that workspaces styles.
|
||||
|
||||
*show-focused-workspace-name*: ++
|
||||
typeof: bool ++
|
||||
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.
|
||||
|
||||
*rewrite*: ++
|
||||
typeof: object ++
|
||||
Rules to rewrite window title. See *rewrite rules*.
|
||||
|
@ -117,6 +136,10 @@ Invalid expressions (e.g., mismatched parentheses) are skipped.
|
|||
# STYLE
|
||||
|
||||
- *#window*
|
||||
- *window#waybar.empty* When no windows is in the workspace
|
||||
- *window#waybar.solo* When one window is in the workspace
|
||||
- *window#waybar.empty* When no windows are in the workspace, or screen is not focused and offscreen-css option is not set
|
||||
- *window#waybar.solo* When one tiled window is in the workspace
|
||||
- *window#waybar.floating* When there are only floating windows in the workspace
|
||||
- *window#waybar.stacked* When there is more than one window in the workspace and the workspace layout is stacked
|
||||
- *window#waybar.tabbed* When there is more than one window in the workspace and the workspace layout is tabbed
|
||||
- *window#waybar.tiled* When there is more than one window in the workspace and the workspace layout is splith or splitv
|
||||
- *window#waybar.<app_id>* Where *app_id* is the app_id or *instance* name like (*chromium*) of the only window in the workspace
|
||||
|
|
|
@ -73,6 +73,10 @@ Addressed by *sway/workspaces*
|
|||
typeof: bool ++
|
||||
Whether to disable *workspace_auto_back_and_forth* when clicking on workspaces. If this is set to *true*, clicking on a workspace you are already on won't do anything, even if *workspace_auto_back_and_forth* is enabled in the Sway configuration.
|
||||
|
||||
*alphabetical_sort*: ++
|
||||
typeof: bool ++
|
||||
Whether to sort workspaces alphabetically. Please note this can make "swaymsg workspace prev/next" move to workspaces inconsistent with the ordering shown in Waybar.
|
||||
|
||||
# FORMAT REPLACEMENTS
|
||||
|
||||
*{value}*: Name of the workspace, as defined by sway.
|
||||
|
|
|
@ -263,9 +263,11 @@ A module group is defined by specifying a module named "group/some-group-name".
|
|||
- *waybar-custom(5)*
|
||||
- *waybar-disk(5)*
|
||||
- *waybar-idle-inhibitor(5)*
|
||||
- *waybar-image(5)*
|
||||
- *waybar-keyboard-state(5)*
|
||||
- *waybar-memory(5)*
|
||||
- *waybar-mpd(5)*
|
||||
- *waybar-mpris(5)*
|
||||
- *waybar-network(5)*
|
||||
- *waybar-pulseaudio(5)*
|
||||
- *waybar-river-mode(5)*
|
||||
|
@ -276,6 +278,7 @@ A module group is defined by specifying a module named "group/some-group-name".
|
|||
- *waybar-sway-scratchpad(5)*
|
||||
- *waybar-sway-window(5)*
|
||||
- *waybar-sway-workspaces(5)*
|
||||
- *waybar-wireplumber(5)*
|
||||
- *waybar-wlr-taskbar(5)*
|
||||
- *waybar-wlr-workspaces(5)*
|
||||
- *waybar-temperature(5)*
|
||||
|
|
33
meson.build
33
meson.build
|
@ -1,6 +1,6 @@
|
|||
project(
|
||||
'waybar', 'cpp', 'c',
|
||||
version: '0.9.16',
|
||||
version: '0.9.17',
|
||||
license: 'MIT',
|
||||
meson_version: '>= 0.49.0',
|
||||
default_options : [
|
||||
|
@ -86,7 +86,10 @@ wayland_cursor = dependency('wayland-cursor')
|
|||
wayland_protos = dependency('wayland-protocols')
|
||||
gtkmm = dependency('gtkmm-3.0', version : ['>=3.22.0'])
|
||||
dbusmenu_gtk = dependency('dbusmenu-gtk3-0.4', required: get_option('dbusmenu-gtk'))
|
||||
giounix = dependency('gio-unix-2.0', required: (get_option('dbusmenu-gtk').enabled() or get_option('logind').enabled() or get_option('upower_glib').enabled()))
|
||||
giounix = dependency('gio-unix-2.0', required: (get_option('dbusmenu-gtk').enabled() or
|
||||
get_option('logind').enabled() or
|
||||
get_option('upower_glib').enabled() or
|
||||
get_option('mpris').enabled()))
|
||||
jsoncpp = dependency('jsoncpp', version : ['>=1.9.2'], fallback : ['jsoncpp', 'jsoncpp_dep'])
|
||||
sigcpp = dependency('sigc++-2.0')
|
||||
libinotify = dependency('libinotify', required: false)
|
||||
|
@ -95,6 +98,7 @@ libinput = dependency('libinput', required: get_option('libinput'))
|
|||
libnl = dependency('libnl-3.0', required: get_option('libnl'))
|
||||
libnlgen = dependency('libnl-genl-3.0', required: get_option('libnl'))
|
||||
upower_glib = dependency('upower-glib', required: get_option('upower_glib'))
|
||||
playerctl = dependency('playerctl', version : ['>=2.0.0'], required: get_option('mpris'))
|
||||
libpulse = dependency('libpulse', required: get_option('pulseaudio'))
|
||||
libudev = dependency('libudev', required: get_option('libudev'))
|
||||
libevdev = dependency('libevdev', required: get_option('libevdev'))
|
||||
|
@ -119,11 +123,18 @@ gtk_layer_shell = dependency('gtk-layer-shell-0',
|
|||
required: get_option('gtk-layer-shell'),
|
||||
fallback : ['gtk-layer-shell', 'gtk_layer_shell_dep'])
|
||||
systemd = dependency('systemd', required: get_option('systemd'))
|
||||
tz_dep = dependency('date',
|
||||
|
||||
cpp_lib_chrono = compiler.compute_int('__cpp_lib_chrono', prefix : '#include <chrono>')
|
||||
have_chrono_timezones = cpp_lib_chrono >= 201907
|
||||
if have_chrono_timezones
|
||||
tz_dep = declare_dependency()
|
||||
else
|
||||
tz_dep = dependency('date',
|
||||
required: false,
|
||||
default_options : [ 'use_system_tzdb=true' ],
|
||||
modules : [ 'date::date', 'date::date-tz' ],
|
||||
fallback: [ 'date', 'tz_dep' ])
|
||||
endif
|
||||
|
||||
prefix = get_option('prefix')
|
||||
sysconfdir = get_option('sysconfdir')
|
||||
|
@ -220,6 +231,7 @@ if true
|
|||
src_files += 'src/modules/hyprland/backend.cpp'
|
||||
src_files += 'src/modules/hyprland/window.cpp'
|
||||
src_files += 'src/modules/hyprland/language.cpp'
|
||||
src_files += 'src/modules/hyprland/submap.cpp'
|
||||
endif
|
||||
|
||||
if libnl.found() and libnlgen.found()
|
||||
|
@ -238,6 +250,11 @@ if (upower_glib.found() and giounix.found() and not get_option('logind').disable
|
|||
src_files += 'src/modules/upower/upower_tooltip.cpp'
|
||||
endif
|
||||
|
||||
if (playerctl.found() and giounix.found() and not get_option('logind').disabled())
|
||||
add_project_arguments('-DHAVE_MPRIS', language: 'cpp')
|
||||
src_files += 'src/modules/mpris/mpris.cpp'
|
||||
endif
|
||||
|
||||
if libpulse.found()
|
||||
add_project_arguments('-DHAVE_LIBPULSE', language: 'cpp')
|
||||
src_files += 'src/modules/pulseaudio.cpp'
|
||||
|
@ -302,7 +319,10 @@ if get_option('rfkill').enabled() and is_linux
|
|||
)
|
||||
endif
|
||||
|
||||
if tz_dep.found()
|
||||
if have_chrono_timezones
|
||||
add_project_arguments('-DHAVE_CHRONO_TIMEZONES', language: 'cpp')
|
||||
src_files += 'src/modules/clock.cpp'
|
||||
elif tz_dep.found()
|
||||
add_project_arguments('-DHAVE_LIBDATE', language: 'cpp')
|
||||
src_files += 'src/modules/clock.cpp'
|
||||
else
|
||||
|
@ -334,6 +354,7 @@ executable(
|
|||
libnl,
|
||||
libnlgen,
|
||||
upower_glib,
|
||||
playerctl,
|
||||
libpulse,
|
||||
libjack,
|
||||
libwireplumber,
|
||||
|
@ -384,9 +405,11 @@ if scdoc.found()
|
|||
'waybar-disk.5.scd',
|
||||
'waybar-gamemode.5.scd',
|
||||
'waybar-idle-inhibitor.5.scd',
|
||||
'waybar-image.5.scd',
|
||||
'waybar-keyboard-state.5.scd',
|
||||
'waybar-memory.5.scd',
|
||||
'waybar-mpd.5.scd',
|
||||
'waybar-mpris.5.scd',
|
||||
'waybar-network.5.scd',
|
||||
'waybar-pulseaudio.5.scd',
|
||||
'waybar-river-mode.5.scd',
|
||||
|
@ -435,7 +458,7 @@ endif
|
|||
|
||||
catch2 = dependency(
|
||||
'catch2',
|
||||
version: '>=3.0.0',
|
||||
version: '>=2.0.0',
|
||||
fallback: ['catch2', 'catch2_dep'],
|
||||
required: get_option('tests'),
|
||||
)
|
||||
|
|
|
@ -5,6 +5,7 @@ option('libudev', type: 'feature', value: 'auto', description: 'Enable libudev s
|
|||
option('libevdev', type: 'feature', value: 'auto', description: 'Enable libevdev support for evdev related features')
|
||||
option('pulseaudio', type: 'feature', value: 'auto', description: 'Enable support for pulseaudio')
|
||||
option('upower_glib', type: 'feature', value: 'auto', description: 'Enable support for upower')
|
||||
option('mpris', type: 'feature', value: 'auto', description: 'Enable support for mpris')
|
||||
option('systemd', type: 'feature', value: 'auto', description: 'Install systemd user service unit')
|
||||
option('dbusmenu-gtk', type: 'feature', value: 'auto', description: 'Enable support for tray')
|
||||
option('man-pages', type: 'feature', value: 'auto', description: 'Generate and install man pages')
|
||||
|
|
|
@ -0,0 +1,139 @@
|
|||
{ lib
|
||||
, stdenv
|
||||
, fetchFromGitHub
|
||||
, meson
|
||||
, pkg-config
|
||||
, ninja
|
||||
, wrapGAppsHook
|
||||
, wayland
|
||||
, wlroots
|
||||
, gtkmm3
|
||||
, libsigcxx
|
||||
, jsoncpp
|
||||
, scdoc
|
||||
, spdlog
|
||||
, gtk-layer-shell
|
||||
, howard-hinnant-date
|
||||
, libinotify-kqueue
|
||||
, libxkbcommon
|
||||
, evdevSupport ? true
|
||||
, libevdev
|
||||
, inputSupport ? true
|
||||
, libinput
|
||||
, jackSupport ? true
|
||||
, libjack2
|
||||
, mpdSupport ? true
|
||||
, libmpdclient
|
||||
, nlSupport ? true
|
||||
, libnl
|
||||
, pulseSupport ? true
|
||||
, libpulseaudio
|
||||
, rfkillSupport ? true
|
||||
, runTests ? true
|
||||
, catch2_3
|
||||
, sndioSupport ? true
|
||||
, sndio
|
||||
, swaySupport ? true
|
||||
, sway
|
||||
, traySupport ? true
|
||||
, libdbusmenu-gtk3
|
||||
, udevSupport ? true
|
||||
, udev
|
||||
, upowerSupport ? true
|
||||
, upower
|
||||
, wireplumberSupport ? true
|
||||
, wireplumber
|
||||
, withMediaPlayer ? false
|
||||
, glib
|
||||
, gobject-introspection
|
||||
, python3
|
||||
, playerctl
|
||||
, version
|
||||
}:
|
||||
|
||||
stdenv.mkDerivation rec {
|
||||
pname = "waybar";
|
||||
inherit version;
|
||||
# version = "0.9.16";
|
||||
|
||||
src = lib.cleanSourceWith {
|
||||
filter = name: type:
|
||||
let
|
||||
baseName = baseNameOf (toString name);
|
||||
in
|
||||
! (
|
||||
lib.hasSuffix ".nix" baseName
|
||||
);
|
||||
src = lib.cleanSource ../.;
|
||||
};
|
||||
|
||||
nativeBuildInputs = [
|
||||
meson
|
||||
ninja
|
||||
pkg-config
|
||||
scdoc
|
||||
wrapGAppsHook
|
||||
] ++ lib.optional withMediaPlayer gobject-introspection;
|
||||
|
||||
propagatedBuildInputs = lib.optionals withMediaPlayer [
|
||||
glib
|
||||
playerctl
|
||||
python3.pkgs.pygobject3
|
||||
];
|
||||
strictDeps = false;
|
||||
|
||||
buildInputs = with lib;
|
||||
[ wayland wlroots gtkmm3 libsigcxx jsoncpp spdlog gtk-layer-shell howard-hinnant-date libxkbcommon ]
|
||||
++ optional (!stdenv.isLinux) libinotify-kqueue
|
||||
++ optional evdevSupport libevdev
|
||||
++ optional inputSupport libinput
|
||||
++ optional jackSupport libjack2
|
||||
++ optional mpdSupport libmpdclient
|
||||
++ optional nlSupport libnl
|
||||
++ optional pulseSupport libpulseaudio
|
||||
++ optional sndioSupport sndio
|
||||
++ optional swaySupport sway
|
||||
++ optional traySupport libdbusmenu-gtk3
|
||||
++ optional udevSupport udev
|
||||
++ optional upowerSupport upower
|
||||
++ optional wireplumberSupport wireplumber;
|
||||
|
||||
checkInputs = [ catch2_3 ];
|
||||
doCheck = runTests;
|
||||
|
||||
mesonFlags = (lib.mapAttrsToList
|
||||
(option: enable: "-D${option}=${if enable then "enabled" else "disabled"}")
|
||||
{
|
||||
dbusmenu-gtk = traySupport;
|
||||
jack = jackSupport;
|
||||
libinput = inputSupport;
|
||||
libnl = nlSupport;
|
||||
libudev = udevSupport;
|
||||
mpd = mpdSupport;
|
||||
pulseaudio = pulseSupport;
|
||||
rfkill = rfkillSupport;
|
||||
sndio = sndioSupport;
|
||||
tests = runTests;
|
||||
upower_glib = upowerSupport;
|
||||
wireplumber = wireplumberSupport;
|
||||
}
|
||||
) ++ [
|
||||
"-Dsystemd=disabled"
|
||||
"-Dgtk-layer-shell=enabled"
|
||||
"-Dman-pages=enabled"
|
||||
];
|
||||
|
||||
preFixup = lib.optionalString withMediaPlayer ''
|
||||
cp $src/resources/custom_modules/mediaplayer.py $out/bin/waybar-mediaplayer.py
|
||||
wrapProgram $out/bin/waybar-mediaplayer.py \
|
||||
--prefix PYTHONPATH : "$PYTHONPATH:$out/${python3.sitePackages}"
|
||||
'';
|
||||
|
||||
meta = with lib; {
|
||||
description = "Highly customizable Wayland bar for Sway and Wlroots based compositors";
|
||||
license = licenses.mit;
|
||||
maintainers = with maintainers; [ FlorianFranzen minijackson synthetica lovesegfault ];
|
||||
platforms = platforms.unix;
|
||||
homepage = "https://github.com/alexays/waybar";
|
||||
};
|
||||
}
|
|
@ -725,10 +725,7 @@ void waybar::Bar::setupAltFormatKeyForModuleList(const char* module_list_name) {
|
|||
|
||||
void waybar::Bar::handleSignal(int signal) {
|
||||
for (auto& module : modules_all_) {
|
||||
auto* custom = dynamic_cast<waybar::modules::Custom*>(module.get());
|
||||
if (custom != nullptr) {
|
||||
custom->refresh(signal);
|
||||
}
|
||||
module->refresh(signal);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,11 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
|
|||
return new waybar::modules::upower::UPower(id, config_[name]);
|
||||
}
|
||||
#endif
|
||||
#ifdef HAVE_MPRIS
|
||||
if (ref == "mpris") {
|
||||
return new waybar::modules::mpris::Mpris(id, config_[name]);
|
||||
}
|
||||
#endif
|
||||
#ifdef HAVE_SWAY
|
||||
if (ref == "sway/mode") {
|
||||
return new waybar::modules::sway::Mode(id, config_[name]);
|
||||
|
@ -67,6 +72,9 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
|
|||
if (ref == "hyprland/language") {
|
||||
return new waybar::modules::hyprland::Language(id, bar_, config_[name]);
|
||||
}
|
||||
if (ref == "hyprland/submap") {
|
||||
return new waybar::modules::hyprland::Submap(id, bar_, config_[name]);
|
||||
}
|
||||
#endif
|
||||
if (ref == "idle_inhibitor") {
|
||||
return new waybar::modules::IdleInhibitor(id, bar_, config_[name]);
|
||||
|
@ -90,6 +98,9 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
|
|||
if (ref == "disk") {
|
||||
return new waybar::modules::Disk(id, config_[name]);
|
||||
}
|
||||
if (ref == "image") {
|
||||
return new waybar::modules::Image(id, config_[name]);
|
||||
}
|
||||
#ifdef HAVE_DBUSMENU
|
||||
if (ref == "tray") {
|
||||
return new waybar::modules::SNI::Tray(id, bar_, config_[name]);
|
||||
|
@ -148,8 +159,6 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
|
|||
}
|
||||
if (ref.compare(0, 7, "custom/") == 0 && ref.size() > 7) {
|
||||
return new waybar::modules::Custom(ref.substr(7), bar_, id, config_[name]);
|
||||
} else if (ref.compare(0, 6, "image/") == 0 && ref.size() > 6) {
|
||||
return new waybar::modules::Image(ref.substr(6), id, config_[name]);
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
auto err = fmt::format("Disabling module \"{}\", {}", name, e.what());
|
||||
|
|
|
@ -85,6 +85,12 @@ int main(int argc, char* argv[]) {
|
|||
waybar::Client::inst()->reset();
|
||||
});
|
||||
|
||||
std::signal(SIGINT, [](int /*signal*/) {
|
||||
spdlog::info("Quitting.");
|
||||
reload = false;
|
||||
waybar::Client::inst()->reset();
|
||||
});
|
||||
|
||||
for (int sig = SIGRTMIN + 1; sig <= SIGRTMAX; ++sig) {
|
||||
std::signal(sig, [](int sig) {
|
||||
for (auto& bar : waybar::Client::inst()->bars) {
|
||||
|
|
|
@ -48,13 +48,13 @@ struct UdevMonitorDeleter {
|
|||
|
||||
void check_eq(int rc, int expected, const char *message = "eq, rc was: ") {
|
||||
if (rc != expected) {
|
||||
throw std::runtime_error(fmt::format(message, rc));
|
||||
throw std::runtime_error(fmt::format(fmt::runtime(message), rc));
|
||||
}
|
||||
}
|
||||
|
||||
void check_neq(int rc, int bad_rc, const char *message = "neq, rc was: ") {
|
||||
if (rc == bad_rc) {
|
||||
throw std::runtime_error(fmt::format(message, rc));
|
||||
throw std::runtime_error(fmt::format(fmt::runtime(message), rc));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -62,7 +62,7 @@ void check0(int rc, const char *message = "rc wasn't 0") { check_eq(rc, 0, messa
|
|||
|
||||
void check_gte(int rc, int gte, const char *message = "rc was: ") {
|
||||
if (rc < gte) {
|
||||
throw std::runtime_error(fmt::format(message, rc));
|
||||
throw std::runtime_error(fmt::format(fmt::runtime(message), rc));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -106,6 +106,15 @@ waybar::modules::Backlight::Backlight(const std::string &id, const Json::Value &
|
|||
dp.emit();
|
||||
}
|
||||
|
||||
// Set up scroll handler
|
||||
event_box_.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);
|
||||
event_box_.signal_scroll_event().connect(sigc::mem_fun(*this, &Backlight::handleScroll));
|
||||
|
||||
// Connect to the login interface
|
||||
login_proxy_ = Gio::DBus::Proxy::create_for_bus_sync(
|
||||
Gio::DBus::BusType::BUS_TYPE_SYSTEM, "org.freedesktop.login1",
|
||||
"/org/freedesktop/login1/session/self", "org.freedesktop.login1.Session");
|
||||
|
||||
udev_thread_ = [this] {
|
||||
std::unique_ptr<udev, UdevDeleter> udev{udev_new()};
|
||||
check_nn(udev.get(), "Udev new failed");
|
||||
|
@ -181,7 +190,8 @@ auto waybar::modules::Backlight::update() -> void {
|
|||
event_box_.show();
|
||||
const uint8_t percent =
|
||||
best->get_max() == 0 ? 100 : round(best->get_actual() * 100.0f / best->get_max());
|
||||
label_.set_markup(fmt::format(format_, fmt::arg("percent", std::to_string(percent)),
|
||||
label_.set_markup(fmt::format(fmt::runtime(format_),
|
||||
fmt::arg("percent", std::to_string(percent)),
|
||||
fmt::arg("icon", getIcon(percent))));
|
||||
getState(percent);
|
||||
} else {
|
||||
|
@ -263,3 +273,71 @@ void waybar::modules::Backlight::enumerate_devices(ForwardIt first, ForwardIt la
|
|||
upsert_device(first, last, inserter, dev.get());
|
||||
}
|
||||
}
|
||||
|
||||
bool waybar::modules::Backlight::handleScroll(GdkEventScroll *e) {
|
||||
// Check if the user has set a custom command for scrolling
|
||||
if (config_["on-scroll-up"].isString() || config_["on-scroll-down"].isString()) {
|
||||
return AModule::handleScroll(e);
|
||||
}
|
||||
|
||||
// Fail fast if the proxy could not be initialized
|
||||
if (!login_proxy_) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check scroll direction
|
||||
auto dir = AModule::getScrollDir(e);
|
||||
if (dir == SCROLL_DIR::NONE) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (config_["reverse-scrolling"].asBool()) {
|
||||
if (dir == SCROLL_DIR::UP) {
|
||||
dir = SCROLL_DIR::DOWN;
|
||||
} else if (dir == SCROLL_DIR::DOWN) {
|
||||
dir = SCROLL_DIR::UP;
|
||||
}
|
||||
}
|
||||
|
||||
// Get scroll step
|
||||
double step = 1;
|
||||
|
||||
if (config_["scroll-step"].isDouble()) {
|
||||
step = config_["scroll-step"].asDouble();
|
||||
}
|
||||
|
||||
// Get the best device
|
||||
decltype(devices_) devices;
|
||||
{
|
||||
std::scoped_lock<std::mutex> lock(udev_thread_mutex_);
|
||||
devices = devices_;
|
||||
}
|
||||
const auto best = best_device(devices.cbegin(), devices.cend(), preferred_device_);
|
||||
|
||||
if (best == nullptr) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Compute the absolute step
|
||||
const auto abs_step = static_cast<int>(round(step * best->get_max() / 100.0f));
|
||||
|
||||
// Compute the new value
|
||||
int new_value = best->get_actual();
|
||||
|
||||
if (dir == SCROLL_DIR::UP) {
|
||||
new_value += abs_step;
|
||||
} else if (dir == SCROLL_DIR::DOWN) {
|
||||
new_value -= abs_step;
|
||||
}
|
||||
|
||||
// Clamp the value
|
||||
new_value = std::clamp(new_value, 0, best->get_max());
|
||||
|
||||
// Set the new value
|
||||
auto call_args = Glib::VariantContainerBase(
|
||||
g_variant_new("(ssu)", "backlight", std::string(best->name()).c_str(), new_value));
|
||||
|
||||
login_proxy_->call_sync("SetBrightness", call_args);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -505,11 +505,12 @@ const std::tuple<uint8_t, float, std::string, float> waybar::modules::Battery::g
|
|||
|
||||
float time_remaining{0.0f};
|
||||
if (status == "Discharging" && time_to_empty_now_exists) {
|
||||
if (time_to_empty_now != 0) time_remaining = (float)time_to_empty_now / 1000.0f;
|
||||
if (time_to_empty_now != 0) time_remaining = (float)time_to_empty_now / 3600.0f;
|
||||
} else if (status == "Discharging" && total_power_exists && total_energy_exists) {
|
||||
if (total_power != 0) time_remaining = (float)total_energy / total_power;
|
||||
} else if (status == "Charging" && time_to_full_now_exists) {
|
||||
if (time_to_full_now_exists && (time_to_full_now != 0)) time_remaining = -(float)time_to_full_now / 1000.0f;
|
||||
if (time_to_full_now_exists && (time_to_full_now != 0))
|
||||
time_remaining = -(float)time_to_full_now / 3600.0f;
|
||||
// If we've turned positive it means the battery is past 100% and so just report that as no
|
||||
// time remaining
|
||||
if (time_remaining > 0.0f) time_remaining = 0.0f;
|
||||
|
@ -603,7 +604,7 @@ const std::string waybar::modules::Battery::formatTimeRemaining(float hoursRemai
|
|||
format = config_["format-time"].asString();
|
||||
}
|
||||
std::string zero_pad_minutes = fmt::format("{:02d}", minutes);
|
||||
return fmt::format(format, fmt::arg("H", full_hours), fmt::arg("M", minutes),
|
||||
return fmt::format(fmt::runtime(format), fmt::arg("H", full_hours), fmt::arg("M", minutes),
|
||||
fmt::arg("m", zero_pad_minutes));
|
||||
}
|
||||
|
||||
|
@ -643,7 +644,8 @@ auto waybar::modules::Battery::update() -> void {
|
|||
} else if (config_["tooltip-format"].isString()) {
|
||||
tooltip_format = config_["tooltip-format"].asString();
|
||||
}
|
||||
label_.set_tooltip_text(fmt::format(tooltip_format, fmt::arg("timeTo", tooltip_text_default),
|
||||
label_.set_tooltip_text(fmt::format(fmt::runtime(tooltip_format),
|
||||
fmt::arg("timeTo", tooltip_text_default),
|
||||
fmt::arg("power", power), fmt::arg("capacity", capacity),
|
||||
fmt::arg("time", time_remaining_formatted)));
|
||||
}
|
||||
|
@ -664,9 +666,9 @@ auto waybar::modules::Battery::update() -> void {
|
|||
} else {
|
||||
event_box_.show();
|
||||
auto icons = std::vector<std::string>{status + "-" + state, status, state};
|
||||
label_.set_markup(fmt::format(format, fmt::arg("capacity", capacity), fmt::arg("power", power),
|
||||
fmt::arg("icon", getIcon(capacity, icons)),
|
||||
fmt::arg("time", time_remaining_formatted)));
|
||||
label_.set_markup(fmt::format(
|
||||
fmt::runtime(format), fmt::arg("capacity", capacity), fmt::arg("power", power),
|
||||
fmt::arg("icon", getIcon(capacity, icons)), fmt::arg("time", time_remaining_formatted)));
|
||||
}
|
||||
// Call parent update
|
||||
ALabel::update();
|
||||
|
|
|
@ -206,7 +206,8 @@ auto waybar::modules::Bluetooth::update() -> void {
|
|||
state_ = state;
|
||||
|
||||
label_.set_markup(fmt::format(
|
||||
format_, fmt::arg("status", state_), fmt::arg("num_connections", connected_devices_.size()),
|
||||
fmt::runtime(format_), fmt::arg("status", state_),
|
||||
fmt::arg("num_connections", connected_devices_.size()),
|
||||
fmt::arg("controller_address", cur_controller_.address),
|
||||
fmt::arg("controller_address_type", cur_controller_.address_type),
|
||||
fmt::arg("controller_alias", cur_controller_.alias),
|
||||
|
@ -234,7 +235,7 @@ auto waybar::modules::Bluetooth::update() -> void {
|
|||
enumerate_format = config_["tooltip-format-enumerate-connected"].asString();
|
||||
}
|
||||
ss << fmt::format(
|
||||
enumerate_format, fmt::arg("device_address", dev.address),
|
||||
fmt::runtime(enumerate_format), fmt::arg("device_address", dev.address),
|
||||
fmt::arg("device_address_type", dev.address_type),
|
||||
fmt::arg("device_alias", dev.alias), fmt::arg("icon", enumerate_icon),
|
||||
fmt::arg("device_battery_percentage", dev.battery_percentage.value_or(0)));
|
||||
|
@ -247,7 +248,7 @@ auto waybar::modules::Bluetooth::update() -> void {
|
|||
}
|
||||
}
|
||||
label_.set_tooltip_text(fmt::format(
|
||||
tooltip_format, fmt::arg("status", state_),
|
||||
fmt::runtime(tooltip_format), fmt::arg("status", state_),
|
||||
fmt::arg("num_connections", connected_devices_.size()),
|
||||
fmt::arg("controller_address", cur_controller_.address),
|
||||
fmt::arg("controller_address_type", cur_controller_.address_type),
|
||||
|
|
|
@ -5,18 +5,16 @@
|
|||
|
||||
#include <ctime>
|
||||
#include <iomanip>
|
||||
#include <regex>
|
||||
#include <sstream>
|
||||
#include <type_traits>
|
||||
|
||||
#include "util/ustring_clen.hpp"
|
||||
#include "util/waybar_time.hpp"
|
||||
#ifdef HAVE_LANGINFO_1STDAY
|
||||
#include <langinfo.h>
|
||||
#include <locale.h>
|
||||
#endif
|
||||
|
||||
using waybar::waybar_time;
|
||||
|
||||
waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
|
||||
: ALabel(config, "clock", id, "{:%H:%M}", 60, false, false, true),
|
||||
current_time_zone_idx_(0),
|
||||
|
@ -34,16 +32,10 @@ waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
|
|||
time_zones_.push_back(date::locate_zone(config_["timezone"].asString()));
|
||||
}
|
||||
|
||||
// If all timezones are parsed and no one is good, add nullptr to the timezones vector, to mark
|
||||
// that local time should be shown.
|
||||
// If all timezones are parsed and no one is good, add current time zone. nullptr in timezones
|
||||
// vector means that local time should be shown
|
||||
if (!time_zones_.size()) {
|
||||
time_zones_.push_back(nullptr);
|
||||
}
|
||||
|
||||
if (!is_timezone_fixed()) {
|
||||
spdlog::warn(
|
||||
"As using a timezone, some format args may be missing as the date library haven't got a "
|
||||
"release since 2018.");
|
||||
time_zones_.push_back(date::current_zone());
|
||||
}
|
||||
|
||||
// Check if a particular placeholder is present in the tooltip format, to know what to calculate
|
||||
|
@ -61,18 +53,94 @@ waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
|
|||
}
|
||||
}
|
||||
|
||||
// Calendar configuration
|
||||
if (is_calendar_in_tooltip_) {
|
||||
if (config_["on-scroll"][kCalendarPlaceholder].isInt()) {
|
||||
calendar_shift_init_ =
|
||||
date::months{config_["on-scroll"].get(kCalendarPlaceholder, 0).asInt()};
|
||||
if (config_[kCalendarPlaceholder]["weeks-pos"].isString()) {
|
||||
if (config_[kCalendarPlaceholder]["weeks-pos"].asString() == "left") {
|
||||
cldWPos_ = WeeksSide::LEFT;
|
||||
} else if (config_[kCalendarPlaceholder]["weeks-pos"].asString() == "right") {
|
||||
cldWPos_ = WeeksSide::RIGHT;
|
||||
}
|
||||
}
|
||||
if (config_[kCalendarPlaceholder]["format"]["months"].isString())
|
||||
fmtMap_.insert({0, config_[kCalendarPlaceholder]["format"]["months"].asString()});
|
||||
else
|
||||
fmtMap_.insert({0, "{}"});
|
||||
if (config_[kCalendarPlaceholder]["format"]["days"].isString())
|
||||
fmtMap_.insert({2, config_[kCalendarPlaceholder]["format"]["days"].asString()});
|
||||
else
|
||||
fmtMap_.insert({2, "{}"});
|
||||
if (config_[kCalendarPlaceholder]["format"]["weeks"].isString() &&
|
||||
cldWPos_ != WeeksSide::HIDDEN) {
|
||||
fmtMap_.insert(
|
||||
{4, std::regex_replace(config_[kCalendarPlaceholder]["format"]["weeks"].asString(),
|
||||
std::regex("\\{\\}"),
|
||||
(first_day_of_week() == date::Monday) ? "{:%W}" : "{:%U}")});
|
||||
Glib::ustring tmp{std::regex_replace(fmtMap_[4], std::regex("</?[^>]+>|\\{.*\\}"), "")};
|
||||
cldWnLen_ += tmp.size();
|
||||
} else {
|
||||
if (cldWPos_ != WeeksSide::HIDDEN)
|
||||
fmtMap_.insert({4, (first_day_of_week() == date::Monday) ? "{:%W}" : "{:%U}"});
|
||||
else
|
||||
cldWnLen_ = 0;
|
||||
}
|
||||
if (config_[kCalendarPlaceholder]["format"]["weekdays"].isString())
|
||||
fmtMap_.insert({1, config_[kCalendarPlaceholder]["format"]["weekdays"].asString()});
|
||||
else
|
||||
fmtMap_.insert({1, "{}"});
|
||||
if (config_[kCalendarPlaceholder]["format"]["today"].isString())
|
||||
fmtMap_.insert({3, config_[kCalendarPlaceholder]["format"]["today"].asString()});
|
||||
else
|
||||
fmtMap_.insert({3, "{}"});
|
||||
if (config_[kCalendarPlaceholder]["mode"].isString()) {
|
||||
const std::string cfgMode{(config_[kCalendarPlaceholder]["mode"].isString())
|
||||
? config_[kCalendarPlaceholder]["mode"].asString()
|
||||
: "month"};
|
||||
const std::map<std::string, const CldMode&> monthModes{{"month", CldMode::MONTH},
|
||||
{"year", CldMode::YEAR}};
|
||||
if (monthModes.find(cfgMode) != monthModes.end())
|
||||
cldMode_ = monthModes.at(cfgMode);
|
||||
else
|
||||
spdlog::warn(
|
||||
"Clock calendar configuration \"mode\"\"\" \"{0}\" is not recognized. Mode = \"month\" "
|
||||
"is using instead",
|
||||
cfgMode);
|
||||
}
|
||||
if (config_[kCalendarPlaceholder]["mode-mon-col"].isInt()) {
|
||||
cldMonCols_ = config_[kCalendarPlaceholder]["mode-mon-col"].asInt();
|
||||
if (cldMonCols_ == 0u || 12 % cldMonCols_ != 0u) {
|
||||
cldMonCols_ = 3u;
|
||||
spdlog::warn(
|
||||
"Clock calendar configuration \"mode-mon-col\" = {0} must be one of [1, 2, 3, 4, 6, "
|
||||
"12]. Value 3 is using instead",
|
||||
cldMonCols_);
|
||||
}
|
||||
} else
|
||||
cldMonCols_ = 1;
|
||||
if (config_[kCalendarPlaceholder]["on-scroll"].isInt()) {
|
||||
cldShift_ = date::months{config_[kCalendarPlaceholder]["on-scroll"].asInt()};
|
||||
event_box_.add_events(Gdk::LEAVE_NOTIFY_MASK);
|
||||
event_box_.signal_leave_notify_event().connect([this](GdkEventCrossing*) {
|
||||
cldCurrShift_ = date::months{0};
|
||||
return false;
|
||||
});
|
||||
}
|
||||
if (config_[kCalendarPlaceholder]["on-click-left"].isString()) {
|
||||
if (config_[kCalendarPlaceholder]["on-click-left"].asString() == "mode")
|
||||
eventMap_.insert({std::make_pair(1, GdkEventType::GDK_BUTTON_PRESS),
|
||||
&waybar::modules::Clock::cldModeSwitch});
|
||||
}
|
||||
if (config_[kCalendarPlaceholder]["on-click-right"].isString()) {
|
||||
if (config_[kCalendarPlaceholder]["on-click-right"].asString() == "mode")
|
||||
eventMap_.insert({std::make_pair(3, GdkEventType::GDK_BUTTON_PRESS),
|
||||
&waybar::modules::Clock::cldModeSwitch});
|
||||
}
|
||||
}
|
||||
|
||||
if (config_["locale"].isString()) {
|
||||
if (config_["locale"].isString())
|
||||
locale_ = std::locale(config_["locale"].asString());
|
||||
} else {
|
||||
else
|
||||
locale_ = std::locale("");
|
||||
}
|
||||
|
||||
thread_ = [this] {
|
||||
dp.emit();
|
||||
|
@ -94,18 +162,22 @@ bool waybar::modules::Clock::is_timezone_fixed() {
|
|||
}
|
||||
|
||||
auto waybar::modules::Clock::update() -> void {
|
||||
auto time_zone = current_timezone();
|
||||
const auto* time_zone = current_timezone();
|
||||
auto now = std::chrono::system_clock::now();
|
||||
waybar_time wtime = {locale_, date::make_zoned(time_zone, date::floor<std::chrono::seconds>(now) +
|
||||
calendar_shift_)};
|
||||
std::string text = "";
|
||||
auto ztime = date::zoned_time{time_zone, date::floor<std::chrono::seconds>(now)};
|
||||
|
||||
auto shifted_date = date::year_month_day{date::floor<date::days>(now)} + cldCurrShift_;
|
||||
auto now_shifted = date::sys_days{shifted_date} + (now - date::floor<date::days>(now));
|
||||
auto shifted_ztime = date::zoned_time{time_zone, date::floor<std::chrono::seconds>(now_shifted)};
|
||||
|
||||
std::string text{""};
|
||||
if (!is_timezone_fixed()) {
|
||||
// As date dep is not fully compatible, prefer fmt
|
||||
tzset();
|
||||
auto localtime = fmt::localtime(std::chrono::system_clock::to_time_t(now));
|
||||
text = fmt::format(locale_, format_, localtime);
|
||||
text = fmt::format(locale_, fmt::runtime(format_), localtime);
|
||||
} else {
|
||||
text = fmt::format(format_, wtime);
|
||||
text = fmt::format(locale_, fmt::runtime(format_), ztime);
|
||||
}
|
||||
label_.set_markup(text);
|
||||
|
||||
|
@ -114,14 +186,14 @@ auto waybar::modules::Clock::update() -> void {
|
|||
std::string calendar_lines{""};
|
||||
std::string timezoned_time_lines{""};
|
||||
if (is_calendar_in_tooltip_) {
|
||||
calendar_lines = calendar_text(wtime);
|
||||
calendar_lines = get_calendar(ztime, shifted_ztime);
|
||||
}
|
||||
if (is_timezoned_list_in_tooltip_) {
|
||||
timezoned_time_lines = timezones_text(&now);
|
||||
}
|
||||
auto tooltip_format = config_["tooltip-format"].asString();
|
||||
text =
|
||||
fmt::format(tooltip_format, wtime, fmt::arg(kCalendarPlaceholder.c_str(), calendar_lines),
|
||||
text = fmt::format(locale_, fmt::runtime(tooltip_format), shifted_ztime,
|
||||
fmt::arg(kCalendarPlaceholder.c_str(), calendar_lines),
|
||||
fmt::arg(KTimezonedTimeListPlaceholder.c_str(), timezoned_time_lines));
|
||||
label_.set_tooltip_markup(text);
|
||||
}
|
||||
|
@ -131,6 +203,21 @@ auto waybar::modules::Clock::update() -> void {
|
|||
ALabel::update();
|
||||
}
|
||||
|
||||
bool waybar::modules::Clock::handleToggle(GdkEventButton* const& e) {
|
||||
const std::map<std::pair<uint, GdkEventType>, void (waybar::modules::Clock::*)()>::const_iterator&
|
||||
rec{eventMap_.find(std::pair(e->button, e->type))};
|
||||
|
||||
const auto callMethod{(rec != eventMap_.cend()) ? rec->second : nullptr};
|
||||
|
||||
if (callMethod) {
|
||||
(this->*callMethod)();
|
||||
} else
|
||||
return ALabel::handleToggle(e);
|
||||
|
||||
update();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool waybar::modules::Clock::handleScroll(GdkEventScroll* e) {
|
||||
// defer to user commands if set
|
||||
if (config_["on-scroll-up"].isString() || config_["on-scroll-down"].isString()) {
|
||||
|
@ -140,11 +227,11 @@ bool waybar::modules::Clock::handleScroll(GdkEventScroll* e) {
|
|||
auto dir = AModule::getScrollDir(e);
|
||||
|
||||
// Shift calendar date
|
||||
if (calendar_shift_init_.count() != 0) {
|
||||
if (cldShift_.count() != 0) {
|
||||
if (dir == SCROLL_DIR::UP)
|
||||
calendar_shift_ += calendar_shift_init_;
|
||||
cldCurrShift_ += ((cldMode_ == CldMode::YEAR) ? 12 : 1) * cldShift_;
|
||||
else
|
||||
calendar_shift_ -= calendar_shift_init_;
|
||||
cldCurrShift_ -= ((cldMode_ == CldMode::YEAR) ? 12 : 1) * cldShift_;
|
||||
} else {
|
||||
// Change time zone
|
||||
if (dir != SCROLL_DIR::UP && dir != SCROLL_DIR::DOWN) {
|
||||
|
@ -168,135 +255,214 @@ bool waybar::modules::Clock::handleScroll(GdkEventScroll* e) {
|
|||
return true;
|
||||
}
|
||||
|
||||
auto waybar::modules::Clock::calendar_text(const waybar_time& wtime) -> std::string {
|
||||
const auto daypoint = date::floor<date::days>(wtime.ztime.get_local_time());
|
||||
const auto ymd{date::year_month_day{daypoint}};
|
||||
|
||||
if (calendar_cached_ymd_ == ymd) {
|
||||
return calendar_cached_text_;
|
||||
}
|
||||
|
||||
const auto curr_day{(calendar_shift_init_.count() != 0 && calendar_shift_.count() != 0)
|
||||
? date::day{0}
|
||||
: ymd.day()};
|
||||
const date::year_month ym{ymd.year(), ymd.month()};
|
||||
const auto weeks_format{config_["format-calendar-weeks"].isString()
|
||||
? config_["format-calendar-weeks"].asString()
|
||||
: ""};
|
||||
|
||||
std::stringstream os;
|
||||
|
||||
const date::weekday first_week_day = first_day_of_week();
|
||||
|
||||
enum class WeeksPlacement {
|
||||
LEFT,
|
||||
RIGHT,
|
||||
HIDDEN,
|
||||
};
|
||||
WeeksPlacement weeks_pos = WeeksPlacement::HIDDEN;
|
||||
|
||||
if (config_["calendar-weeks-pos"].isString()) {
|
||||
if (config_["calendar-weeks-pos"].asString() == "left") {
|
||||
weeks_pos = WeeksPlacement::LEFT;
|
||||
// Add paddings before the header
|
||||
os << std::string(4, ' ');
|
||||
} else if (config_["calendar-weeks-pos"].asString() == "right") {
|
||||
weeks_pos = WeeksPlacement::RIGHT;
|
||||
}
|
||||
}
|
||||
|
||||
weekdays_header(first_week_day, os);
|
||||
|
||||
// First week prefixed with spaces if needed.
|
||||
auto first_month_day = date::weekday(ym / 1);
|
||||
int empty_days = (first_week_day - first_month_day).count() + 1;
|
||||
date::sys_days last_week_day{static_cast<date::sys_days>(ym / 1) + date::days{7 - empty_days}};
|
||||
|
||||
if (first_week_day == date::Monday) {
|
||||
last_week_day -= date::days{1};
|
||||
}
|
||||
/* Print weeknumber on the left for the first row*/
|
||||
if (weeks_pos == WeeksPlacement::LEFT) {
|
||||
os << fmt::format(weeks_format, date::format("%U", last_week_day)) << ' ';
|
||||
last_week_day += date::weeks{1};
|
||||
}
|
||||
|
||||
if (empty_days > 0) {
|
||||
os << std::string(empty_days * 3 - 1, ' ');
|
||||
}
|
||||
const auto last_day = (ym / date::literals::last).day();
|
||||
auto weekday = first_month_day;
|
||||
for (auto d = date::day(1); d <= last_day; ++d, ++weekday) {
|
||||
if (weekday != first_week_day) {
|
||||
os << ' ';
|
||||
} else if (unsigned(d) != 1) {
|
||||
last_week_day -= date::days{1};
|
||||
if (weeks_pos == WeeksPlacement::RIGHT) {
|
||||
os << ' ';
|
||||
os << fmt::format(weeks_format, date::format("%U", last_week_day));
|
||||
}
|
||||
|
||||
os << "\n";
|
||||
|
||||
if (weeks_pos == WeeksPlacement::LEFT) {
|
||||
os << fmt::format(weeks_format, date::format("%U", last_week_day));
|
||||
os << ' ';
|
||||
}
|
||||
last_week_day += date::weeks{1} + date::days{1};
|
||||
}
|
||||
if (d == curr_day) {
|
||||
if (config_["today-format"].isString()) {
|
||||
auto today_format = config_["today-format"].asString();
|
||||
os << fmt::format(today_format, date::format("%e", d));
|
||||
} else {
|
||||
os << "<b><u>" << date::format("%e", d) << "</u></b>";
|
||||
}
|
||||
} else if (config_["format-calendar"].isString()) {
|
||||
os << fmt::format(config_["format-calendar"].asString(), date::format("%e", d));
|
||||
} else {
|
||||
os << date::format("%e", d);
|
||||
}
|
||||
/*Print weeks on the right when the endings with spaces*/
|
||||
if (weeks_pos == WeeksPlacement::RIGHT && d == last_day) {
|
||||
last_week_day -= date::days{1};
|
||||
empty_days = 6 - (weekday - first_week_day).count();
|
||||
os << std::string(empty_days * 3 + 1, ' ');
|
||||
os << fmt::format(weeks_format, date::format("%U", last_week_day));
|
||||
last_week_day += date::days{1};
|
||||
}
|
||||
}
|
||||
|
||||
auto result = os.str();
|
||||
calendar_cached_ymd_ = ymd;
|
||||
calendar_cached_text_ = result;
|
||||
return result;
|
||||
// The number of weeks in calendar month layout plus 1 more for calendar titles
|
||||
unsigned cldRowsInMonth(date::year_month const ym, date::weekday const firstdow) {
|
||||
using namespace date;
|
||||
return static_cast<unsigned>(
|
||||
ceil<weeks>((weekday{ym / 1} - firstdow) + ((ym / last).day() - day{0})).count()) +
|
||||
2;
|
||||
}
|
||||
|
||||
auto waybar::modules::Clock::weekdays_header(const date::weekday& first_week_day, std::ostream& os)
|
||||
-> void {
|
||||
std::stringstream res;
|
||||
auto wd = first_week_day;
|
||||
do {
|
||||
if (wd != first_week_day) {
|
||||
res << ' ';
|
||||
auto cldGetWeekForLine(date::year_month const ym, date::weekday const firstdow, unsigned const line)
|
||||
-> const date::year_month_weekday {
|
||||
unsigned index = line - 2;
|
||||
auto sd = date::sys_days{ym / 1};
|
||||
if (date::weekday{sd} == firstdow) ++index;
|
||||
auto ymdw = ym / firstdow[index];
|
||||
return ymdw;
|
||||
}
|
||||
|
||||
auto getCalendarLine(date::year_month_day const currDate, date::year_month const ym,
|
||||
unsigned const line, date::weekday const firstdow,
|
||||
const std::locale* const locale_) -> std::string {
|
||||
using namespace date::literals;
|
||||
std::ostringstream res;
|
||||
|
||||
switch (line) {
|
||||
case 0: {
|
||||
// Output month and year title
|
||||
res << date::format(*locale_, "%B %Y", ym);
|
||||
break;
|
||||
}
|
||||
Glib::ustring wd_ustring(date::format(locale_, "%a", wd));
|
||||
auto clen = ustring_clen(wd_ustring);
|
||||
auto wd_len = wd_ustring.length();
|
||||
case 1: {
|
||||
// Output weekday names title
|
||||
auto wd{firstdow};
|
||||
do {
|
||||
Glib::ustring wd_ustring{date::format(*locale_, "%a", wd)};
|
||||
auto clen{ustring_clen(wd_ustring)};
|
||||
auto wd_len{wd_ustring.length()};
|
||||
while (clen > 2) {
|
||||
wd_ustring = wd_ustring.substr(0, wd_len - 1);
|
||||
wd_len--;
|
||||
--wd_len;
|
||||
clen = ustring_clen(wd_ustring);
|
||||
}
|
||||
const std::string pad(2 - clen, ' ');
|
||||
res << pad << wd_ustring;
|
||||
} while (++wd != first_week_day);
|
||||
res << "\n";
|
||||
|
||||
if (config_["format-calendar-weekdays"].isString()) {
|
||||
os << fmt::format(config_["format-calendar-weekdays"].asString(), res.str());
|
||||
} else
|
||||
os << res.str();
|
||||
if (wd != firstdow) res << ' ';
|
||||
|
||||
res << pad << wd_ustring;
|
||||
} while (++wd != firstdow);
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
// Output first week prefixed with spaces if necessary
|
||||
auto wd = date::weekday{ym / 1};
|
||||
res << std::string(static_cast<unsigned>((wd - firstdow).count()) * 3, ' ');
|
||||
|
||||
if (currDate.year() != ym.year() || currDate.month() != ym.month() || currDate != ym / 1_d)
|
||||
res << date::format("%e", 1_d);
|
||||
else
|
||||
res << "{today}";
|
||||
|
||||
auto d = 2_d;
|
||||
|
||||
while (++wd != firstdow) {
|
||||
if (currDate.year() != ym.year() || currDate.month() != ym.month() || currDate != ym / d)
|
||||
res << date::format(" %e", d);
|
||||
else
|
||||
res << " {today}";
|
||||
|
||||
++d;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
// Output a non-first week:
|
||||
auto ymdw{cldGetWeekForLine(ym, firstdow, line)};
|
||||
if (ymdw.ok()) {
|
||||
auto d = date::year_month_day{ymdw}.day();
|
||||
auto const e = (ym / last).day();
|
||||
auto wd = firstdow;
|
||||
|
||||
if (currDate.year() != ym.year() || currDate.month() != ym.month() || currDate != ym / d)
|
||||
res << date::format("%e", d);
|
||||
else
|
||||
res << "{today}";
|
||||
|
||||
while (++wd != firstdow && ++d <= e) {
|
||||
if (currDate.year() != ym.year() || currDate.month() != ym.month() || currDate != ym / d)
|
||||
res << date::format(" %e", d);
|
||||
else
|
||||
res << " {today}";
|
||||
}
|
||||
// Append row with spaces if the week did not complete
|
||||
res << std::string(static_cast<unsigned>((firstdow - wd).count()) * 3, ' ');
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return res.str();
|
||||
}
|
||||
|
||||
auto waybar::modules::Clock::get_calendar(const date::zoned_seconds& now,
|
||||
const date::zoned_seconds& wtime) -> std::string {
|
||||
auto daypoint = date::floor<date::days>(wtime.get_local_time());
|
||||
const auto ymd{date::year_month_day{daypoint}};
|
||||
const auto ym{ymd.year() / ymd.month()};
|
||||
const auto y{ymd.year()};
|
||||
const auto firstdow = first_day_of_week();
|
||||
const auto maxRows{12 / cldMonCols_};
|
||||
std::ostringstream os;
|
||||
std::ostringstream tmp;
|
||||
// get currdate
|
||||
daypoint = date::floor<date::days>(now.get_local_time());
|
||||
const auto currDate{date::year_month_day{daypoint}};
|
||||
|
||||
if (cldMode_ == CldMode::YEAR) {
|
||||
if (y / date::month{1} / 1 == cldYearShift_)
|
||||
return cldYearCached_;
|
||||
else
|
||||
cldYearShift_ = y / date::month{1} / 1;
|
||||
}
|
||||
if (cldMode_ == CldMode::MONTH) {
|
||||
if (ym == cldMonShift_)
|
||||
return cldMonCached_;
|
||||
else
|
||||
cldMonShift_ = ym;
|
||||
}
|
||||
|
||||
// Compute number of lines needed for each calendar month
|
||||
unsigned ml[12]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
|
||||
|
||||
for (auto& m : ml) {
|
||||
if (cldMode_ == CldMode::YEAR || m == static_cast<unsigned>(ymd.month()))
|
||||
m = cldRowsInMonth(y / date::month{m}, firstdow);
|
||||
else
|
||||
m = 0u;
|
||||
}
|
||||
for (auto row{0u}; row < maxRows; ++row) {
|
||||
const auto lines = *std::max_element(std::begin(ml) + (row * cldMonCols_),
|
||||
std::begin(ml) + ((row + 1) * cldMonCols_));
|
||||
for (auto line{0u}; line < lines; ++line) {
|
||||
for (auto col{0u}; col < cldMonCols_; ++col) {
|
||||
const auto mon{date::month{row * cldMonCols_ + col + 1}};
|
||||
if (cldMode_ == CldMode::YEAR || y / mon == ym) {
|
||||
date::year_month ymTmp{y / mon};
|
||||
if (col != 0 && cldMode_ == CldMode::YEAR) os << " ";
|
||||
|
||||
// Week numbers on the left
|
||||
if (cldWPos_ == WeeksSide::LEFT && line > 0) {
|
||||
if (line > 1) {
|
||||
if (line < ml[static_cast<unsigned>(ymTmp.month()) - 1u])
|
||||
os << fmt::format(fmt::runtime(fmtMap_[4]),
|
||||
(line == 2)
|
||||
? date::sys_days{ymTmp / 1}
|
||||
: date::sys_days{cldGetWeekForLine(ymTmp, firstdow, line)})
|
||||
<< ' ';
|
||||
else
|
||||
os << std::string(cldWnLen_, ' ');
|
||||
}
|
||||
}
|
||||
|
||||
os << fmt::format(
|
||||
fmt::runtime((cldWPos_ != WeeksSide::LEFT || line == 0) ? "{:<{}}" : "{:>{}}"),
|
||||
getCalendarLine(currDate, ymTmp, line, firstdow, &locale_),
|
||||
(cldMonColLen_ + ((line < 2) ? cldWnLen_ : 0)));
|
||||
|
||||
// Week numbers on the right
|
||||
if (cldWPos_ == WeeksSide ::RIGHT && line > 0) {
|
||||
if (line > 1) {
|
||||
if (line < ml[static_cast<unsigned>(ymTmp.month()) - 1u])
|
||||
os << ' '
|
||||
<< fmt::format(fmt::runtime(fmtMap_[4]),
|
||||
(line == 2)
|
||||
? date::sys_days{ymTmp / 1}
|
||||
: date::sys_days{cldGetWeekForLine(ymTmp, firstdow, line)});
|
||||
else
|
||||
os << std::string(cldWnLen_, ' ');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply user formats to calendar
|
||||
if (line < 2)
|
||||
tmp << fmt::format(fmt::runtime(fmtMap_[line]), os.str());
|
||||
else
|
||||
tmp << os.str();
|
||||
// Clear ostringstream
|
||||
std::ostringstream().swap(os);
|
||||
if (line + 1u != lines || (row + 1u != maxRows && cldMode_ == CldMode::YEAR)) tmp << '\n';
|
||||
}
|
||||
if (row + 1u != maxRows && cldMode_ == CldMode::YEAR) tmp << '\n';
|
||||
}
|
||||
|
||||
os << fmt::format( // Apply days format
|
||||
fmt::runtime(fmt::format(fmt::runtime(fmtMap_[2]), tmp.str())),
|
||||
// Apply today format
|
||||
fmt::arg("today", fmt::format(fmt::runtime(fmtMap_[3]), date::format("%e", ymd.day()))));
|
||||
|
||||
if (cldMode_ == CldMode::YEAR)
|
||||
cldYearCached_ = os.str();
|
||||
else
|
||||
cldMonCached_ = os.str();
|
||||
|
||||
return os.str();
|
||||
}
|
||||
|
||||
void waybar::modules::Clock::cldModeSwitch() {
|
||||
cldMode_ = (cldMode_ == CldMode::YEAR) ? CldMode::MONTH : CldMode::YEAR;
|
||||
}
|
||||
|
||||
auto waybar::modules::Clock::timezones_text(std::chrono::system_clock::time_point* now)
|
||||
|
@ -305,7 +471,6 @@ auto waybar::modules::Clock::timezones_text(std::chrono::system_clock::time_poin
|
|||
return "";
|
||||
}
|
||||
std::stringstream os;
|
||||
waybar_time wtime;
|
||||
for (size_t time_zone_idx = 0; time_zone_idx < time_zones_.size(); ++time_zone_idx) {
|
||||
if (static_cast<int>(time_zone_idx) == current_time_zone_idx_) {
|
||||
continue;
|
||||
|
@ -314,8 +479,8 @@ auto waybar::modules::Clock::timezones_text(std::chrono::system_clock::time_poin
|
|||
if (!timezone) {
|
||||
timezone = date::current_zone();
|
||||
}
|
||||
wtime = {locale_, date::make_zoned(timezone, date::floor<std::chrono::seconds>(*now))};
|
||||
os << fmt::format(format_, wtime) << "\n";
|
||||
auto ztime = date::zoned_time{timezone, date::floor<std::chrono::seconds>(*now)};
|
||||
os << fmt::format(locale_, fmt::runtime(format_), ztime) << '\n';
|
||||
}
|
||||
return os.str();
|
||||
}
|
||||
|
|
|
@ -39,7 +39,6 @@ auto waybar::modules::Cpu::update() -> void {
|
|||
auto icons = std::vector<std::string>{state};
|
||||
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||
store.push_back(fmt::arg("load", cpu_load));
|
||||
store.push_back(fmt::arg("load", cpu_load));
|
||||
store.push_back(fmt::arg("usage", total_usage));
|
||||
store.push_back(fmt::arg("icon", getIcon(total_usage, icons)));
|
||||
store.push_back(fmt::arg("max_frequency", max_frequency));
|
||||
|
|
|
@ -275,8 +275,8 @@ waybar::modules::Custom::Node waybar::modules::Custom::parseItem(Json::Value &pa
|
|||
if(!parsed["name"].asString().empty()) {
|
||||
n.name_ = name_ + parsed["name"].asString();
|
||||
}
|
||||
if (!parsed["percentage"].asString().empty() && parsed["percentage"].isUInt()) {
|
||||
n.percentage_ = parsed["percentage"].asUInt();
|
||||
if (!parsed["percentage"].asString().empty() && parsed["percentage"].isNumeric()) {
|
||||
n.percentage_ = (int)lround(parsed["percentage"].asFloat());
|
||||
} else {
|
||||
n.percentage_ = 0;
|
||||
}
|
||||
|
|
|
@ -58,11 +58,11 @@ auto waybar::modules::Disk::update() -> void {
|
|||
event_box_.hide();
|
||||
} else {
|
||||
event_box_.show();
|
||||
label_.set_markup(
|
||||
fmt::format(format, stats.f_bavail * 100 / stats.f_blocks, fmt::arg("free", free),
|
||||
fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks),
|
||||
fmt::arg("used", used), fmt::arg("percentage_used", percentage_used),
|
||||
fmt::arg("total", total), fmt::arg("path", path_)));
|
||||
label_.set_markup(fmt::format(
|
||||
fmt::runtime(format), stats.f_bavail * 100 / stats.f_blocks, fmt::arg("free", free),
|
||||
fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks), fmt::arg("used", used),
|
||||
fmt::arg("percentage_used", percentage_used), fmt::arg("total", total),
|
||||
fmt::arg("path", path_)));
|
||||
}
|
||||
|
||||
if (tooltipEnabled()) {
|
||||
|
@ -70,11 +70,11 @@ auto waybar::modules::Disk::update() -> void {
|
|||
if (config_["tooltip-format"].isString()) {
|
||||
tooltip_format = config_["tooltip-format"].asString();
|
||||
}
|
||||
label_.set_tooltip_text(
|
||||
fmt::format(tooltip_format, stats.f_bavail * 100 / stats.f_blocks, fmt::arg("free", free),
|
||||
fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks),
|
||||
fmt::arg("used", used), fmt::arg("percentage_used", percentage_used),
|
||||
fmt::arg("total", total), fmt::arg("path", path_)));
|
||||
label_.set_tooltip_text(fmt::format(
|
||||
fmt::runtime(tooltip_format), stats.f_bavail * 100 / stats.f_blocks, fmt::arg("free", free),
|
||||
fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks), fmt::arg("used", used),
|
||||
fmt::arg("percentage_used", percentage_used), fmt::arg("total", total),
|
||||
fmt::arg("path", path_)));
|
||||
}
|
||||
// Call parent update
|
||||
ALabel::update();
|
||||
|
|
|
@ -213,13 +213,13 @@ auto Gamemode::update() -> void {
|
|||
|
||||
// Tooltip
|
||||
if (tooltip) {
|
||||
std::string text = fmt::format(tooltip_format, fmt::arg("count", gameCount));
|
||||
std::string text = fmt::format(fmt::runtime(tooltip_format), fmt::arg("count", gameCount));
|
||||
box_.set_tooltip_text(text);
|
||||
}
|
||||
|
||||
// Label format
|
||||
std::string str =
|
||||
fmt::format(showAltText ? format_alt : format, fmt::arg("glyph", useIcon ? "" : glyph),
|
||||
std::string str = fmt::format(fmt::runtime(showAltText ? format_alt : format),
|
||||
fmt::arg("glyph", useIcon ? "" : glyph),
|
||||
fmt::arg("count", gameCount > 0 ? std::to_string(gameCount) : ""));
|
||||
label_.set_markup(str);
|
||||
|
||||
|
|
|
@ -49,24 +49,23 @@ auto Language::update() -> void {
|
|||
|
||||
void Language::onEvent(const std::string& ev) {
|
||||
std::lock_guard<std::mutex> lg(mutex_);
|
||||
auto layoutName = ev.substr(ev.find_last_of(',') + 1);
|
||||
auto keebName = ev.substr(0, ev.find_last_of(','));
|
||||
keebName = keebName.substr(keebName.find_first_of('>') + 2);
|
||||
std::string kbName(begin(ev) + ev.find_last_of('>') + 1, begin(ev) + ev.find_first_of(','));
|
||||
auto layoutName = ev.substr(ev.find_first_of(',') + 1);
|
||||
|
||||
if (config_.isMember("keyboard-name") && keebName != config_["keyboard-name"].asString())
|
||||
if (config_.isMember("keyboard-name") && kbName != config_["keyboard-name"].asString())
|
||||
return; // ignore
|
||||
|
||||
const auto BRIEFNAME = getShortFrom(layoutName);
|
||||
|
||||
if (config_.isMember("format-" + BRIEFNAME)) {
|
||||
const auto PROPNAME = "format-" + BRIEFNAME;
|
||||
layoutName = fmt::format(format_, config_[PROPNAME].asString());
|
||||
} else {
|
||||
layoutName = fmt::format(format_, layoutName);
|
||||
}
|
||||
|
||||
layoutName = waybar::util::sanitize_string(layoutName);
|
||||
|
||||
const auto briefName = getShortFrom(layoutName);
|
||||
|
||||
if (config_.isMember("format-" + briefName)) {
|
||||
const auto propName = "format-" + briefName;
|
||||
layoutName = fmt::format(fmt::runtime(format_), config_[propName].asString());
|
||||
} else {
|
||||
layoutName = fmt::format(fmt::runtime(format_), layoutName);
|
||||
}
|
||||
|
||||
if (layoutName == layoutName_) return;
|
||||
|
||||
layoutName_ = layoutName;
|
||||
|
@ -77,18 +76,30 @@ void Language::onEvent(const std::string& ev) {
|
|||
}
|
||||
|
||||
void Language::initLanguage() {
|
||||
const auto INPUTDEVICES = gIPC->getSocket1Reply("devices");
|
||||
const auto inputDevices = gIPC->getSocket1Reply("devices");
|
||||
|
||||
if (!config_.isMember("keyboard-name")) return;
|
||||
|
||||
const auto KEEBNAME = config_["keyboard-name"].asString();
|
||||
const auto kbName = config_["keyboard-name"].asString();
|
||||
|
||||
try {
|
||||
auto searcher = INPUTDEVICES.substr(INPUTDEVICES.find(KEEBNAME) + KEEBNAME.length());
|
||||
searcher = searcher.substr(searcher.find("keymap:") + 7);
|
||||
auto searcher = kbName.empty()
|
||||
? inputDevices
|
||||
: inputDevices.substr(inputDevices.find(kbName) + kbName.length());
|
||||
searcher = searcher.substr(searcher.find("keymap:") + 8);
|
||||
searcher = searcher.substr(0, searcher.find_first_of("\n\t"));
|
||||
|
||||
layoutName_ = searcher;
|
||||
searcher = waybar::util::sanitize_string(searcher);
|
||||
|
||||
auto layoutName = std::string{};
|
||||
const auto briefName = getShortFrom(searcher);
|
||||
|
||||
if (config_.isMember("format-" + briefName)) {
|
||||
const auto propName = "format-" + briefName;
|
||||
layoutName = fmt::format(fmt::runtime(format_), config_[propName].asString());
|
||||
} else {
|
||||
layoutName = fmt::format(fmt::runtime(format_), searcher);
|
||||
}
|
||||
|
||||
layoutName_ = layoutName;
|
||||
|
||||
spdlog::debug("hyprland language initLanguage found {}", layoutName_);
|
||||
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
#include "modules/hyprland/submap.hpp"
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <util/sanitize_str.hpp>
|
||||
|
||||
namespace waybar::modules::hyprland {
|
||||
|
||||
Submap::Submap(const std::string& id, const Bar& bar, const Json::Value& config)
|
||||
: ALabel(config, "submap", id, "{}", 0, true), bar_(bar) {
|
||||
modulesReady = true;
|
||||
|
||||
if (!gIPC.get()) {
|
||||
gIPC = std::make_unique<IPC>();
|
||||
}
|
||||
|
||||
label_.hide();
|
||||
ALabel::update();
|
||||
|
||||
// register for hyprland ipc
|
||||
gIPC->registerForIPC("submap", this);
|
||||
dp.emit();
|
||||
}
|
||||
|
||||
Submap::~Submap() {
|
||||
gIPC->unregisterForIPC(this);
|
||||
// wait for possible event handler to finish
|
||||
std::lock_guard<std::mutex> lg(mutex_);
|
||||
}
|
||||
|
||||
auto Submap::update() -> void {
|
||||
std::lock_guard<std::mutex> lg(mutex_);
|
||||
|
||||
if (submap_.empty()) {
|
||||
event_box_.hide();
|
||||
} else {
|
||||
label_.set_markup(fmt::format(fmt::runtime(format_), submap_));
|
||||
if (tooltipEnabled()) {
|
||||
label_.set_tooltip_text(submap_);
|
||||
}
|
||||
event_box_.show();
|
||||
}
|
||||
// Call parent update
|
||||
ALabel::update();
|
||||
}
|
||||
|
||||
void Submap::onEvent(const std::string& ev) {
|
||||
std::lock_guard<std::mutex> lg(mutex_);
|
||||
|
||||
if (ev.find("submap") == std::string::npos) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto submapName = ev.substr(ev.find_last_of('>') + 1);
|
||||
submapName = waybar::util::sanitize_string(submapName);
|
||||
|
||||
submap_ = submapName;
|
||||
|
||||
spdlog::debug("hyprland submap onevent with {}", submap_);
|
||||
|
||||
dp.emit();
|
||||
}
|
||||
} // namespace waybar::modules::hyprland
|
|
@ -40,8 +40,8 @@ auto Window::update() -> void {
|
|||
|
||||
if (!format_.empty()) {
|
||||
label_.show();
|
||||
label_.set_markup(
|
||||
fmt::format(format_, waybar::util::rewriteTitle(lastView, config_["rewrite"])));
|
||||
label_.set_markup(fmt::format(fmt::runtime(format_),
|
||||
waybar::util::rewriteTitle(lastView, config_["rewrite"])));
|
||||
} else {
|
||||
label_.hide();
|
||||
}
|
||||
|
|
|
@ -63,19 +63,13 @@ auto waybar::modules::IdleInhibitor::update() -> void {
|
|||
}
|
||||
|
||||
std::string status_text = status ? "activated" : "deactivated";
|
||||
label_.set_markup(fmt::format(format_, fmt::arg("status", status_text),
|
||||
label_.set_markup(fmt::format(fmt::runtime(format_), fmt::arg("status", status_text),
|
||||
fmt::arg("icon", getIcon(0, status_text))));
|
||||
label_.get_style_context()->add_class(status_text);
|
||||
if (tooltipEnabled()) {
|
||||
label_.set_tooltip_markup(
|
||||
status ? fmt::format(config_["tooltip-format-activated"].isString()
|
||||
? config_["tooltip-format-activated"].asString()
|
||||
: "{status}",
|
||||
fmt::arg("status", status_text),
|
||||
fmt::arg("icon", getIcon(0, status_text)))
|
||||
: fmt::format(config_["tooltip-format-deactivated"].isString()
|
||||
? config_["tooltip-format-deactivated"].asString()
|
||||
: "{status}",
|
||||
auto config = config_[status ? "tooltip-format-activated" : "tooltip-format-deactivated"];
|
||||
auto tooltip_format = config.isString() ? config.asString() : "{status}";
|
||||
label_.set_tooltip_markup(fmt::format(fmt::runtime(tooltip_format),
|
||||
fmt::arg("status", status_text),
|
||||
fmt::arg("icon", getIcon(0, status_text))));
|
||||
}
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
#include "modules/image.hpp"
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
waybar::modules::Image::Image(const std::string& name, const std::string& id,
|
||||
const Json::Value& config)
|
||||
: AModule(config, "image-" + name, id, "{}") {
|
||||
event_box_.add(image_);
|
||||
waybar::modules::Image::Image(const std::string& id, const Json::Value& config)
|
||||
: AModule(config, "image", id), box_(Gtk::ORIENTATION_HORIZONTAL, 0) {
|
||||
box_.pack_start(image_);
|
||||
box_.set_name("image");
|
||||
if (!id.empty()) {
|
||||
box_.get_style_context()->add_class(id);
|
||||
}
|
||||
event_box_.add(box_);
|
||||
|
||||
dp.emit();
|
||||
|
||||
path_ = config["path"].asString();
|
||||
size_ = config["size"].asInt();
|
||||
|
||||
interval_ = config_["interval"].asInt();
|
||||
|
@ -40,8 +41,17 @@ void waybar::modules::Image::refresh(int sig) {
|
|||
}
|
||||
|
||||
auto waybar::modules::Image::update() -> void {
|
||||
Glib::RefPtr<Gdk::Pixbuf> pixbuf;
|
||||
util::command::res output_;
|
||||
|
||||
Glib::RefPtr<Gdk::Pixbuf> pixbuf;
|
||||
if (config_["path"].isString()) {
|
||||
path_ = config_["path"].asString();
|
||||
} else if (config_["exec"].isString()) {
|
||||
output_ = util::command::exec(config_["exec"].asString());
|
||||
path_ = output_.out;
|
||||
} else {
|
||||
path_ = "";
|
||||
}
|
||||
if (Glib::file_test(path_, Glib::FILE_TEST_EXISTS))
|
||||
pixbuf = Gdk::Pixbuf::create_from_file(path_, size_, size_);
|
||||
else
|
||||
|
|
|
@ -118,7 +118,7 @@ auto Inhibitor::update() -> void {
|
|||
std::string status_text = activated() ? "activated" : "deactivated";
|
||||
|
||||
label_.get_style_context()->remove_class(activated() ? "deactivated" : "activated");
|
||||
label_.set_markup(fmt::format(format_, fmt::arg("status", status_text),
|
||||
label_.set_markup(fmt::format(fmt::runtime(format_), fmt::arg("status", status_text),
|
||||
fmt::arg("icon", getIcon(0, status_text))));
|
||||
label_.get_style_context()->add_class(status_text);
|
||||
|
||||
|
|
|
@ -72,7 +72,7 @@ auto JACK::update() -> void {
|
|||
} else
|
||||
format = "{load}%";
|
||||
|
||||
label_.set_markup(fmt::format(format, fmt::arg("load", std::round(load_)),
|
||||
label_.set_markup(fmt::format(fmt::runtime(format), fmt::arg("load", std::round(load_)),
|
||||
fmt::arg("bufsize", bufsize_), fmt::arg("samplerate", samplerate_),
|
||||
fmt::arg("latency", fmt::format("{:.2f}", latency)),
|
||||
fmt::arg("xruns", xruns_)));
|
||||
|
@ -81,9 +81,9 @@ auto JACK::update() -> void {
|
|||
std::string tooltip_format = "{bufsize}/{samplerate} {latency}ms";
|
||||
if (config_["tooltip-format"].isString()) tooltip_format = config_["tooltip-format"].asString();
|
||||
label_.set_tooltip_text(fmt::format(
|
||||
tooltip_format, fmt::arg("load", std::round(load_)), fmt::arg("bufsize", bufsize_),
|
||||
fmt::arg("samplerate", samplerate_), fmt::arg("latency", fmt::format("{:.2f}", latency)),
|
||||
fmt::arg("xruns", xruns_)));
|
||||
fmt::runtime(tooltip_format), fmt::arg("load", std::round(load_)),
|
||||
fmt::arg("bufsize", bufsize_), fmt::arg("samplerate", samplerate_),
|
||||
fmt::arg("latency", fmt::format("{:.2f}", latency)), fmt::arg("xruns", xruns_)));
|
||||
}
|
||||
|
||||
// Call parent update
|
||||
|
|
|
@ -278,7 +278,7 @@ auto waybar::modules::KeyboardState::update() -> void {
|
|||
};
|
||||
for (auto& label_state : label_states) {
|
||||
std::string text;
|
||||
text = fmt::format(label_state.format,
|
||||
text = fmt::format(fmt::runtime(label_state.format),
|
||||
fmt::arg("icon", label_state.state ? icon_locked_ : icon_unlocked_),
|
||||
fmt::arg("name", label_state.name));
|
||||
label_state.label.set_markup(text);
|
||||
|
|
|
@ -56,7 +56,8 @@ auto waybar::modules::Memory::update() -> void {
|
|||
event_box_.show();
|
||||
auto icons = std::vector<std::string>{state};
|
||||
label_.set_markup(fmt::format(
|
||||
format, used_ram_percentage, fmt::arg("icon", getIcon(used_ram_percentage, icons)),
|
||||
fmt::runtime(format), used_ram_percentage,
|
||||
fmt::arg("icon", getIcon(used_ram_percentage, icons)),
|
||||
fmt::arg("total", total_ram_gigabytes), fmt::arg("swapTotal", total_swap_gigabytes),
|
||||
fmt::arg("percentage", used_ram_percentage),
|
||||
fmt::arg("swapPercentage", used_swap_percentage), fmt::arg("used", used_ram_gigabytes),
|
||||
|
@ -68,8 +69,8 @@ auto waybar::modules::Memory::update() -> void {
|
|||
if (config_["tooltip-format"].isString()) {
|
||||
auto tooltip_format = config_["tooltip-format"].asString();
|
||||
label_.set_tooltip_text(fmt::format(
|
||||
tooltip_format, used_ram_percentage, fmt::arg("total", total_ram_gigabytes),
|
||||
fmt::arg("swapTotal", total_swap_gigabytes),
|
||||
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("swapPercentage", used_swap_percentage), fmt::arg("used", used_ram_gigabytes),
|
||||
fmt::arg("swapUsed", used_swap_gigabytes), fmt::arg("avail", available_ram_gigabytes),
|
||||
|
|
|
@ -103,7 +103,6 @@ void waybar::modules::MPD::setLabel() {
|
|||
label_.hide();
|
||||
}
|
||||
|
||||
|
||||
if (tooltipEnabled()) {
|
||||
std::string tooltip_format;
|
||||
tooltip_format = config_["tooltip-format-disconnected"].isString()
|
||||
|
@ -175,14 +174,14 @@ void waybar::modules::MPD::setLabel() {
|
|||
|
||||
try {
|
||||
auto text = fmt::format(
|
||||
format, fmt::arg("artist", artist.raw()), fmt::arg("albumArtist", album_artist.raw()),
|
||||
fmt::arg("album", album.raw()), fmt::arg("title", title.raw()), fmt::arg("date", date),
|
||||
fmt::arg("volume", volume), fmt::arg("elapsedTime", elapsedTime),
|
||||
fmt::arg("totalTime", totalTime), fmt::arg("songPosition", song_pos),
|
||||
fmt::arg("queueLength", queue_length), fmt::arg("stateIcon", stateIcon),
|
||||
fmt::arg("consumeIcon", consumeIcon), fmt::arg("randomIcon", randomIcon),
|
||||
fmt::arg("repeatIcon", repeatIcon), fmt::arg("singleIcon", singleIcon),
|
||||
fmt::arg("filename", filename));
|
||||
fmt::runtime(format), fmt::arg("artist", artist.raw()),
|
||||
fmt::arg("albumArtist", album_artist.raw()), fmt::arg("album", album.raw()),
|
||||
fmt::arg("title", title.raw()), fmt::arg("date", date), fmt::arg("volume", volume),
|
||||
fmt::arg("elapsedTime", elapsedTime), fmt::arg("totalTime", totalTime),
|
||||
fmt::arg("songPosition", song_pos), fmt::arg("queueLength", queue_length),
|
||||
fmt::arg("stateIcon", stateIcon), fmt::arg("consumeIcon", consumeIcon),
|
||||
fmt::arg("randomIcon", randomIcon), fmt::arg("repeatIcon", repeatIcon),
|
||||
fmt::arg("singleIcon", singleIcon), fmt::arg("filename", filename));
|
||||
if (text.empty()) {
|
||||
label_.hide();
|
||||
} else {
|
||||
|
@ -199,7 +198,7 @@ void waybar::modules::MPD::setLabel() {
|
|||
: "MPD (connected)";
|
||||
try {
|
||||
auto tooltip_text =
|
||||
fmt::format(tooltip_format, fmt::arg("artist", artist.raw()),
|
||||
fmt::format(fmt::runtime(tooltip_format), fmt::arg("artist", artist.raw()),
|
||||
fmt::arg("albumArtist", album_artist.raw()), fmt::arg("album", album.raw()),
|
||||
fmt::arg("title", title.raw()), fmt::arg("date", date),
|
||||
fmt::arg("volume", volume), fmt::arg("elapsedTime", elapsedTime),
|
||||
|
|
|
@ -0,0 +1,394 @@
|
|||
#include "modules/mpris/mpris.hpp"
|
||||
|
||||
#include <fmt/core.h>
|
||||
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
extern "C" {
|
||||
#include <playerctl/playerctl.h>
|
||||
}
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
namespace waybar::modules::mpris {
|
||||
|
||||
const std::string DEFAULT_FORMAT = "{player} ({status}): {dynamic}";
|
||||
|
||||
Mpris::Mpris(const std::string& id, const Json::Value& config)
|
||||
: AModule(config, "mpris", id),
|
||||
box_(Gtk::ORIENTATION_HORIZONTAL, 0),
|
||||
label_(),
|
||||
format_(DEFAULT_FORMAT),
|
||||
interval_(0),
|
||||
player_("playerctld"),
|
||||
manager(),
|
||||
player() {
|
||||
box_.pack_start(label_);
|
||||
box_.set_name(name_);
|
||||
event_box_.add(box_);
|
||||
event_box_.signal_button_press_event().connect(sigc::mem_fun(*this, &Mpris::handleToggle));
|
||||
|
||||
if (config_["format"].isString()) {
|
||||
format_ = config_["format"].asString();
|
||||
}
|
||||
if (config_["format-playing"].isString()) {
|
||||
format_playing_ = config_["format-playing"].asString();
|
||||
}
|
||||
if (config_["format-paused"].isString()) {
|
||||
format_paused_ = config_["format-paused"].asString();
|
||||
}
|
||||
if (config_["format-stopped"].isString()) {
|
||||
format_stopped_ = config_["format-stopped"].asString();
|
||||
}
|
||||
if (config_["interval"].isUInt()) {
|
||||
interval_ = std::chrono::seconds(config_["interval"].asUInt());
|
||||
}
|
||||
if (config_["player"].isString()) {
|
||||
player_ = config_["player"].asString();
|
||||
}
|
||||
if (config_["ignored-players"].isArray()) {
|
||||
for (auto it = config_["ignored-players"].begin(); it != config_["ignored-players"].end();
|
||||
++it) {
|
||||
ignored_players_.push_back(it->asString());
|
||||
}
|
||||
}
|
||||
|
||||
GError* error = nullptr;
|
||||
manager = playerctl_player_manager_new(&error);
|
||||
if (error) {
|
||||
throw std::runtime_error(fmt::format("unable to create MPRIS client: {}", error->message));
|
||||
}
|
||||
|
||||
g_object_connect(manager, "signal::name-appeared", G_CALLBACK(onPlayerNameAppeared), this, NULL);
|
||||
g_object_connect(manager, "signal::name-vanished", G_CALLBACK(onPlayerNameVanished), this, NULL);
|
||||
|
||||
if (player_ == "playerctld") {
|
||||
// use playerctld proxy
|
||||
PlayerctlPlayerName name = {
|
||||
.instance = (gchar*)player_.c_str(),
|
||||
.source = PLAYERCTL_SOURCE_DBUS_SESSION,
|
||||
};
|
||||
player = playerctl_player_new_from_name(&name, &error);
|
||||
|
||||
} else {
|
||||
GList* players = playerctl_list_players(&error);
|
||||
if (error) {
|
||||
auto e = fmt::format("unable to list players: {}", error->message);
|
||||
g_error_free(error);
|
||||
throw std::runtime_error(e);
|
||||
}
|
||||
|
||||
for (auto p = players; p != NULL; p = p->next) {
|
||||
auto pn = static_cast<PlayerctlPlayerName*>(p->data);
|
||||
if (strcmp(pn->name, player_.c_str()) == 0) {
|
||||
player = playerctl_player_new_from_name(pn, &error);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (error) {
|
||||
throw std::runtime_error(
|
||||
fmt::format("unable to connect to player {}: {}", player_, error->message));
|
||||
}
|
||||
|
||||
if (player) {
|
||||
g_object_connect(player, "signal::play", G_CALLBACK(onPlayerPlay), this, "signal::pause",
|
||||
G_CALLBACK(onPlayerPause), this, "signal::stop", G_CALLBACK(onPlayerStop),
|
||||
this, "signal::stop", G_CALLBACK(onPlayerStop), this, "signal::metadata",
|
||||
G_CALLBACK(onPlayerMetadata), this, NULL);
|
||||
}
|
||||
|
||||
// allow setting an interval count that triggers periodic refreshes
|
||||
if (interval_.count() > 0) {
|
||||
thread_ = [this] {
|
||||
dp.emit();
|
||||
thread_.sleep_for(interval_);
|
||||
};
|
||||
}
|
||||
|
||||
// trigger initial update
|
||||
dp.emit();
|
||||
}
|
||||
|
||||
Mpris::~Mpris() {
|
||||
if (manager != NULL) g_object_unref(manager);
|
||||
if (player != NULL) g_object_unref(player);
|
||||
}
|
||||
|
||||
auto Mpris::getIcon(const Json::Value& icons, const std::string& key) -> std::string {
|
||||
if (icons.isObject()) {
|
||||
if (icons[key].isString()) {
|
||||
return icons[key].asString();
|
||||
} else if (icons["default"].isString()) {
|
||||
return icons["default"].asString();
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
auto Mpris::onPlayerNameAppeared(PlayerctlPlayerManager* manager, PlayerctlPlayerName* player_name,
|
||||
gpointer data) -> void {
|
||||
Mpris* mpris = static_cast<Mpris*>(data);
|
||||
if (!mpris) return;
|
||||
|
||||
spdlog::debug("mpris: name-appeared callback: {}", player_name->name);
|
||||
|
||||
if (std::string(player_name->name) != mpris->player_) {
|
||||
return;
|
||||
}
|
||||
|
||||
GError* error = nullptr;
|
||||
mpris->player = playerctl_player_new_from_name(player_name, &error);
|
||||
g_object_connect(mpris->player, "signal::play", G_CALLBACK(onPlayerPlay), mpris, "signal::pause",
|
||||
G_CALLBACK(onPlayerPause), mpris, "signal::stop", G_CALLBACK(onPlayerStop),
|
||||
mpris, "signal::stop", G_CALLBACK(onPlayerStop), mpris, "signal::metadata",
|
||||
G_CALLBACK(onPlayerMetadata), mpris, NULL);
|
||||
|
||||
mpris->dp.emit();
|
||||
}
|
||||
|
||||
auto Mpris::onPlayerNameVanished(PlayerctlPlayerManager* manager, PlayerctlPlayerName* player_name,
|
||||
gpointer data) -> void {
|
||||
Mpris* mpris = static_cast<Mpris*>(data);
|
||||
if (!mpris) return;
|
||||
|
||||
spdlog::debug("mpris: player-vanished callback: {}", player_name->name);
|
||||
|
||||
if (std::string(player_name->name) == mpris->player_) {
|
||||
mpris->player = nullptr;
|
||||
mpris->dp.emit();
|
||||
}
|
||||
}
|
||||
|
||||
auto Mpris::onPlayerPlay(PlayerctlPlayer* player, gpointer data) -> void {
|
||||
Mpris* mpris = static_cast<Mpris*>(data);
|
||||
if (!mpris) return;
|
||||
|
||||
spdlog::debug("mpris: player-play callback");
|
||||
// update widget
|
||||
mpris->dp.emit();
|
||||
}
|
||||
|
||||
auto Mpris::onPlayerPause(PlayerctlPlayer* player, gpointer data) -> void {
|
||||
Mpris* mpris = static_cast<Mpris*>(data);
|
||||
if (!mpris) return;
|
||||
|
||||
spdlog::debug("mpris: player-pause callback");
|
||||
// update widget
|
||||
mpris->dp.emit();
|
||||
}
|
||||
|
||||
auto Mpris::onPlayerStop(PlayerctlPlayer* player, gpointer data) -> void {
|
||||
Mpris* mpris = static_cast<Mpris*>(data);
|
||||
if (!mpris) return;
|
||||
|
||||
spdlog::debug("mpris: player-stop callback");
|
||||
|
||||
// hide widget
|
||||
mpris->event_box_.set_visible(false);
|
||||
// update widget
|
||||
mpris->dp.emit();
|
||||
}
|
||||
|
||||
auto Mpris::onPlayerMetadata(PlayerctlPlayer* player, GVariant* metadata, gpointer data) -> void {
|
||||
Mpris* mpris = static_cast<Mpris*>(data);
|
||||
if (!mpris) return;
|
||||
|
||||
spdlog::debug("mpris: player-metadata callback");
|
||||
// update widget
|
||||
mpris->dp.emit();
|
||||
}
|
||||
|
||||
auto Mpris::getPlayerInfo() -> std::optional<PlayerInfo> {
|
||||
if (!player) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
GError* error = nullptr;
|
||||
|
||||
char* player_status = nullptr;
|
||||
auto player_playback_status = PLAYERCTL_PLAYBACK_STATUS_STOPPED;
|
||||
g_object_get(player, "status", &player_status, "playback-status", &player_playback_status, NULL);
|
||||
|
||||
std::string player_name = player_;
|
||||
if (player_name == "playerctld") {
|
||||
GList* players = playerctl_list_players(&error);
|
||||
if (error) {
|
||||
auto e = fmt::format("unable to list players: {}", error->message);
|
||||
g_error_free(error);
|
||||
throw std::runtime_error(e);
|
||||
}
|
||||
// > 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 (std::any_of(ignored_players_.begin(), ignored_players_.end(),
|
||||
[&](const std::string& pn) { return player_name == pn; })) {
|
||||
spdlog::warn("mpris[{}]: ignoring player update", player_name);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// make status lowercase
|
||||
player_status[0] = std::tolower(player_status[0]);
|
||||
|
||||
PlayerInfo info = {
|
||||
.name = player_name,
|
||||
.status = player_playback_status,
|
||||
.status_string = player_status,
|
||||
};
|
||||
|
||||
if (auto artist_ = playerctl_player_get_artist(player, &error)) {
|
||||
spdlog::debug("mpris[{}]: artist = {}", info.name, artist_);
|
||||
info.artist = Glib::Markup::escape_text(artist_);
|
||||
g_free(artist_);
|
||||
}
|
||||
if (error) goto errorexit;
|
||||
|
||||
if (auto album_ = playerctl_player_get_album(player, &error)) {
|
||||
spdlog::debug("mpris[{}]: album = {}", info.name, album_);
|
||||
info.album = Glib::Markup::escape_text(album_);
|
||||
g_free(album_);
|
||||
}
|
||||
if (error) goto errorexit;
|
||||
|
||||
if (auto title_ = playerctl_player_get_title(player, &error)) {
|
||||
spdlog::debug("mpris[{}]: title = {}", info.name, title_);
|
||||
info.title = Glib::Markup::escape_text(title_);
|
||||
g_free(title_);
|
||||
}
|
||||
if (error) goto errorexit;
|
||||
|
||||
if (auto length_ = playerctl_player_print_metadata_prop(player, "mpris:length", &error)) {
|
||||
spdlog::debug("mpris[{}]: mpris:length = {}", info.name, length_);
|
||||
std::chrono::microseconds len = std::chrono::microseconds(std::strtol(length_, nullptr, 10));
|
||||
auto len_h = std::chrono::duration_cast<std::chrono::hours>(len);
|
||||
auto len_m = std::chrono::duration_cast<std::chrono::minutes>(len - len_h);
|
||||
auto len_s = std::chrono::duration_cast<std::chrono::seconds>(len - len_m);
|
||||
info.length = fmt::format("{:02}:{:02}:{:02}", len_h.count(), len_m.count(), len_s.count());
|
||||
g_free(length_);
|
||||
}
|
||||
if (error) goto errorexit;
|
||||
|
||||
return info;
|
||||
|
||||
errorexit:
|
||||
spdlog::error("mpris[{}]: {}", info.name, error->message);
|
||||
g_error_free(error);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool Mpris::handleToggle(GdkEventButton* const& e) {
|
||||
GError* error = nullptr;
|
||||
|
||||
auto info = getPlayerInfo();
|
||||
if (!info) return false;
|
||||
|
||||
if (e->type == GdkEventType::GDK_BUTTON_PRESS) {
|
||||
switch (e->button) {
|
||||
case 1: // left-click
|
||||
if (config_["on-click"].isString()) {
|
||||
return AModule::handleToggle(e);
|
||||
}
|
||||
playerctl_player_play_pause(player, &error);
|
||||
break;
|
||||
case 2: // middle-click
|
||||
if (config_["on-middle-click"].isString()) {
|
||||
return AModule::handleToggle(e);
|
||||
}
|
||||
playerctl_player_previous(player, &error);
|
||||
break;
|
||||
case 3: // right-click
|
||||
if (config_["on-right-click"].isString()) {
|
||||
return AModule::handleToggle(e);
|
||||
}
|
||||
playerctl_player_next(player, &error);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (error) {
|
||||
spdlog::error("mpris[{}]: error running builtin on-click action: {}", (*info).name,
|
||||
error->message);
|
||||
g_error_free(error);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
auto Mpris::update() -> void {
|
||||
auto opt = getPlayerInfo();
|
||||
if (!opt) {
|
||||
event_box_.set_visible(false);
|
||||
AModule::update();
|
||||
return;
|
||||
}
|
||||
auto info = *opt;
|
||||
|
||||
if (info.status == PLAYERCTL_PLAYBACK_STATUS_STOPPED) {
|
||||
spdlog::debug("mpris[{}]: player stopped, skipping update", info.name);
|
||||
return;
|
||||
}
|
||||
|
||||
spdlog::debug("mpris[{}]: running update", info.name);
|
||||
|
||||
// dynamic is the auto-formatted string containing a nice out-of-the-box
|
||||
// format text
|
||||
std::stringstream dynamic;
|
||||
if (info.artist) dynamic << *info.artist << " - ";
|
||||
if (info.album) dynamic << *info.album << " - ";
|
||||
if (info.title) dynamic << *info.title;
|
||||
if (info.length)
|
||||
dynamic << " "
|
||||
<< "<small>"
|
||||
<< "[" << *info.length << "]"
|
||||
<< "</small>";
|
||||
|
||||
// set css class for player status
|
||||
if (!lastStatus.empty() && box_.get_style_context()->has_class(lastStatus)) {
|
||||
box_.get_style_context()->remove_class(lastStatus);
|
||||
}
|
||||
if (!box_.get_style_context()->has_class(info.status_string)) {
|
||||
box_.get_style_context()->add_class(info.status_string);
|
||||
}
|
||||
lastStatus = info.status_string;
|
||||
|
||||
// set css class for player name
|
||||
if (!lastPlayer.empty() && box_.get_style_context()->has_class(lastPlayer)) {
|
||||
box_.get_style_context()->remove_class(lastPlayer);
|
||||
}
|
||||
if (!box_.get_style_context()->has_class(info.name)) {
|
||||
box_.get_style_context()->add_class(info.name);
|
||||
}
|
||||
lastPlayer = info.name;
|
||||
|
||||
auto formatstr = format_;
|
||||
switch (info.status) {
|
||||
case PLAYERCTL_PLAYBACK_STATUS_PLAYING:
|
||||
if (!format_playing_.empty()) formatstr = format_playing_;
|
||||
break;
|
||||
case PLAYERCTL_PLAYBACK_STATUS_PAUSED:
|
||||
if (!format_paused_.empty()) formatstr = format_paused_;
|
||||
break;
|
||||
case PLAYERCTL_PLAYBACK_STATUS_STOPPED:
|
||||
if (!format_stopped_.empty()) formatstr = format_stopped_;
|
||||
break;
|
||||
}
|
||||
auto label_format =
|
||||
fmt::format(fmt::runtime(formatstr), fmt::arg("player", info.name),
|
||||
fmt::arg("status", info.status_string), fmt::arg("artist", *info.artist),
|
||||
fmt::arg("title", *info.title), fmt::arg("album", *info.album),
|
||||
fmt::arg("length", *info.length), fmt::arg("dynamic", dynamic.str()),
|
||||
fmt::arg("player_icon", getIcon(config_["player-icons"], info.name)),
|
||||
fmt::arg("status_icon", getIcon(config_["status-icons"], info.status_string)));
|
||||
label_.set_markup(label_format);
|
||||
|
||||
event_box_.set_visible(true);
|
||||
// call parent update
|
||||
AModule::update();
|
||||
}
|
||||
|
||||
} // namespace waybar::modules::mpris
|
|
@ -331,7 +331,7 @@ auto waybar::modules::Network::update() -> void {
|
|||
getState(signal_strength_);
|
||||
|
||||
auto text = fmt::format(
|
||||
format_, fmt::arg("essid", essid_), fmt::arg("signaldBm", signal_strength_dbm_),
|
||||
fmt::runtime(format_), fmt::arg("essid", essid_), 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_),
|
||||
|
@ -363,8 +363,8 @@ auto waybar::modules::Network::update() -> void {
|
|||
}
|
||||
if (!tooltip_format.empty()) {
|
||||
auto tooltip_text = fmt::format(
|
||||
tooltip_format, fmt::arg("essid", essid_), fmt::arg("signaldBm", signal_strength_dbm_),
|
||||
fmt::arg("signalStrength", signal_strength_),
|
||||
fmt::runtime(tooltip_format), fmt::arg("essid", essid_),
|
||||
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_)),
|
||||
|
|
|
@ -294,9 +294,9 @@ auto waybar::modules::Pulseaudio::update() -> void {
|
|||
format_source = config_["format-source"].asString();
|
||||
}
|
||||
}
|
||||
format_source = fmt::format(format_source, fmt::arg("volume", source_volume_));
|
||||
format_source = fmt::format(fmt::runtime(format_source), fmt::arg("volume", source_volume_));
|
||||
auto text = fmt::format(
|
||||
format, fmt::arg("desc", desc_), fmt::arg("volume", volume_),
|
||||
fmt::runtime(format), fmt::arg("desc", desc_), fmt::arg("volume", volume_),
|
||||
fmt::arg("format_source", format_source), fmt::arg("source_volume", source_volume_),
|
||||
fmt::arg("source_desc", source_desc_), fmt::arg("icon", getIcon(volume_, getPulseIcon())));
|
||||
if (text.empty()) {
|
||||
|
@ -313,7 +313,7 @@ auto waybar::modules::Pulseaudio::update() -> void {
|
|||
}
|
||||
if (!tooltip_format.empty()) {
|
||||
label_.set_tooltip_text(fmt::format(
|
||||
tooltip_format, fmt::arg("desc", desc_), fmt::arg("volume", volume_),
|
||||
fmt::runtime(tooltip_format), fmt::arg("desc", desc_), fmt::arg("volume", volume_),
|
||||
fmt::arg("format_source", format_source), fmt::arg("source_volume", source_volume_),
|
||||
fmt::arg("source_desc", source_desc_),
|
||||
fmt::arg("icon", getIcon(volume_, getPulseIcon()))));
|
||||
|
|
|
@ -103,7 +103,7 @@ void Mode::handle_mode(const char *mode) {
|
|||
}
|
||||
|
||||
label_.get_style_context()->add_class(mode);
|
||||
label_.set_markup(fmt::format(format_, Glib::Markup::escape_text(mode).raw()));
|
||||
label_.set_markup(fmt::format(fmt::runtime(format_), Glib::Markup::escape_text(mode).raw()));
|
||||
label_.show();
|
||||
}
|
||||
|
||||
|
|
|
@ -106,7 +106,7 @@ void Window::handle_focused_view(const char *title) {
|
|||
label_.hide(); // hide empty labels or labels with empty format
|
||||
} else {
|
||||
label_.show();
|
||||
label_.set_markup(fmt::format(format_, Glib::Markup::escape_text(title).raw()));
|
||||
label_.set_markup(fmt::format(fmt::runtime(format_), Glib::Markup::escape_text(title).raw()));
|
||||
}
|
||||
|
||||
ALabel::update();
|
||||
|
|
|
@ -110,7 +110,8 @@ auto Sndio::update() -> void {
|
|||
label_.get_style_context()->remove_class("muted");
|
||||
}
|
||||
|
||||
auto text = fmt::format(format, fmt::arg("volume", vol), fmt::arg("raw_value", volume_));
|
||||
auto text =
|
||||
fmt::format(fmt::runtime(format), fmt::arg("volume", vol), fmt::arg("raw_value", volume_));
|
||||
if (text.empty()) {
|
||||
label_.hide();
|
||||
} else {
|
||||
|
@ -118,7 +119,6 @@ auto Sndio::update() -> void {
|
|||
label_.show();
|
||||
}
|
||||
|
||||
|
||||
ALabel::update();
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
#include <fcntl.h>
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
namespace waybar::modules::sway {
|
||||
|
||||
Ipc::Ipc() {
|
||||
|
|
|
@ -96,14 +96,14 @@ void Language::onEvent(const struct Ipc::ipc_response& res) {
|
|||
auto Language::update() -> void {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
auto display_layout = trim(fmt::format(
|
||||
format_, fmt::arg("short", layout_.short_name),
|
||||
fmt::runtime(format_), fmt::arg("short", layout_.short_name),
|
||||
fmt::arg("shortDescription", layout_.short_description), fmt::arg("long", layout_.full_name),
|
||||
fmt::arg("variant", layout_.variant), fmt::arg("flag", layout_.country_flag())));
|
||||
label_.set_markup(display_layout);
|
||||
if (tooltipEnabled()) {
|
||||
if (tooltip_format_ != "") {
|
||||
auto tooltip_display_layout = trim(
|
||||
fmt::format(tooltip_format_, fmt::arg("short", layout_.short_name),
|
||||
fmt::format(fmt::runtime(tooltip_format_), fmt::arg("short", layout_.short_name),
|
||||
fmt::arg("shortDescription", layout_.short_description),
|
||||
fmt::arg("long", layout_.full_name), fmt::arg("variant", layout_.variant),
|
||||
fmt::arg("flag", layout_.country_flag())));
|
||||
|
|
|
@ -42,7 +42,7 @@ auto Mode::update() -> void {
|
|||
if (mode_.empty()) {
|
||||
event_box_.hide();
|
||||
} else {
|
||||
label_.set_markup(fmt::format(format_, mode_));
|
||||
label_.set_markup(fmt::format(fmt::runtime(format_), mode_));
|
||||
if (tooltipEnabled()) {
|
||||
label_.set_tooltip_text(mode_);
|
||||
}
|
||||
|
|
|
@ -32,7 +32,8 @@ auto Scratchpad::update() -> void {
|
|||
if (count_ || show_empty_) {
|
||||
event_box_.show();
|
||||
label_.set_markup(
|
||||
fmt::format(format_, fmt::arg("icon", getIcon(count_, "", config_["format-icons"].size())),
|
||||
fmt::format(fmt::runtime(format_),
|
||||
fmt::arg("icon", getIcon(count_, "", config_["format-icons"].size())),
|
||||
fmt::arg("count", count_)));
|
||||
if (tooltip_enabled_) {
|
||||
label_.set_tooltip_markup(tooltip_text_);
|
||||
|
@ -64,7 +65,7 @@ auto Scratchpad::onCmd(const struct Ipc::ipc_response& res) -> void {
|
|||
if (tooltip_enabled_) {
|
||||
tooltip_text_.clear();
|
||||
for (const auto& window : tree["nodes"][0]["nodes"][0]["floating_nodes"]) {
|
||||
tooltip_text_.append(fmt::format(tooltip_format_ + '\n',
|
||||
tooltip_text_.append(fmt::format(fmt::runtime(tooltip_format_ + '\n'),
|
||||
fmt::arg("app", window["app_id"].asString()),
|
||||
fmt::arg("title", window["name"].asString())));
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
namespace waybar::modules::sway {
|
||||
|
||||
Window::Window(const std::string& id, const Bar& bar, const Json::Value& config)
|
||||
: AIconLabel(config, "window", id, "{title}", 0, true), bar_(bar), windowId_(-1) {
|
||||
: AIconLabel(config, "window", id, "{}", 0, true), bar_(bar), windowId_(-1) {
|
||||
// Icon size
|
||||
if (config_["icon-size"].isUInt()) {
|
||||
app_icon_size_ = config["icon-size"].asUInt();
|
||||
|
@ -35,6 +35,7 @@ Window::Window(const std::string& id, const Bar& bar, const Json::Value& config)
|
|||
ipc_.handleEvent();
|
||||
} catch (const std::exception& e) {
|
||||
spdlog::error("Window: {}", e.what());
|
||||
spdlog::trace("Window::Window exception");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -46,12 +47,13 @@ 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_, windowId_, window_, app_id_, app_class_, shell_) =
|
||||
std::tie(app_nb_, floating_count_, windowId_, window_, app_id_, app_class_, shell_, layout_) =
|
||||
getFocusedNode(payload["nodes"], output);
|
||||
updateAppIconName();
|
||||
dp.emit();
|
||||
} catch (const std::exception& e) {
|
||||
spdlog::error("Window: {}", e.what());
|
||||
spdlog::trace("Window::onCmd exception");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -156,29 +158,55 @@ void Window::updateAppIcon() {
|
|||
}
|
||||
|
||||
auto Window::update() -> void {
|
||||
if (!old_app_id_.empty()) {
|
||||
bar_.window.get_style_context()->remove_class(old_app_id_);
|
||||
}
|
||||
spdlog::trace("workspace layout {}, tiled count {}, floating count {}", layout_, app_nb_,
|
||||
floating_count_);
|
||||
|
||||
int mode = 0;
|
||||
if (app_nb_ == 0) {
|
||||
bar_.window.get_style_context()->remove_class("solo");
|
||||
if (!bar_.window.get_style_context()->has_class("empty")) {
|
||||
bar_.window.get_style_context()->add_class("empty");
|
||||
if (floating_count_ == 0) {
|
||||
mode += 1;
|
||||
} else {
|
||||
mode += 4;
|
||||
}
|
||||
} else if (app_nb_ == 1) {
|
||||
bar_.window.get_style_context()->remove_class("empty");
|
||||
if (!bar_.window.get_style_context()->has_class("solo")) {
|
||||
bar_.window.get_style_context()->add_class("solo");
|
||||
mode += 2;
|
||||
} else {
|
||||
if (layout_ == "tabbed") {
|
||||
mode += 8;
|
||||
} else if (layout_ == "stacked") {
|
||||
mode += 16;
|
||||
} else {
|
||||
mode += 32;
|
||||
}
|
||||
if (!app_id_.empty() && !bar_.window.get_style_context()->has_class(app_id_)) {
|
||||
bar_.window.get_style_context()->add_class(app_id_);
|
||||
old_app_id_ = app_id_;
|
||||
}
|
||||
} else {
|
||||
bar_.window.get_style_context()->remove_class("solo");
|
||||
bar_.window.get_style_context()->remove_class("empty");
|
||||
}
|
||||
label_.set_markup(fmt::format(
|
||||
format_, fmt::arg("title", waybar::util::rewriteTitle(window_, config_["rewrite"])),
|
||||
|
||||
if (!old_app_id_.empty() && ((mode & 2) == 0 || old_app_id_ != app_id_) &&
|
||||
bar_.window.get_style_context()->has_class(old_app_id_)) {
|
||||
spdlog::trace("Removing app_id class: {}", old_app_id_);
|
||||
bar_.window.get_style_context()->remove_class(old_app_id_);
|
||||
old_app_id_ = "";
|
||||
}
|
||||
|
||||
setClass("empty", ((mode & 1) > 0));
|
||||
setClass("solo", ((mode & 2) > 0));
|
||||
setClass("floating", ((mode & 4) > 0));
|
||||
setClass("tabbed", ((mode & 8) > 0));
|
||||
setClass("stacked", ((mode & 16) > 0));
|
||||
setClass("tiled", ((mode & 32) > 0));
|
||||
|
||||
if ((mode & 2) > 0 && !app_id_.empty() && !bar_.window.get_style_context()->has_class(app_id_)) {
|
||||
spdlog::trace("Adding app_id class: {}", app_id_);
|
||||
bar_.window.get_style_context()->add_class(app_id_);
|
||||
old_app_id_ = app_id_;
|
||||
}
|
||||
|
||||
label_.set_markup(
|
||||
fmt::format(fmt::runtime(format_),
|
||||
fmt::arg("title", waybar::util::rewriteTitle(window_, config_["rewrite"])),
|
||||
fmt::arg("app_id", app_id_), fmt::arg("shell", shell_)));
|
||||
if (tooltipEnabled()) {
|
||||
label_.set_tooltip_text(window_);
|
||||
|
@ -190,71 +218,143 @@ auto Window::update() -> void {
|
|||
AIconLabel::update();
|
||||
}
|
||||
|
||||
int leafNodesInWorkspace(const Json::Value& node) {
|
||||
void Window::setClass(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);
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<int, int> leafNodesInWorkspace(const Json::Value& node) {
|
||||
auto const& nodes = node["nodes"];
|
||||
auto const& floating_nodes = node["floating_nodes"];
|
||||
if (nodes.empty() && floating_nodes.empty()) {
|
||||
if (node["type"] == "workspace")
|
||||
return 0;
|
||||
else
|
||||
return 1;
|
||||
if (node["type"].asString() == "workspace")
|
||||
return {0, 0};
|
||||
else if (node["type"].asString() == "floating_con") {
|
||||
return {0, 1};
|
||||
} else {
|
||||
return {1, 0};
|
||||
}
|
||||
}
|
||||
int sum = 0;
|
||||
if (!nodes.empty()) {
|
||||
for (auto const& node : nodes) sum += leafNodesInWorkspace(node);
|
||||
int floating_sum = 0;
|
||||
for (auto const& node : nodes) {
|
||||
std::pair all_leaf_nodes = leafNodesInWorkspace(node);
|
||||
sum += all_leaf_nodes.first;
|
||||
floating_sum += all_leaf_nodes.second;
|
||||
}
|
||||
if (!floating_nodes.empty()) {
|
||||
for (auto const& node : floating_nodes) sum += leafNodesInWorkspace(node);
|
||||
for (auto const& node : floating_nodes) {
|
||||
std::pair all_leaf_nodes = leafNodesInWorkspace(node);
|
||||
sum += all_leaf_nodes.first;
|
||||
floating_sum += all_leaf_nodes.second;
|
||||
}
|
||||
return sum;
|
||||
return {sum, floating_sum};
|
||||
}
|
||||
|
||||
std::tuple<std::size_t, int, 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) {
|
||||
std::tuple<std::size_t, int, int, 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) {
|
||||
for (auto const& node : nodes) {
|
||||
if (node["output"].isString()) {
|
||||
output = node["output"].asString();
|
||||
if (node["type"].asString() == "output") {
|
||||
if ((!config_["all-outputs"].asBool() || config_["offscreen-css"].asBool()) &&
|
||||
(node["name"].asString() != bar_.output->name)) {
|
||||
continue;
|
||||
}
|
||||
output = node["name"].asString();
|
||||
} else if (node["type"].asString() == "workspace") {
|
||||
// needs to be a string comparison, because filterWorkspace is the current_workspace
|
||||
if (node["name"].asString() != immediateParent["current_workspace"].asString()) {
|
||||
continue;
|
||||
}
|
||||
if (node["focused"].asBool()) {
|
||||
std::pair all_leaf_nodes = leafNodesInWorkspace(node);
|
||||
return {all_leaf_nodes.first,
|
||||
all_leaf_nodes.second,
|
||||
node["id"].asInt(),
|
||||
(((all_leaf_nodes.first > 0) || (all_leaf_nodes.second > 0)) &&
|
||||
(config_["show-focused-workspace-name"].asBool()))
|
||||
? node["name"].asString()
|
||||
: "",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
node["layout"].asString()};
|
||||
}
|
||||
parentWorkspace = node;
|
||||
} else if ((node["type"].asString() == "con" || node["type"].asString() == "floating_con") &&
|
||||
(node["focused"].asBool())) {
|
||||
// found node
|
||||
if (node["focused"].asBool() && (node["type"] == "con" || node["type"] == "floating_con")) {
|
||||
if ((!config_["all-outputs"].asBool() && output == bar_.output->name) ||
|
||||
config_["all-outputs"].asBool()) {
|
||||
spdlog::trace("actual output {}, output found {}, node (focused) found {}", bar_.output->name,
|
||||
output, node["name"].asString());
|
||||
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() : "";
|
||||
|
||||
int nb = node.size();
|
||||
if (parentWorkspace != 0) nb = leafNodesInWorkspace(parentWorkspace);
|
||||
return {nb, node["id"].asInt(), Glib::Markup::escape_text(node["name"].asString()),
|
||||
app_id, app_class, shell};
|
||||
int floating_count = 0;
|
||||
std::string workspace_layout = "";
|
||||
if (!parentWorkspace.isNull()) {
|
||||
std::pair all_leaf_nodes = leafNodesInWorkspace(parentWorkspace);
|
||||
nb = all_leaf_nodes.first;
|
||||
floating_count = all_leaf_nodes.second;
|
||||
workspace_layout = parentWorkspace["layout"].asString();
|
||||
}
|
||||
return {nb,
|
||||
floating_count,
|
||||
node["id"].asInt(),
|
||||
Glib::Markup::escape_text(node["name"].asString()),
|
||||
app_id,
|
||||
app_class,
|
||||
shell,
|
||||
workspace_layout};
|
||||
}
|
||||
|
||||
// iterate
|
||||
if (node["type"] == "workspace") parentWorkspace = node;
|
||||
auto [nb, id, name, app_id, app_class, shell] =
|
||||
gfnWithWorkspace(node["nodes"], output, config_, bar_, parentWorkspace);
|
||||
if (id > -1 && !name.empty()) {
|
||||
return {nb, id, name, app_id, app_class, shell};
|
||||
}
|
||||
// Search for floating node
|
||||
std::tie(nb, id, name, app_id, app_class, shell) =
|
||||
gfnWithWorkspace(node["floating_nodes"], output, config_, bar_, parentWorkspace);
|
||||
if (id > -1 && !name.empty()) {
|
||||
return {nb, id, name, app_id, app_class, shell};
|
||||
auto [nb, f, id, name, app_id, app_class, shell, workspace_layout] =
|
||||
gfnWithWorkspace(node["nodes"], output, config_, bar_, parentWorkspace, node);
|
||||
auto [nb2, f2, id2, name2, app_id2, app_class2, shell2, workspace_layout2] =
|
||||
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};
|
||||
} else if (id2 > 0 && !name2.empty()) {
|
||||
return {nb2, f2, id2, name2, app_id2, app_class, shell2, workspace_layout2};
|
||||
}
|
||||
}
|
||||
return {0, -1, "", "", "", ""};
|
||||
|
||||
// this only comes into effect when no focused children are present
|
||||
if (config_["all-outputs"].asBool() && config_["offscreen-css"].asBool() &&
|
||||
immediateParent["type"].asString() == "workspace") {
|
||||
std::pair all_leaf_nodes = leafNodesInWorkspace(immediateParent);
|
||||
// using an empty string as default ensures that no window depending styles are set due to the
|
||||
// checks above for !name.empty()
|
||||
return {all_leaf_nodes.first,
|
||||
all_leaf_nodes.second,
|
||||
0,
|
||||
(all_leaf_nodes.first > 0 || all_leaf_nodes.second > 0)
|
||||
? config_["offscreen-css-text"].asString()
|
||||
: "",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
immediateParent["layout"].asString()};
|
||||
}
|
||||
|
||||
return {0, 0, -1, "", "", "", "", ""};
|
||||
}
|
||||
|
||||
std::tuple<std::size_t, int, 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>
|
||||
Window::getFocusedNode(const Json::Value& nodes, std::string& output) {
|
||||
Json::Value placeholder = 0;
|
||||
return gfnWithWorkspace(nodes, output, config_, bar_, placeholder);
|
||||
Json::Value placeholder = Json::Value::null;
|
||||
return gfnWithWorkspace(nodes, output, config_, bar_, placeholder, placeholder);
|
||||
}
|
||||
|
||||
void Window::getTree() {
|
||||
|
@ -262,6 +362,7 @@ void Window::getTree() {
|
|||
ipc_.sendCmd(IPC_GET_TREE);
|
||||
} catch (const std::exception& e) {
|
||||
spdlog::error("Window: {}", e.what());
|
||||
spdlog::trace("Window::getTree exception");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -130,6 +130,10 @@ void Workspaces::onCmd(const struct Ipc::ipc_response &res) {
|
|||
// In a first pass, the maximum "num" value is computed to enqueue
|
||||
// unnumbered workspaces behind numbered ones when computing the sort
|
||||
// attribute.
|
||||
//
|
||||
// Note: if the 'alphabetical_sort' option is true, the user is in
|
||||
// agreement that the "workspace prev/next" commands may not follow
|
||||
// the order displayed in Waybar.
|
||||
int max_num = -1;
|
||||
for (auto &workspace : workspaces_) {
|
||||
max_num = std::max(workspace["num"].asInt(), max_num);
|
||||
|
@ -143,16 +147,19 @@ void Workspaces::onCmd(const struct Ipc::ipc_response &res) {
|
|||
}
|
||||
}
|
||||
std::sort(workspaces_.begin(), workspaces_.end(),
|
||||
[](const Json::Value &lhs, const Json::Value &rhs) {
|
||||
[this](const Json::Value &lhs, const Json::Value &rhs) {
|
||||
auto lname = lhs["name"].asString();
|
||||
auto rname = rhs["name"].asString();
|
||||
int l = lhs["sort"].asInt();
|
||||
int r = rhs["sort"].asInt();
|
||||
|
||||
if (l == r) {
|
||||
if (l == r || config_["alphabetical_sort"].asBool()) {
|
||||
// In case both integers are the same, lexicographical
|
||||
// sort. The code above already ensure that this will only
|
||||
// happend in case of explicitly numbered workspaces.
|
||||
//
|
||||
// Additionally, if the config specifies to sort workspaces
|
||||
// alphabetically do this here.
|
||||
return lname < rname;
|
||||
}
|
||||
|
||||
|
@ -226,7 +233,7 @@ auto Workspaces::update() -> void {
|
|||
std::string output = (*it)["name"].asString();
|
||||
if (config_["format"].isString()) {
|
||||
auto format = config_["format"].asString();
|
||||
output = fmt::format(format, fmt::arg("icon", getIcon(output, *it)),
|
||||
output = fmt::format(fmt::runtime(format), fmt::arg("icon", getIcon(output, *it)),
|
||||
fmt::arg("value", output), fmt::arg("name", trimWorkspaceName(output)),
|
||||
fmt::arg("index", (*it)["num"].asString()));
|
||||
}
|
||||
|
@ -252,11 +259,9 @@ Gtk::Button &Workspaces::addButton(const Json::Value &node) {
|
|||
try {
|
||||
if (node["target_output"].isString()) {
|
||||
ipc_.sendCmd(IPC_COMMAND,
|
||||
fmt::format(workspace_switch_cmd_ + "; move workspace to output \"{}\"; " +
|
||||
workspace_switch_cmd_,
|
||||
"--no-auto-back-and-forth", node["name"].asString(),
|
||||
node["target_output"].asString(), "--no-auto-back-and-forth",
|
||||
node["name"].asString()));
|
||||
fmt::format(persistent_workspace_switch_cmd_, "--no-auto-back-and-forth",
|
||||
node["name"].asString(), node["target_output"].asString(),
|
||||
"--no-auto-back-and-forth", node["name"].asString()));
|
||||
} else {
|
||||
ipc_.sendCmd(IPC_COMMAND, fmt::format("workspace {} \"{}\"",
|
||||
config_["disable-auto-back-and-forth"].asBool()
|
||||
|
|
|
@ -55,7 +55,7 @@ auto waybar::modules::Temperature::update() -> void {
|
|||
}
|
||||
|
||||
auto max_temp = config_["critical-threshold"].isInt() ? config_["critical-threshold"].asInt() : 0;
|
||||
label_.set_markup(fmt::format(format, fmt::arg("temperatureC", temperature_c),
|
||||
label_.set_markup(fmt::format(fmt::runtime(format), fmt::arg("temperatureC", temperature_c),
|
||||
fmt::arg("temperatureF", temperature_f),
|
||||
fmt::arg("temperatureK", temperature_k),
|
||||
fmt::arg("icon", getIcon(temperature_c, "", max_temp))));
|
||||
|
@ -64,9 +64,9 @@ auto waybar::modules::Temperature::update() -> void {
|
|||
if (config_["tooltip-format"].isString()) {
|
||||
tooltip_format = config_["tooltip-format"].asString();
|
||||
}
|
||||
label_.set_tooltip_text(fmt::format(tooltip_format, fmt::arg("temperatureC", temperature_c),
|
||||
fmt::arg("temperatureF", temperature_f),
|
||||
fmt::arg("temperatureK", temperature_k)));
|
||||
label_.set_tooltip_text(fmt::format(
|
||||
fmt::runtime(tooltip_format), fmt::arg("temperatureC", temperature_c),
|
||||
fmt::arg("temperatureF", temperature_f), fmt::arg("temperatureK", temperature_k)));
|
||||
}
|
||||
// Call parent update
|
||||
ALabel::update();
|
||||
|
|
|
@ -336,8 +336,8 @@ auto UPower::update() -> void {
|
|||
break;
|
||||
}
|
||||
std::string label_format =
|
||||
fmt::format(showAltText ? format_alt : format, fmt::arg("percentage", percentString),
|
||||
fmt::arg("time", time_format));
|
||||
fmt::format(fmt::runtime(showAltText ? format_alt : format),
|
||||
fmt::arg("percentage", percentString), fmt::arg("time", time_format));
|
||||
// Only set the label text if it doesn't only contain spaces
|
||||
bool onlySpaces = true;
|
||||
for (auto& character : label_format) {
|
||||
|
|
|
@ -29,7 +29,7 @@ UPowerTooltip::~UPowerTooltip() {}
|
|||
uint UPowerTooltip::updateTooltip(Devices& devices) {
|
||||
// Removes all old devices
|
||||
for (auto child : contentBox->get_children()) {
|
||||
child->~Widget();
|
||||
delete child;
|
||||
}
|
||||
|
||||
uint deviceCount = 0;
|
||||
|
|
|
@ -127,12 +127,12 @@ auto User::update() -> void {
|
|||
auto startSystemTime = currentSystemTime - workSystemTimeSeconds;
|
||||
long workSystemDays = uptimeSeconds / 86400;
|
||||
|
||||
auto label = fmt::format(ALabel::format_, fmt::arg("up_H", fmt::format("{:%H}", startSystemTime)),
|
||||
auto label = fmt::format(
|
||||
fmt::runtime(ALabel::format_), fmt::arg("up_H", fmt::format("{:%H}", startSystemTime)),
|
||||
fmt::arg("up_M", fmt::format("{:%M}", startSystemTime)),
|
||||
fmt::arg("up_d", fmt::format("{:%d}", startSystemTime)),
|
||||
fmt::arg("up_m", fmt::format("{:%m}", startSystemTime)),
|
||||
fmt::arg("up_Y", fmt::format("{:%Y}", startSystemTime)),
|
||||
fmt::arg("work_d", workSystemDays),
|
||||
fmt::arg("up_Y", fmt::format("{:%Y}", startSystemTime)), fmt::arg("work_d", workSystemDays),
|
||||
fmt::arg("work_H", fmt::format("{:%H}", workSystemTimeSeconds)),
|
||||
fmt::arg("work_M", fmt::format("{:%M}", workSystemTimeSeconds)),
|
||||
fmt::arg("work_S", fmt::format("{:%S}", workSystemTimeSeconds)),
|
||||
|
|
|
@ -1,15 +1,22 @@
|
|||
#include "modules/wireplumber.hpp"
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
bool isValidNodeId(uint32_t id) { return id > 0 && id < G_MAXUINT32; }
|
||||
|
||||
waybar::modules::Wireplumber::Wireplumber(const std::string& id, const Json::Value& config)
|
||||
: ALabel(config, "wireplumber", id, "{volume}%"),
|
||||
wp_core_(nullptr),
|
||||
apis_(nullptr),
|
||||
om_(nullptr),
|
||||
mixer_api_(nullptr),
|
||||
def_nodes_api_(nullptr),
|
||||
default_node_name_(nullptr),
|
||||
pending_plugins_(0),
|
||||
muted_(false),
|
||||
volume_(0.0),
|
||||
node_id_(0) {
|
||||
wp_init(WP_INIT_ALL);
|
||||
wp_init(WP_INIT_PIPEWIRE);
|
||||
wp_core_ = wp_core_new(NULL, NULL);
|
||||
apis_ = g_ptr_array_new_with_free_func(g_object_unref);
|
||||
om_ = wp_object_manager_new();
|
||||
|
@ -18,10 +25,15 @@ waybar::modules::Wireplumber::Wireplumber(const std::string& id, const Json::Val
|
|||
|
||||
loadRequiredApiModules();
|
||||
|
||||
spdlog::debug("[{}]: connecting to pipewire...", this->name_);
|
||||
|
||||
if (!wp_core_connect(wp_core_)) {
|
||||
spdlog::error("[{}]: Could not connect to PipeWire", this->name_);
|
||||
throw std::runtime_error("Could not connect to PipeWire\n");
|
||||
}
|
||||
|
||||
spdlog::debug("[{}]: connected!", this->name_);
|
||||
|
||||
g_signal_connect_swapped(om_, "installed", (GCallback)onObjectManagerInstalled, this);
|
||||
|
||||
activatePlugins();
|
||||
|
@ -33,33 +45,26 @@ waybar::modules::Wireplumber::~Wireplumber() {
|
|||
g_clear_pointer(&apis_, g_ptr_array_unref);
|
||||
g_clear_object(&om_);
|
||||
g_clear_object(&wp_core_);
|
||||
g_clear_object(&mixer_api_);
|
||||
g_clear_object(&def_nodes_api_);
|
||||
g_free(default_node_name_);
|
||||
}
|
||||
|
||||
uint32_t waybar::modules::Wireplumber::getDefaultNodeId(waybar::modules::Wireplumber* self) {
|
||||
uint32_t id;
|
||||
g_autoptr(WpPlugin) def_nodes_api = wp_plugin_find(self->wp_core_, "default-nodes-api");
|
||||
void waybar::modules::Wireplumber::updateNodeName(waybar::modules::Wireplumber* self, uint32_t id) {
|
||||
spdlog::debug("[{}]: updating node name with node.id {}", self->name_, id);
|
||||
|
||||
if (!def_nodes_api) {
|
||||
throw std::runtime_error("Default nodes API is not loaded\n");
|
||||
if (!isValidNodeId(id)) {
|
||||
spdlog::warn("[{}]: '{}' is not a valid node ID. Ignoring node name update.", self->name_, id);
|
||||
return;
|
||||
}
|
||||
|
||||
g_signal_emit_by_name(def_nodes_api, "get-default-node", "Audio/Sink", &id);
|
||||
|
||||
if (id <= 0 || id >= G_MAXUINT32) {
|
||||
auto err = fmt::format("'{}' is not a valid ID (returned by default-nodes-api)\n", id);
|
||||
throw std::runtime_error(err);
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
void waybar::modules::Wireplumber::updateNodeName(waybar::modules::Wireplumber* self) {
|
||||
auto proxy = static_cast<WpProxy*>(
|
||||
wp_object_manager_lookup(self->om_, WP_TYPE_GLOBAL_PROXY, WP_CONSTRAINT_TYPE_G_PROPERTY,
|
||||
"bound-id", "=u", self->node_id_, NULL));
|
||||
auto proxy = static_cast<WpProxy*>(wp_object_manager_lookup(
|
||||
self->om_, WP_TYPE_GLOBAL_PROXY, WP_CONSTRAINT_TYPE_G_PROPERTY, "bound-id", "=u", id, NULL));
|
||||
|
||||
if (!proxy) {
|
||||
throw std::runtime_error(fmt::format("Object '{}' not found\n", self->node_id_));
|
||||
auto err = fmt::format("Object '{}' not found\n", id);
|
||||
spdlog::error("[{}]: {}", self->name_, err);
|
||||
throw std::runtime_error(err);
|
||||
}
|
||||
|
||||
g_autoptr(WpProperties) properties =
|
||||
|
@ -73,15 +78,24 @@ void waybar::modules::Wireplumber::updateNodeName(waybar::modules::Wireplumber*
|
|||
auto description = wp_properties_get(properties, "node.description");
|
||||
|
||||
self->node_name_ = nick ? nick : description;
|
||||
spdlog::debug("[{}]: Updating node name to: {}", self->name_, self->node_name_);
|
||||
}
|
||||
|
||||
void waybar::modules::Wireplumber::updateVolume(waybar::modules::Wireplumber* self) {
|
||||
void waybar::modules::Wireplumber::updateVolume(waybar::modules::Wireplumber* self, uint32_t id) {
|
||||
spdlog::debug("[{}]: updating volume", self->name_);
|
||||
double vol;
|
||||
GVariant* variant = NULL;
|
||||
g_autoptr(WpPlugin) mixer_api = wp_plugin_find(self->wp_core_, "mixer-api");
|
||||
g_signal_emit_by_name(mixer_api, "get-volume", self->node_id_, &variant);
|
||||
|
||||
if (!isValidNodeId(id)) {
|
||||
spdlog::error("[{}]: '{}' is not a valid node ID. Ignoring volume update.", self->name_, id);
|
||||
return;
|
||||
}
|
||||
|
||||
g_signal_emit_by_name(self->mixer_api_, "get-volume", id, &variant);
|
||||
|
||||
if (!variant) {
|
||||
auto err = fmt::format("Node {} does not support volume\n", self->node_id_);
|
||||
auto err = fmt::format("Node {} does not support volume\n", id);
|
||||
spdlog::error("[{}]: {}", self->name_, err);
|
||||
throw std::runtime_error(err);
|
||||
}
|
||||
|
||||
|
@ -93,22 +107,121 @@ void waybar::modules::Wireplumber::updateVolume(waybar::modules::Wireplumber* se
|
|||
self->dp.emit();
|
||||
}
|
||||
|
||||
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, NULL));
|
||||
|
||||
if (!node) {
|
||||
spdlog::warn("[{}]: (onMixerChanged) - Object with id {} not found", self->name_, id);
|
||||
return;
|
||||
}
|
||||
|
||||
const gchar* name = wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(node), "node.name");
|
||||
|
||||
if (g_strcmp0(self->default_node_name_, name) != 0) {
|
||||
spdlog::debug(
|
||||
"[{}]: (onMixerChanged) - ignoring mixer update for node: id: {}, name: {} as it is not "
|
||||
"the default node: {}",
|
||||
self->name_, id, name, self->default_node_name_);
|
||||
return;
|
||||
}
|
||||
|
||||
spdlog::debug("[{}]: (onMixerChanged) - Need to update volume for node with id {} and name {}",
|
||||
self->name_, id, name);
|
||||
updateVolume(self, id);
|
||||
}
|
||||
|
||||
void waybar::modules::Wireplumber::onDefaultNodesApiChanged(waybar::modules::Wireplumber* self) {
|
||||
spdlog::debug("[{}]: (onDefaultNodesApiChanged)", self->name_);
|
||||
|
||||
uint32_t default_node_id;
|
||||
g_signal_emit_by_name(self->def_nodes_api_, "get-default-node", "Audio/Sink", &default_node_id);
|
||||
|
||||
if (!isValidNodeId(default_node_id)) {
|
||||
spdlog::warn("[{}]: '{}' is not a valid node ID. Ignoring node change.", self->name_,
|
||||
default_node_id);
|
||||
return;
|
||||
}
|
||||
|
||||
g_autoptr(WpNode) node = static_cast<WpNode*>(
|
||||
wp_object_manager_lookup(self->om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_G_PROPERTY, "bound-id",
|
||||
"=u", default_node_id, NULL));
|
||||
|
||||
if (!node) {
|
||||
spdlog::warn("[{}]: (onDefaultNodesApiChanged) - Object with id {} not found", self->name_,
|
||||
default_node_id);
|
||||
return;
|
||||
}
|
||||
|
||||
const gchar* default_node_name =
|
||||
wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(node), "node.name");
|
||||
|
||||
spdlog::debug(
|
||||
"[{}]: (onDefaultNodesApiChanged) - got the following default node: Node(name: {}, id: {})",
|
||||
self->name_, default_node_name, default_node_id);
|
||||
|
||||
if (g_strcmp0(self->default_node_name_, default_node_name) == 0) {
|
||||
spdlog::debug(
|
||||
"[{}]: (onDefaultNodesApiChanged) - Default node has not changed. Node(name: {}, id: {}). "
|
||||
"Ignoring.",
|
||||
self->name_, self->default_node_name_, default_node_id);
|
||||
return;
|
||||
}
|
||||
|
||||
spdlog::debug(
|
||||
"[{}]: (onDefaultNodesApiChanged) - Default node changed to -> Node(name: {}, id: {})",
|
||||
self->name_, default_node_name, default_node_id);
|
||||
|
||||
self->default_node_name_ = g_strdup(default_node_name);
|
||||
updateVolume(self, default_node_id);
|
||||
updateNodeName(self, default_node_id);
|
||||
}
|
||||
|
||||
void waybar::modules::Wireplumber::onObjectManagerInstalled(waybar::modules::Wireplumber* self) {
|
||||
self->node_id_ =
|
||||
self->config_["node-id"].isInt() ? self->config_["node-id"].asInt() : getDefaultNodeId(self);
|
||||
spdlog::debug("[{}]: onObjectManagerInstalled", self->name_);
|
||||
|
||||
g_autoptr(WpPlugin) mixer_api = wp_plugin_find(self->wp_core_, "mixer-api");
|
||||
self->def_nodes_api_ = wp_plugin_find(self->wp_core_, "default-nodes-api");
|
||||
|
||||
updateVolume(self);
|
||||
updateNodeName(self);
|
||||
g_signal_connect_swapped(mixer_api, "changed", (GCallback)updateVolume, self);
|
||||
if (!self->def_nodes_api_) {
|
||||
spdlog::error("[{}]: default nodes api is not loaded.", self->name_);
|
||||
throw std::runtime_error("Default nodes API is not loaded\n");
|
||||
}
|
||||
|
||||
self->mixer_api_ = wp_plugin_find(self->wp_core_, "mixer-api");
|
||||
|
||||
if (!self->mixer_api_) {
|
||||
spdlog::error("[{}]: mixer api is not loaded.", self->name_);
|
||||
throw std::runtime_error("Mixer api is not loaded\n");
|
||||
}
|
||||
|
||||
uint32_t default_node_id;
|
||||
g_signal_emit_by_name(self->def_nodes_api_, "get-default-configured-node-name", "Audio/Sink",
|
||||
&self->default_node_name_);
|
||||
g_signal_emit_by_name(self->def_nodes_api_, "get-default-node", "Audio/Sink", &default_node_id);
|
||||
|
||||
if (self->default_node_name_) {
|
||||
spdlog::debug("[{}]: (onObjectManagerInstalled) - default configured node name: {} and id: {}",
|
||||
self->name_, self->default_node_name_, default_node_id);
|
||||
}
|
||||
|
||||
updateVolume(self, default_node_id);
|
||||
updateNodeName(self, default_node_id);
|
||||
|
||||
g_signal_connect_swapped(self->mixer_api_, "changed", (GCallback)onMixerChanged, self);
|
||||
g_signal_connect_swapped(self->def_nodes_api_, "changed", (GCallback)onDefaultNodesApiChanged,
|
||||
self);
|
||||
}
|
||||
|
||||
void waybar::modules::Wireplumber::onPluginActivated(WpObject* p, GAsyncResult* res,
|
||||
waybar::modules::Wireplumber* self) {
|
||||
auto plugin_name = wp_plugin_get_name(WP_PLUGIN(p));
|
||||
spdlog::debug("[{}]: onPluginActivated: {}", self->name_, plugin_name);
|
||||
g_autoptr(GError) error = NULL;
|
||||
|
||||
if (!wp_object_activate_finish(p, res, &error)) {
|
||||
spdlog::error("[{}]: error activating plugin: {}", self->name_, error->message);
|
||||
throw std::runtime_error(error->message);
|
||||
}
|
||||
|
||||
|
@ -118,6 +231,7 @@ void waybar::modules::Wireplumber::onPluginActivated(WpObject* p, GAsyncResult*
|
|||
}
|
||||
|
||||
void waybar::modules::Wireplumber::activatePlugins() {
|
||||
spdlog::debug("[{}]: activating plugins", name_);
|
||||
for (uint16_t i = 0; i < apis_->len; i++) {
|
||||
WpPlugin* plugin = static_cast<WpPlugin*>(g_ptr_array_index(apis_, i));
|
||||
pending_plugins_++;
|
||||
|
@ -127,13 +241,13 @@ void waybar::modules::Wireplumber::activatePlugins() {
|
|||
}
|
||||
|
||||
void waybar::modules::Wireplumber::prepare() {
|
||||
wp_object_manager_add_interest(om_, WP_TYPE_NODE, NULL);
|
||||
wp_object_manager_add_interest(om_, WP_TYPE_GLOBAL_PROXY, NULL);
|
||||
wp_object_manager_request_object_features(om_, WP_TYPE_GLOBAL_PROXY,
|
||||
WP_PIPEWIRE_OBJECT_FEATURES_MINIMAL);
|
||||
spdlog::debug("[{}]: preparing object manager", name_);
|
||||
wp_object_manager_add_interest(om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_PW_PROPERTY, "media.class",
|
||||
"=s", "Audio/Sink", NULL);
|
||||
}
|
||||
|
||||
void waybar::modules::Wireplumber::loadRequiredApiModules() {
|
||||
spdlog::debug("[{}]: loading required modules", name_);
|
||||
g_autoptr(GError) error = NULL;
|
||||
|
||||
if (!wp_core_load_component(wp_core_, "libwireplumber-module-default-nodes-api", "module", NULL,
|
||||
|
@ -165,7 +279,7 @@ auto waybar::modules::Wireplumber::update() -> void {
|
|||
label_.get_style_context()->remove_class("muted");
|
||||
}
|
||||
|
||||
std::string markup = fmt::format(format, fmt::arg("node_name", node_name_),
|
||||
std::string markup = fmt::format(fmt::runtime(format), fmt::arg("node_name", node_name_),
|
||||
fmt::arg("volume", volume_), fmt::arg("icon", getIcon(volume_)));
|
||||
label_.set_markup(markup);
|
||||
|
||||
|
@ -177,9 +291,9 @@ auto waybar::modules::Wireplumber::update() -> void {
|
|||
}
|
||||
|
||||
if (!tooltip_format.empty()) {
|
||||
label_.set_tooltip_text(fmt::format(tooltip_format, fmt::arg("node_name", node_name_),
|
||||
fmt::arg("volume", volume_),
|
||||
fmt::arg("icon", getIcon(volume_))));
|
||||
label_.set_tooltip_text(
|
||||
fmt::format(fmt::runtime(tooltip_format), fmt::arg("node_name", node_name_),
|
||||
fmt::arg("volume", volume_), fmt::arg("icon", getIcon(volume_))));
|
||||
} else {
|
||||
label_.set_tooltip_text(node_name_);
|
||||
}
|
||||
|
|
|
@ -102,8 +102,11 @@ Glib::RefPtr<Gio::DesktopAppInfo> get_desktop_app_info(const std::string &app_id
|
|||
desktop_file = desktop_list[0][i];
|
||||
} else {
|
||||
auto tmp_info = Gio::DesktopAppInfo::create(desktop_list[0][i]);
|
||||
auto startup_class = tmp_info->get_startup_wm_class();
|
||||
if (!tmp_info)
|
||||
// see https://github.com/Alexays/Waybar/issues/1446
|
||||
continue;
|
||||
|
||||
auto startup_class = tmp_info->get_startup_wm_class();
|
||||
if (startup_class == app_id) {
|
||||
desktop_file = desktop_list[0][i];
|
||||
break;
|
||||
|
@ -615,7 +618,8 @@ void Task::update() {
|
|||
app_id = Glib::Markup::escape_text(app_id);
|
||||
}
|
||||
if (!format_before_.empty()) {
|
||||
auto txt = fmt::format(format_before_, fmt::arg("title", title), fmt::arg("name", name),
|
||||
auto txt =
|
||||
fmt::format(fmt::runtime(format_before_), fmt::arg("title", title), fmt::arg("name", name),
|
||||
fmt::arg("app_id", app_id), fmt::arg("state", state_string()),
|
||||
fmt::arg("short_state", state_string(true)));
|
||||
if (markup)
|
||||
|
@ -625,7 +629,8 @@ void Task::update() {
|
|||
text_before_.show();
|
||||
}
|
||||
if (!format_after_.empty()) {
|
||||
auto txt = fmt::format(format_after_, fmt::arg("title", title), fmt::arg("name", name),
|
||||
auto txt =
|
||||
fmt::format(fmt::runtime(format_after_), fmt::arg("title", title), fmt::arg("name", name),
|
||||
fmt::arg("app_id", app_id), fmt::arg("state", state_string()),
|
||||
fmt::arg("short_state", state_string(true)));
|
||||
if (markup)
|
||||
|
@ -636,7 +641,8 @@ void Task::update() {
|
|||
}
|
||||
|
||||
if (!format_tooltip_.empty()) {
|
||||
auto txt = fmt::format(format_tooltip_, fmt::arg("title", title), fmt::arg("name", name),
|
||||
auto txt =
|
||||
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)));
|
||||
if (markup)
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
#include "client.hpp"
|
||||
#include "gtkmm/widget.h"
|
||||
#include "modules/wlr/workspace_manager_binding.hpp"
|
||||
|
||||
|
@ -166,8 +167,20 @@ WorkspaceManager::~WorkspaceManager() {
|
|||
return;
|
||||
}
|
||||
|
||||
wl_display *display = Client::inst()->wl_display;
|
||||
|
||||
// Send `stop` request and wait for one roundtrip. This is not quite correct as
|
||||
// the protocol encourages us to wait for the .finished event, but it should work
|
||||
// with wlroots workspace manager implementation.
|
||||
zext_workspace_manager_v1_stop(workspace_manager_);
|
||||
wl_display_roundtrip(display);
|
||||
|
||||
// If the .finished handler is still not executed, destroy the workspace manager here.
|
||||
if (workspace_manager_) {
|
||||
spdlog::warn("Foreign toplevel manager destroyed before .finished event");
|
||||
zext_workspace_manager_v1_destroy(workspace_manager_);
|
||||
workspace_manager_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
auto WorkspaceManager::remove_workspace_group(uint32_t id) -> void {
|
||||
|
@ -366,7 +379,7 @@ Workspace::~Workspace() {
|
|||
}
|
||||
|
||||
auto Workspace::update() -> void {
|
||||
label_.set_markup(fmt::format(format_, fmt::arg("name", name_),
|
||||
label_.set_markup(fmt::format(fmt::runtime(format_), fmt::arg("name", name_),
|
||||
fmt::arg("icon", with_icon_ ? get_icon() : "")));
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,11 @@
|
|||
|
||||
#include <glibmm.h>
|
||||
|
||||
#include <catch2/catch_all.hpp>
|
||||
#if __has_include(<catch2/catch_test_macros.hpp>)
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#else
|
||||
#include <catch2/catch.hpp>
|
||||
#endif
|
||||
#include <thread>
|
||||
#include <type_traits>
|
||||
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
#include "config.hpp"
|
||||
|
||||
#include <catch2/catch_all.hpp>
|
||||
#if __has_include(<catch2/catch_test_macros.hpp>)
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#else
|
||||
#include <catch2/catch.hpp>
|
||||
#endif
|
||||
|
||||
TEST_CASE("Load simple config", "[config]") {
|
||||
waybar::Config conf;
|
||||
|
|
|
@ -0,0 +1,162 @@
|
|||
#include "util/date.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <ctime>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
|
||||
#if __has_include(<catch2/catch_test_macros.hpp>)
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/matchers/catch_matchers_all.hpp>
|
||||
#else
|
||||
#include <catch2/catch.hpp>
|
||||
#endif
|
||||
|
||||
#ifndef SKIP
|
||||
#define SKIP(...) \
|
||||
WARN(__VA_ARGS__); \
|
||||
return
|
||||
#endif
|
||||
|
||||
using namespace std::literals::chrono_literals;
|
||||
|
||||
/*
|
||||
* Check that the date/time formatter with locale and timezone support is working as expected.
|
||||
*/
|
||||
|
||||
const date::zoned_time<std::chrono::seconds> TEST_TIME = date::zoned_time{
|
||||
"UTC", date::local_days{date::Monday[1] / date::January / 2022} + 13h + 4min + 5s};
|
||||
|
||||
/*
|
||||
* Check if the date formatted with LC_TIME=en_US is within expectations.
|
||||
*
|
||||
* The check expects Glibc output style and will fail with FreeBSD (different implementation)
|
||||
* or musl (no implementation).
|
||||
*/
|
||||
static const bool LC_TIME_is_sane = []() {
|
||||
try {
|
||||
std::stringstream ss;
|
||||
ss.imbue(std::locale("en_US.UTF-8"));
|
||||
|
||||
time_t t = 1641211200;
|
||||
std::tm tm = *std::gmtime(&t);
|
||||
|
||||
ss << std::put_time(&tm, "%x %X");
|
||||
return ss.str() == "01/03/2022 12:00:00 PM";
|
||||
} catch (std::exception &) {
|
||||
return false;
|
||||
}
|
||||
}();
|
||||
|
||||
TEST_CASE("Format UTC time", "[clock][util]") {
|
||||
const auto loc = std::locale("C");
|
||||
const auto tm = TEST_TIME;
|
||||
|
||||
CHECK(fmt::format(loc, "{}", tm).empty()); // no format specified
|
||||
CHECK(fmt::format(loc, "{:%c %Z}", tm) == "Mon Jan 3 13:04:05 2022 UTC");
|
||||
CHECK(fmt::format(loc, "{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103130405");
|
||||
|
||||
if (!LC_TIME_is_sane) {
|
||||
SKIP("Locale support check failed, skip tests");
|
||||
}
|
||||
|
||||
/* Test a few locales that are most likely to be present */
|
||||
SECTION("US locale") {
|
||||
try {
|
||||
const auto loc = std::locale("en_US.UTF-8");
|
||||
|
||||
CHECK(fmt::format(loc, "{}", tm).empty()); // no format specified
|
||||
CHECK_THAT(fmt::format(loc, "{:%c}", tm), // HowardHinnant/date#704
|
||||
Catch::Matchers::StartsWith("Mon 03 Jan 2022 01:04:05 PM"));
|
||||
CHECK(fmt::format(loc, "{:%x %X}", tm) == "01/03/2022 01:04:05 PM");
|
||||
CHECK(fmt::format(loc, "{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103130405");
|
||||
} catch (const std::runtime_error &) {
|
||||
WARN("Locale en_US not found, skip tests");
|
||||
}
|
||||
}
|
||||
SECTION("GB locale") {
|
||||
try {
|
||||
const auto loc = std::locale("en_GB.UTF-8");
|
||||
|
||||
CHECK(fmt::format(loc, "{}", tm).empty()); // no format specified
|
||||
CHECK_THAT(fmt::format(loc, "{:%c}", tm), // HowardHinnant/date#704
|
||||
Catch::Matchers::StartsWith("Mon 03 Jan 2022 13:04:05"));
|
||||
CHECK(fmt::format(loc, "{:%x %X}", tm) == "03/01/22 13:04:05");
|
||||
CHECK(fmt::format(loc, "{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103130405");
|
||||
} catch (const std::runtime_error &) {
|
||||
WARN("Locale en_GB not found, skip tests");
|
||||
}
|
||||
}
|
||||
SECTION("Global locale") {
|
||||
try {
|
||||
const auto loc = std::locale::global(std::locale("en_US.UTF-8"));
|
||||
|
||||
CHECK(fmt::format("{}", tm).empty()); // no format specified
|
||||
CHECK_THAT(fmt::format("{:%c}", tm), // HowardHinnant/date#704
|
||||
Catch::Matchers::StartsWith("Mon 03 Jan 2022 01:04:05 PM"));
|
||||
CHECK(fmt::format("{:%x %X}", tm) == "01/03/2022 01:04:05 PM");
|
||||
CHECK(fmt::format("{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103130405");
|
||||
|
||||
std::locale::global(loc);
|
||||
} catch (const std::runtime_error &) {
|
||||
WARN("Locale en_US not found, skip tests");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Format zoned time", "[clock][util]") {
|
||||
const auto loc = std::locale("C");
|
||||
const auto tm = date::zoned_time{"America/New_York", TEST_TIME};
|
||||
|
||||
CHECK(fmt::format(loc, "{}", tm).empty()); // no format specified
|
||||
CHECK(fmt::format(loc, "{:%c %Z}", tm) == "Mon Jan 3 08:04:05 2022 EST");
|
||||
CHECK(fmt::format(loc, "{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103080405");
|
||||
|
||||
if (!LC_TIME_is_sane) {
|
||||
SKIP("Locale support check failed, skip tests");
|
||||
}
|
||||
|
||||
/* Test a few locales that are most likely to be present */
|
||||
SECTION("US locale") {
|
||||
try {
|
||||
const auto loc = std::locale("en_US.UTF-8");
|
||||
|
||||
CHECK(fmt::format(loc, "{}", tm).empty()); // no format specified
|
||||
CHECK_THAT(fmt::format(loc, "{:%c}", tm), // HowardHinnant/date#704
|
||||
Catch::Matchers::StartsWith("Mon 03 Jan 2022 08:04:05 AM"));
|
||||
CHECK(fmt::format(loc, "{:%x %X}", tm) == "01/03/2022 08:04:05 AM");
|
||||
CHECK(fmt::format(loc, "{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103080405");
|
||||
} catch (const std::runtime_error &) {
|
||||
WARN("Locale en_US not found, skip tests");
|
||||
}
|
||||
}
|
||||
SECTION("GB locale") {
|
||||
try {
|
||||
const auto loc = std::locale("en_GB.UTF-8");
|
||||
|
||||
CHECK(fmt::format(loc, "{}", tm).empty()); // no format specified
|
||||
CHECK_THAT(fmt::format(loc, "{:%c}", tm), // HowardHinnant/date#704
|
||||
Catch::Matchers::StartsWith("Mon 03 Jan 2022 08:04:05"));
|
||||
CHECK(fmt::format(loc, "{:%x %X}", tm) == "03/01/22 08:04:05");
|
||||
CHECK(fmt::format(loc, "{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103080405");
|
||||
} catch (const std::runtime_error &) {
|
||||
WARN("Locale en_GB not found, skip tests");
|
||||
}
|
||||
}
|
||||
SECTION("Global locale") {
|
||||
try {
|
||||
const auto loc = std::locale::global(std::locale("en_US.UTF-8"));
|
||||
|
||||
CHECK(fmt::format("{}", tm).empty()); // no format specified
|
||||
CHECK_THAT(fmt::format("{:%c}", tm), // HowardHinnant/date#704
|
||||
Catch::Matchers::StartsWith("Mon 03 Jan 2022 08:04:05 AM"));
|
||||
CHECK(fmt::format("{:%x %X}", tm) == "01/03/2022 08:04:05 AM");
|
||||
CHECK(fmt::format("{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103080405");
|
||||
|
||||
std::locale::global(loc);
|
||||
} catch (const std::runtime_error &) {
|
||||
WARN("Locale en_US not found, skip tests");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,8 +3,13 @@
|
|||
#include <spdlog/sinks/stdout_sinks.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#if __has_include(<catch2/catch_all.hpp>)
|
||||
#include <catch2/catch_all.hpp>
|
||||
#include <catch2/reporters/catch_reporter_tap.hpp>
|
||||
#else
|
||||
#include <catch2/catch.hpp>
|
||||
#include <catch2/catch_reporter_tap.hpp>
|
||||
#endif
|
||||
#include <memory>
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
|
@ -13,10 +18,16 @@ int main(int argc, char* argv[]) {
|
|||
|
||||
session.applyCommandLine(argc, argv);
|
||||
const auto logger = spdlog::default_logger();
|
||||
#if CATCH_VERSION_MAJOR >= 3
|
||||
for (const auto& spec : session.config().getReporterSpecs()) {
|
||||
if (spec.name() == "tap") {
|
||||
const auto& reporter_name = spec.name();
|
||||
#else
|
||||
{
|
||||
const auto& reporter_name = session.config().getReporterName();
|
||||
#endif
|
||||
if (reporter_name == "tap") {
|
||||
spdlog::set_pattern("# [%l] %v");
|
||||
} else if (spec.name() == "compact") {
|
||||
} else if (reporter_name == "compact") {
|
||||
logger->sinks().clear();
|
||||
} else {
|
||||
logger->sinks().assign({std::make_shared<spdlog::sinks::stderr_sink_st>()});
|
||||
|
|
|
@ -15,7 +15,7 @@ test_src = files(
|
|||
|
||||
if tz_dep.found()
|
||||
test_dep += tz_dep
|
||||
test_src += files('waybar_time.cpp')
|
||||
test_src += files('date.cpp')
|
||||
endif
|
||||
|
||||
waybar_test = executable(
|
||||
|
|
|
@ -1,90 +0,0 @@
|
|||
#include "util/waybar_time.hpp"
|
||||
|
||||
#include <date/date.h>
|
||||
#include <date/tz.h>
|
||||
|
||||
#include <catch2/catch_all.hpp>
|
||||
#include <chrono>
|
||||
#include <stdexcept>
|
||||
|
||||
using namespace std::literals::chrono_literals;
|
||||
|
||||
/*
|
||||
* Check that the date/time formatter with locale and timezone support is working as expected.
|
||||
*/
|
||||
|
||||
const date::zoned_time<std::chrono::seconds> TEST_TIME = date::make_zoned(
|
||||
"UTC", date::local_days{date::Monday[1] / date::January / 2022} + 13h + 4min + 5s);
|
||||
|
||||
TEST_CASE("Format UTC time", "[clock][util]") {
|
||||
waybar::waybar_time tm{std::locale("C"), TEST_TIME};
|
||||
|
||||
REQUIRE(fmt::format("{}", tm).empty()); // no format specified
|
||||
REQUIRE(fmt::format("{:%c %Z}", tm) == "Mon Jan 3 13:04:05 2022 UTC");
|
||||
REQUIRE(fmt::format("{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103130405");
|
||||
|
||||
/* Test a few locales that are most likely to be present */
|
||||
SECTION("US locale") {
|
||||
try {
|
||||
tm.locale = std::locale("en_US");
|
||||
|
||||
REQUIRE(fmt::format("{}", tm).empty()); // no format specified
|
||||
REQUIRE_THAT(fmt::format("{:%c}", tm), // HowardHinnant/date#704
|
||||
Catch::Matchers::StartsWith("Mon 03 Jan 2022 01:04:05 PM"));
|
||||
REQUIRE(fmt::format("{:%x %X}", tm) == "01/03/2022 01:04:05 PM");
|
||||
REQUIRE(fmt::format("{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103130405");
|
||||
} catch (const std::runtime_error&) {
|
||||
// locale not found; ignore
|
||||
}
|
||||
}
|
||||
SECTION("GB locale") {
|
||||
try {
|
||||
tm.locale = std::locale("en_GB");
|
||||
|
||||
REQUIRE(fmt::format("{}", tm).empty()); // no format specified
|
||||
REQUIRE_THAT(fmt::format("{:%c}", tm), // HowardHinnant/date#704
|
||||
Catch::Matchers::StartsWith("Mon 03 Jan 2022 13:04:05"));
|
||||
REQUIRE(fmt::format("{:%x %X}", tm) == "03/01/22 13:04:05");
|
||||
REQUIRE(fmt::format("{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103130405");
|
||||
} catch (const std::runtime_error&) {
|
||||
// locale not found; ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Format zoned time", "[clock][util]") {
|
||||
waybar::waybar_time tm{std::locale("C"), date::make_zoned("America/New_York", TEST_TIME)};
|
||||
|
||||
REQUIRE(fmt::format("{}", tm).empty()); // no format specified
|
||||
REQUIRE(fmt::format("{:%c %Z}", tm) == "Mon Jan 3 08:04:05 2022 EST");
|
||||
REQUIRE(fmt::format("{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103080405");
|
||||
|
||||
/* Test a few locales that are most likely to be present */
|
||||
SECTION("US locale") {
|
||||
try {
|
||||
tm.locale = std::locale("en_US");
|
||||
|
||||
REQUIRE(fmt::format("{}", tm).empty()); // no format specified
|
||||
REQUIRE_THAT(fmt::format("{:%c}", tm), // HowardHinnant/date#704
|
||||
Catch::Matchers::StartsWith("Mon 03 Jan 2022 08:04:05 AM"));
|
||||
REQUIRE(fmt::format("{:%x %X}", tm) == "01/03/2022 08:04:05 AM");
|
||||
REQUIRE(fmt::format("{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103080405");
|
||||
} catch (const std::runtime_error&) {
|
||||
// locale not found; ignore
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("GB locale") {
|
||||
try {
|
||||
tm.locale = std::locale("en_GB");
|
||||
|
||||
REQUIRE(fmt::format("{}", tm).empty()); // no format specified
|
||||
REQUIRE_THAT(fmt::format("{:%c}", tm), // HowardHinnant/date#704
|
||||
Catch::Matchers::StartsWith("Mon 03 Jan 2022 08:04:05"));
|
||||
REQUIRE(fmt::format("{:%x %X}", tm) == "03/01/22 08:04:05");
|
||||
REQUIRE(fmt::format("{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103080405");
|
||||
} catch (const std::runtime_error&) {
|
||||
// locale not found; ignore
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue