Merge branch 'master' into add-river-layout

This commit is contained in:
Alexander Courtis 2023-02-11 13:22:46 +11:00
commit 5e4a47c0a8
72 changed files with 1595 additions and 619 deletions

1
.envrc.sample Normal file
View File

@ -0,0 +1 @@
use flake

View File

@ -9,7 +9,7 @@ jobs:
# https://github.com/actions/virtual-environments/issues/4060 - for lack of VirtualBox on MacOS 11 runners # https://github.com/actions/virtual-environments/issues/4060 - for lack of VirtualBox on MacOS 11 runners
runs-on: macos-12 runs-on: macos-12
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Test in FreeBSD VM - name: Test in FreeBSD VM
uses: vmactions/freebsd-vm@v0 uses: vmactions/freebsd-vm@v0
with: with:

View File

@ -6,7 +6,7 @@ jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: DoozyX/clang-format-lint-action@v0.13 - uses: DoozyX/clang-format-lint-action@v0.13
with: with:
source: '.' source: '.'

View File

@ -13,16 +13,20 @@ jobs:
- fedora - fedora
- opensuse - opensuse
- gentoo - gentoo
cpp_std: [c++17]
include:
- distro: fedora
cpp_std: c++20
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: container:
image: alexays/waybar:${{ matrix.distro }} image: alexays/waybar:${{ matrix.distro }}
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: configure - name: configure
run: meson -Dman-pages=enabled build run: meson -Dman-pages=enabled -Dcpp_std=${{matrix.cpp_std}} build
- name: build - name: build
run: ninja -C build run: ninja -C build
- name: test - name: test
run: meson test -C build --no-rebuild --print-errorlogs --suite waybar run: meson test -C build --no-rebuild --verbose --suite waybar

1
.gitignore vendored
View File

@ -43,3 +43,4 @@ packagecache
*.exe *.exe
*.out *.out
*.app *.app
/.direnv/

View File

@ -3,4 +3,5 @@
FROM archlinux:base-devel FROM archlinux:base-devel
RUN pacman -Syu --noconfirm && \ RUN pacman -Syu --noconfirm && \
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 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

View File

@ -2,12 +2,33 @@
FROM fedora:latest FROM fedora:latest
RUN dnf install -y @c-development git-core meson scdoc 'pkgconfig(date)' \ RUN dnf install -y @c-development \
'pkgconfig(dbusmenu-gtk3-0.4)' 'pkgconfig(fmt)' 'pkgconfig(gdk-pixbuf-2.0)' \ git-core glibc-langpack-en meson scdoc \
'pkgconfig(gio-unix-2.0)' 'pkgconfig(gtk-layer-shell-0)' 'pkgconfig(gtkmm-3.0)' \ 'pkgconfig(catch2)' \
'pkgconfig(jsoncpp)' 'pkgconfig(libinput)' 'pkgconfig(libmpdclient)' \ 'pkgconfig(date)' \
'pkgconfig(libnl-3.0)' 'pkgconfig(libnl-genl-3.0)' 'pkgconfig(libpulse)' \ 'pkgconfig(dbusmenu-gtk3-0.4)' \
'pkgconfig(libudev)' 'pkgconfig(pugixml)' 'pkgconfig(sigc++-2.0)' 'pkgconfig(spdlog)' \ 'pkgconfig(fmt)' \
'pkgconfig(wayland-client)' 'pkgconfig(wayland-cursor)' 'pkgconfig(wayland-protocols)' 'pkgconfig(xkbregistry)' \ 'pkgconfig(gdk-pixbuf-2.0)' \
'pkgconfig(playerctl)' && \ '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 dnf clean all -y

94
flake.lock Normal file
View File

@ -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
}

65
flake.nix Normal file
View File

@ -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; [
];
};
});
}

View File

@ -15,6 +15,7 @@ class AModule : public IModule {
bool enable_scroll = false); bool enable_scroll = false);
virtual ~AModule(); virtual ~AModule();
virtual auto update() -> void; virtual auto update() -> void;
virtual auto refresh(int) -> void{};
virtual operator Gtk::Widget &(); virtual operator Gtk::Widget &();
Glib::Dispatcher dp; Glib::Dispatcher dp;

View File

@ -1,7 +1,7 @@
#pragma once #pragma once
#include <json/json.h> #include <json/json.h>
#ifdef HAVE_LIBDATE #if defined(HAVE_CHRONO_TIMEZONES) || defined(HAVE_LIBDATE)
#include "modules/clock.hpp" #include "modules/clock.hpp"
#else #else
#include "modules/simpleclock.hpp" #include "modules/simpleclock.hpp"

View File

@ -6,6 +6,7 @@
#include <vector> #include <vector>
#include "ALabel.hpp" #include "ALabel.hpp"
#include "giomm/dbusproxy.h"
#include "util/json.hpp" #include "util/json.hpp"
#include "util/sleeper_thread.hpp" #include "util/sleeper_thread.hpp"
@ -50,6 +51,8 @@ class Backlight : public ALabel {
template <class ForwardIt, class Inserter> template <class ForwardIt, class Inserter>
static void enumerate_devices(ForwardIt first, ForwardIt last, Inserter inserter, udev *udev); static void enumerate_devices(ForwardIt first, ForwardIt last, Inserter inserter, udev *udev);
bool handleScroll(GdkEventScroll *e);
const std::string preferred_device_; const std::string preferred_device_;
static constexpr int EPOLL_MAX_EVENTS = 16; static constexpr int EPOLL_MAX_EVENTS = 16;
@ -60,5 +63,7 @@ class Backlight : public ALabel {
std::vector<BacklightDev> devices_; std::vector<BacklightDev> devices_;
// thread must destruct before shared data // thread must destruct before shared data
util::SleeperThread udev_thread_; util::SleeperThread udev_thread_;
Glib::RefPtr<Gio::DBus::Proxy> login_proxy_;
}; };
} // namespace waybar::modules } // namespace waybar::modules

View File

@ -1,19 +1,22 @@
#pragma once #pragma once
#include <date/tz.h>
#include "ALabel.hpp" #include "ALabel.hpp"
#include "util/date.hpp"
#include "util/sleeper_thread.hpp" #include "util/sleeper_thread.hpp"
namespace waybar { namespace waybar::modules {
struct waybar_time;
namespace modules {
const std::string kCalendarPlaceholder = "calendar"; const std::string kCalendarPlaceholder = "calendar";
const std::string KTimezonedTimeListPlaceholder = "timezoned_time_list"; const std::string KTimezonedTimeListPlaceholder = "timezoned_time_list";
enum class WeeksSide {
LEFT,
RIGHT,
HIDDEN,
};
enum class CldMode { MONTH, YEAR };
class Clock : public ALabel { class Clock : public ALabel {
public: public:
Clock(const std::string&, const Json::Value&); Clock(const std::string&, const Json::Value&);
@ -22,26 +25,37 @@ class Clock : public ALabel {
private: private:
util::SleeperThread thread_; util::SleeperThread thread_;
std::map<std::pair<uint, GdkEventType>, void (waybar::modules::Clock::*)()> eventMap_;
std::locale locale_; std::locale locale_;
std::vector<const date::time_zone*> time_zones_; std::vector<const date::time_zone*> time_zones_;
int current_time_zone_idx_; 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_calendar_in_tooltip_;
bool is_timezoned_list_in_tooltip_; bool is_timezoned_list_in_tooltip_;
bool handleScroll(GdkEventScroll* e); bool handleScroll(GdkEventScroll* e);
bool handleToggle(GdkEventButton* const& e);
std::string fmt_str_weeks_;
std::string fmt_str_calendar_;
int fmt_weeks_left_pad_{0};
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; auto first_day_of_week() -> date::weekday;
const date::time_zone* current_timezone(); const date::time_zone* current_timezone();
bool is_timezone_fixed(); bool is_timezone_fixed();
auto timezones_text(std::chrono::system_clock::time_point* now) -> std::string; 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::modules
} // namespace waybar

View File

@ -7,6 +7,7 @@
#include <string> #include <string>
#include "ALabel.hpp" #include "ALabel.hpp"
#include "gtkmm/box.h"
#include "util/command.hpp" #include "util/command.hpp"
#include "util/json.hpp" #include "util/json.hpp"
#include "util/sleeper_thread.hpp" #include "util/sleeper_thread.hpp"
@ -15,7 +16,7 @@ namespace waybar::modules {
class Image : public AModule { class Image : public AModule {
public: public:
Image(const std::string&, const std::string&, const Json::Value&); Image(const std::string&, const Json::Value&);
auto update() -> void; auto update() -> void;
void refresh(int /*signal*/); void refresh(int /*signal*/);
@ -23,6 +24,7 @@ class Image : public AModule {
void delayWorker(); void delayWorker();
void handleEvent(); void handleEvent();
Gtk::Box box_;
Gtk::Image image_; Gtk::Image image_;
std::string path_; std::string path_;
int size_; int size_;

View File

@ -8,6 +8,7 @@
#include <cstring> #include <cstring>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <string>
#include "ipc.hpp" #include "ipc.hpp"
#include "util/sleeper_thread.hpp" #include "util/sleeper_thread.hpp"

View File

@ -19,10 +19,11 @@ class Window : public AIconLabel, public sigc::trackable {
auto update() -> void; auto update() -> void;
private: private:
void setClass(std::string classname, bool enable);
void onEvent(const struct Ipc::ipc_response&); void onEvent(const struct Ipc::ipc_response&);
void onCmd(const struct Ipc::ipc_response&); void onCmd(const struct Ipc::ipc_response&);
std::tuple<std::size_t, int, std::string, std::string, std::string, std::string> getFocusedNode( std::tuple<std::size_t, int, int, std::string, std::string, std::string, std::string, std::string>
const Json::Value& nodes, std::string& output); getFocusedNode(const Json::Value& nodes, std::string& output);
void getTree(); void getTree();
void updateAppIconName(); void updateAppIconName();
void updateAppIcon(); void updateAppIcon();
@ -32,12 +33,14 @@ class Window : public AIconLabel, public sigc::trackable {
int windowId_; int windowId_;
std::string app_id_; std::string app_id_;
std::string app_class_; std::string app_class_;
std::string layout_;
std::string old_app_id_; std::string old_app_id_;
std::size_t app_nb_; std::size_t app_nb_;
std::string shell_; std::string shell_;
unsigned app_icon_size_{24}; unsigned app_icon_size_{24};
bool update_app_icon_{true}; bool update_app_icon_{true};
std::string app_icon_name_; std::string app_icon_name_;
int floating_count_;
util::JsonParser parser_; util::JsonParser parser_;
std::mutex mutex_; std::mutex mutex_;
Ipc ipc_; Ipc ipc_;

View File

@ -4,6 +4,7 @@
#include <gtkmm/button.h> #include <gtkmm/button.h>
#include <gtkmm/label.h> #include <gtkmm/label.h>
#include <string_view>
#include <unordered_map> #include <unordered_map>
#include "AModule.hpp" #include "AModule.hpp"
@ -21,7 +22,9 @@ class Workspaces : public AModule, public sigc::trackable {
auto update() -> void; auto update() -> void;
private: 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); static int convertWorkspaceNameToNum(std::string name);

View File

@ -20,15 +20,19 @@ class Wireplumber : public ALabel {
void loadRequiredApiModules(); void loadRequiredApiModules();
void prepare(); void prepare();
void activatePlugins(); void activatePlugins();
static void updateVolume(waybar::modules::Wireplumber* self); static void updateVolume(waybar::modules::Wireplumber* self, uint32_t id);
static void updateNodeName(waybar::modules::Wireplumber* self); static void updateNodeName(waybar::modules::Wireplumber* self, uint32_t id);
static uint32_t getDefaultNodeId(waybar::modules::Wireplumber* self);
static void onPluginActivated(WpObject* p, GAsyncResult* res, waybar::modules::Wireplumber* self); static void onPluginActivated(WpObject* p, GAsyncResult* res, waybar::modules::Wireplumber* self);
static void onObjectManagerInstalled(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_; WpCore* wp_core_;
GPtrArray* apis_; GPtrArray* apis_;
WpObjectManager* om_; WpObjectManager* om_;
WpPlugin* mixer_api_;
WpPlugin* def_nodes_api_;
gchar* default_node_name_;
uint32_t pending_plugins_; uint32_t pending_plugins_;
bool muted_; bool muted_;
double volume_; double volume_;

60
include/util/date.hpp Normal file
View File

@ -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));
}
};

View File

@ -66,9 +66,9 @@ struct formatter<pow_format> {
std::string string; std::string string;
switch (spec) { switch (spec) {
case '>': case '>':
return format_to(ctx.out(), "{:>{}}", fmt::format("{}", s), max_width); return fmt::format_to(ctx.out(), "{:>{}}", fmt::format("{}", s), max_width);
case '<': case '<':
return format_to(ctx.out(), "{:<{}}", fmt::format("{}", s), max_width); return fmt::format_to(ctx.out(), "{:<{}}", fmt::format("{}", s), max_width);
case '=': case '=':
format = "{coefficient:<{number_width}.1f}{padding}{prefix}{unit}"; format = "{coefficient:<{number_width}.1f}{padding}{prefix}{unit}";
break; break;
@ -77,8 +77,8 @@ struct formatter<pow_format> {
format = "{coefficient:.1f}{prefix}{unit}"; format = "{coefficient:.1f}{prefix}{unit}";
break; break;
} }
return format_to( return fmt::format_to(
ctx.out(), format, fmt::arg("coefficient", fraction), ctx.out(), fmt::runtime(format), fmt::arg("coefficient", fraction),
fmt::arg("number_width", number_width), fmt::arg("number_width", number_width),
fmt::arg("prefix", std::string() + units[pow] + ((s.binary_ && pow) ? "i" : "")), fmt::arg("prefix", std::string() + units[pow] + ((s.binary_ && pow) ? "i" : "")),
fmt::arg("unit", s.unit_), fmt::arg("unit", s.unit_),

View File

@ -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));
}
};

View File

@ -58,16 +58,25 @@ The *backlight* module displays the current backlight level.
*on-scroll-up*: ++ *on-scroll-up*: ++
typeof: string ++ 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*: ++ *on-scroll-down*: ++
typeof: string 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*: ++ *smooth-scrolling-threshold*: ++
typeof: double typeof: double
Threshold to be used when scrolling. 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: # EXAMPLE:
``` ```

View File

@ -1,4 +1,4 @@
waybar-custom(5) waybar-image(5)
# NAME # NAME
@ -10,12 +10,13 @@ The *image* module displays an image from a path.
# CONFIGURATION # CONFIGURATION
Addressed by *custom/<name>*
*path*: ++ *path*: ++
typeof: string ++ typeof: string ++
The path to the image. 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*: ++ *size*: ++
typeof: integer ++ typeof: integer ++
The width/height to render the image. The width/height to render the image.
@ -58,15 +59,15 @@ Addressed by *custom/<name>*
# EXAMPLES # EXAMPLES
## Spotify:
## mpd:
``` ```
"image/album-art": { "image#album-art": {
"path": "/tmp/mpd_art", "path": "/tmp/mpd_art",
"size": 32, "size": 32,
"interval": 5, "interval": 5,
"on-click": "mpc toggle" "on-click": "mpc toggle"
} }
``` ```
# STYLE
- *#image*

View File

@ -66,6 +66,25 @@ Addressed by *sway/window*
default: true ++ default: true ++
Option to disable tooltip on hover. 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*: ++ *rewrite*: ++
typeof: object ++ typeof: object ++
Rules to rewrite window title. See *rewrite rules*. Rules to rewrite window title. See *rewrite rules*.
@ -117,6 +136,10 @@ Invalid expressions (e.g., mismatched parentheses) are skipped.
# STYLE # STYLE
- *#window* - *#window*
- *window#waybar.empty* When no windows 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 window is in the workspace - *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 - *window#waybar.<app_id>* Where *app_id* is the app_id or *instance* name like (*chromium*) of the only window in the workspace

View File

@ -263,6 +263,7 @@ A module group is defined by specifying a module named "group/some-group-name".
- *waybar-custom(5)* - *waybar-custom(5)*
- *waybar-disk(5)* - *waybar-disk(5)*
- *waybar-idle-inhibitor(5)* - *waybar-idle-inhibitor(5)*
- *waybar-image(5)*
- *waybar-keyboard-state(5)* - *waybar-keyboard-state(5)*
- *waybar-memory(5)* - *waybar-memory(5)*
- *waybar-mpd(5)* - *waybar-mpd(5)*
@ -278,6 +279,7 @@ A module group is defined by specifying a module named "group/some-group-name".
- *waybar-sway-scratchpad(5)* - *waybar-sway-scratchpad(5)*
- *waybar-sway-window(5)* - *waybar-sway-window(5)*
- *waybar-sway-workspaces(5)* - *waybar-sway-workspaces(5)*
- *waybar-wireplumber(5)*
- *waybar-wlr-taskbar(5)* - *waybar-wlr-taskbar(5)*
- *waybar-wlr-workspaces(5)* - *waybar-wlr-workspaces(5)*
- *waybar-temperature(5)* - *waybar-temperature(5)*

View File

@ -1,6 +1,6 @@
project( project(
'waybar', 'cpp', 'c', 'waybar', 'cpp', 'c',
version: '0.9.16', version: '0.9.17',
license: 'MIT', license: 'MIT',
meson_version: '>= 0.49.0', meson_version: '>= 0.49.0',
default_options : [ default_options : [
@ -123,11 +123,18 @@ gtk_layer_shell = dependency('gtk-layer-shell-0',
required: get_option('gtk-layer-shell'), required: get_option('gtk-layer-shell'),
fallback : ['gtk-layer-shell', 'gtk_layer_shell_dep']) fallback : ['gtk-layer-shell', 'gtk_layer_shell_dep'])
systemd = dependency('systemd', required: get_option('systemd')) systemd = dependency('systemd', required: get_option('systemd'))
tz_dep = dependency('date',
required: false, cpp_lib_chrono = compiler.compute_int('__cpp_lib_chrono', prefix : '#include <chrono>')
default_options : [ 'use_system_tzdb=true' ], have_chrono_timezones = cpp_lib_chrono >= 201907
modules : [ 'date::date', 'date::date-tz' ], if have_chrono_timezones
fallback: [ 'date', 'tz_dep' ]) 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') prefix = get_option('prefix')
sysconfdir = get_option('sysconfdir') sysconfdir = get_option('sysconfdir')
@ -313,7 +320,10 @@ if get_option('rfkill').enabled() and is_linux
) )
endif 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') add_project_arguments('-DHAVE_LIBDATE', language: 'cpp')
src_files += 'src/modules/clock.cpp' src_files += 'src/modules/clock.cpp'
else else
@ -396,6 +406,7 @@ if scdoc.found()
'waybar-disk.5.scd', 'waybar-disk.5.scd',
'waybar-gamemode.5.scd', 'waybar-gamemode.5.scd',
'waybar-idle-inhibitor.5.scd', 'waybar-idle-inhibitor.5.scd',
'waybar-image.5.scd',
'waybar-keyboard-state.5.scd', 'waybar-keyboard-state.5.scd',
'waybar-memory.5.scd', 'waybar-memory.5.scd',
'waybar-mpd.5.scd', 'waybar-mpd.5.scd',
@ -449,7 +460,7 @@ endif
catch2 = dependency( catch2 = dependency(
'catch2', 'catch2',
version: '>=3.0.0', version: '>=2.0.0',
fallback: ['catch2', 'catch2_dep'], fallback: ['catch2', 'catch2_dep'],
required: get_option('tests'), required: get_option('tests'),
) )

139
nix/default.nix Normal file
View File

@ -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";
};
}

View File

@ -725,10 +725,7 @@ void waybar::Bar::setupAltFormatKeyForModuleList(const char* module_list_name) {
void waybar::Bar::handleSignal(int signal) { void waybar::Bar::handleSignal(int signal) {
for (auto& module : modules_all_) { for (auto& module : modules_all_) {
auto* custom = dynamic_cast<waybar::modules::Custom*>(module.get()); module->refresh(signal);
if (custom != nullptr) {
custom->refresh(signal);
}
} }
} }

View File

@ -101,6 +101,9 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
if (ref == "disk") { if (ref == "disk") {
return new waybar::modules::Disk(id, config_[name]); return new waybar::modules::Disk(id, config_[name]);
} }
if (ref == "image") {
return new waybar::modules::Image(id, config_[name]);
}
#ifdef HAVE_DBUSMENU #ifdef HAVE_DBUSMENU
if (ref == "tray") { if (ref == "tray") {
return new waybar::modules::SNI::Tray(id, bar_, config_[name]); return new waybar::modules::SNI::Tray(id, bar_, config_[name]);
@ -159,8 +162,6 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
} }
if (ref.compare(0, 7, "custom/") == 0 && ref.size() > 7) { if (ref.compare(0, 7, "custom/") == 0 && ref.size() > 7) {
return new waybar::modules::Custom(ref.substr(7), id, config_[name]); return new waybar::modules::Custom(ref.substr(7), 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) { } catch (const std::exception& e) {
auto err = fmt::format("Disabling module \"{}\", {}", name, e.what()); auto err = fmt::format("Disabling module \"{}\", {}", name, e.what());

View File

@ -85,6 +85,12 @@ int main(int argc, char* argv[]) {
waybar::Client::inst()->reset(); 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) { for (int sig = SIGRTMIN + 1; sig <= SIGRTMAX; ++sig) {
std::signal(sig, [](int sig) { std::signal(sig, [](int sig) {
for (auto& bar : waybar::Client::inst()->bars) { for (auto& bar : waybar::Client::inst()->bars) {

View File

@ -48,13 +48,13 @@ struct UdevMonitorDeleter {
void check_eq(int rc, int expected, const char *message = "eq, rc was: ") { void check_eq(int rc, int expected, const char *message = "eq, rc was: ") {
if (rc != expected) { 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: ") { void check_neq(int rc, int bad_rc, const char *message = "neq, rc was: ") {
if (rc == bad_rc) { 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: ") { void check_gte(int rc, int gte, const char *message = "rc was: ") {
if (rc < gte) { 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(); 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] { udev_thread_ = [this] {
std::unique_ptr<udev, UdevDeleter> udev{udev_new()}; std::unique_ptr<udev, UdevDeleter> udev{udev_new()};
check_nn(udev.get(), "Udev new failed"); check_nn(udev.get(), "Udev new failed");
@ -181,7 +190,8 @@ auto waybar::modules::Backlight::update() -> void {
event_box_.show(); event_box_.show();
const uint8_t percent = const uint8_t percent =
best->get_max() == 0 ? 100 : round(best->get_actual() * 100.0f / best->get_max()); 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)))); fmt::arg("icon", getIcon(percent))));
getState(percent); getState(percent);
} else { } else {
@ -263,3 +273,71 @@ void waybar::modules::Backlight::enumerate_devices(ForwardIt first, ForwardIt la
upsert_device(first, last, inserter, dev.get()); 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;
}

View File

@ -505,12 +505,12 @@ const std::tuple<uint8_t, float, std::string, float> waybar::modules::Battery::g
float time_remaining{0.0f}; float time_remaining{0.0f};
if (status == "Discharging" && time_to_empty_now_exists) { 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) { } else if (status == "Discharging" && total_power_exists && total_energy_exists) {
if (total_power != 0) time_remaining = (float)total_energy / total_power; if (total_power != 0) time_remaining = (float)total_energy / total_power;
} else if (status == "Charging" && time_to_full_now_exists) { } else if (status == "Charging" && time_to_full_now_exists) {
if (time_to_full_now_exists && (time_to_full_now != 0)) if (time_to_full_now_exists && (time_to_full_now != 0))
time_remaining = -(float)time_to_full_now / 1000.0f; 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 // If we've turned positive it means the battery is past 100% and so just report that as no
// time remaining // time remaining
if (time_remaining > 0.0f) time_remaining = 0.0f; if (time_remaining > 0.0f) time_remaining = 0.0f;
@ -604,7 +604,7 @@ const std::string waybar::modules::Battery::formatTimeRemaining(float hoursRemai
format = config_["format-time"].asString(); format = config_["format-time"].asString();
} }
std::string zero_pad_minutes = fmt::format("{:02d}", minutes); 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)); fmt::arg("m", zero_pad_minutes));
} }
@ -644,7 +644,8 @@ auto waybar::modules::Battery::update() -> void {
} else if (config_["tooltip-format"].isString()) { } else if (config_["tooltip-format"].isString()) {
tooltip_format = config_["tooltip-format"].asString(); 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("power", power), fmt::arg("capacity", capacity),
fmt::arg("time", time_remaining_formatted))); fmt::arg("time", time_remaining_formatted)));
} }
@ -665,9 +666,9 @@ auto waybar::modules::Battery::update() -> void {
} else { } else {
event_box_.show(); event_box_.show();
auto icons = std::vector<std::string>{status + "-" + state, status, state}; auto icons = std::vector<std::string>{status + "-" + state, status, state};
label_.set_markup(fmt::format(format, fmt::arg("capacity", capacity), fmt::arg("power", power), label_.set_markup(fmt::format(
fmt::arg("icon", getIcon(capacity, icons)), fmt::runtime(format), fmt::arg("capacity", capacity), fmt::arg("power", power),
fmt::arg("time", time_remaining_formatted))); fmt::arg("icon", getIcon(capacity, icons)), fmt::arg("time", time_remaining_formatted)));
} }
// Call parent update // Call parent update
ALabel::update(); ALabel::update();

View File

@ -206,7 +206,8 @@ auto waybar::modules::Bluetooth::update() -> void {
state_ = state; state_ = state;
label_.set_markup(fmt::format( 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", cur_controller_.address),
fmt::arg("controller_address_type", cur_controller_.address_type), fmt::arg("controller_address_type", cur_controller_.address_type),
fmt::arg("controller_alias", cur_controller_.alias), 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(); enumerate_format = config_["tooltip-format-enumerate-connected"].asString();
} }
ss << fmt::format( 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_address_type", dev.address_type),
fmt::arg("device_alias", dev.alias), fmt::arg("icon", enumerate_icon), fmt::arg("device_alias", dev.alias), fmt::arg("icon", enumerate_icon),
fmt::arg("device_battery_percentage", dev.battery_percentage.value_or(0))); 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( 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("num_connections", connected_devices_.size()),
fmt::arg("controller_address", cur_controller_.address), fmt::arg("controller_address", cur_controller_.address),
fmt::arg("controller_address_type", cur_controller_.address_type), fmt::arg("controller_address_type", cur_controller_.address_type),

View File

@ -10,14 +10,11 @@
#include <type_traits> #include <type_traits>
#include "util/ustring_clen.hpp" #include "util/ustring_clen.hpp"
#include "util/waybar_time.hpp"
#ifdef HAVE_LANGINFO_1STDAY #ifdef HAVE_LANGINFO_1STDAY
#include <langinfo.h> #include <langinfo.h>
#include <locale.h> #include <locale.h>
#endif #endif
using waybar::waybar_time;
waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config) waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
: ALabel(config, "clock", id, "{:%H:%M}", 60, false, false, true), : ALabel(config, "clock", id, "{:%H:%M}", 60, false, false, true),
current_time_zone_idx_(0), current_time_zone_idx_(0),
@ -35,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())); 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 // If all timezones are parsed and no one is good, add current time zone. nullptr in timezones
// that local time should be shown. // vector means that local time should be shown
if (!time_zones_.size()) { if (!time_zones_.size()) {
time_zones_.push_back(nullptr); time_zones_.push_back(date::current_zone());
}
if (!is_timezone_fixed()) {
spdlog::warn(
"As using a timezone, some format args may be missing as the date library haven't got a "
"release since 2018.");
} }
// Check if a particular placeholder is present in the tooltip format, to know what to calculate // Check if a particular placeholder is present in the tooltip format, to know what to calculate
@ -62,39 +53,94 @@ waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
} }
} }
// Calendar configuration
if (is_calendar_in_tooltip_) { if (is_calendar_in_tooltip_) {
if (config_["on-scroll"][kCalendarPlaceholder].isInt()) { if (config_[kCalendarPlaceholder]["weeks-pos"].isString()) {
calendar_shift_init_ = if (config_[kCalendarPlaceholder]["weeks-pos"].asString() == "left") {
date::months{config_["on-scroll"].get(kCalendarPlaceholder, 0).asInt()}; 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_.add_events(Gdk::LEAVE_NOTIFY_MASK);
event_box_.signal_leave_notify_event().connect([this](GdkEventCrossing*) { event_box_.signal_leave_notify_event().connect([this](GdkEventCrossing*) {
calendar_shift_ = date::months{0}; cldCurrShift_ = date::months{0};
return false; 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()); locale_ = std::locale(config_["locale"].asString());
} else { else
locale_ = std::locale(""); locale_ = std::locale("");
}
if (config_["format-calendar-weeks"].isString()) {
fmt_str_weeks_ =
std::regex_replace(config_["format-calendar-weeks"].asString(), std::regex("\\{\\}"),
(first_day_of_week() == date::Monday) ? "{:%V}" : "{:%U}");
fmt_weeks_left_pad_ =
std::regex_replace(fmt_str_weeks_, std::regex("</?[^>]+>|\\{.*\\}"), "").length();
} else {
fmt_str_weeks_ = "";
}
if (config_["format-calendar"].isString()) {
fmt_str_calendar_ = config_["format-calendar"].asString();
} else {
fmt_str_calendar_ = "{}";
}
thread_ = [this] { thread_ = [this] {
dp.emit(); dp.emit();
@ -116,24 +162,22 @@ bool waybar::modules::Clock::is_timezone_fixed() {
} }
auto waybar::modules::Clock::update() -> void { auto waybar::modules::Clock::update() -> void {
auto time_zone = current_timezone(); const auto* time_zone = current_timezone();
auto now = std::chrono::system_clock::now(); auto now = std::chrono::system_clock::now();
waybar_time wtime = {locale_, auto ztime = date::zoned_time{time_zone, date::floor<std::chrono::seconds>(now)};
date::make_zoned(time_zone, date::floor<std::chrono::seconds>(now))};
auto shifted_date = date::year_month_day{date::floor<date::days>(now)} + calendar_shift_; 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 now_shifted = date::sys_days{shifted_date} + (now - date::floor<date::days>(now));
waybar_time shifted_wtime = { auto shifted_ztime = date::zoned_time{time_zone, date::floor<std::chrono::seconds>(now_shifted)};
locale_, date::make_zoned(time_zone, date::floor<std::chrono::seconds>(now_shifted))};
std::string text = ""; std::string text{""};
if (!is_timezone_fixed()) { if (!is_timezone_fixed()) {
// As date dep is not fully compatible, prefer fmt // As date dep is not fully compatible, prefer fmt
tzset(); tzset();
auto localtime = fmt::localtime(std::chrono::system_clock::to_time_t(now)); 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 { } else {
text = fmt::format(format_, wtime); text = fmt::format(locale_, fmt::runtime(format_), ztime);
} }
label_.set_markup(text); label_.set_markup(text);
@ -142,13 +186,13 @@ auto waybar::modules::Clock::update() -> void {
std::string calendar_lines{""}; std::string calendar_lines{""};
std::string timezoned_time_lines{""}; std::string timezoned_time_lines{""};
if (is_calendar_in_tooltip_) { if (is_calendar_in_tooltip_) {
calendar_lines = calendar_text(shifted_wtime); calendar_lines = get_calendar(ztime, shifted_ztime);
} }
if (is_timezoned_list_in_tooltip_) { if (is_timezoned_list_in_tooltip_) {
timezoned_time_lines = timezones_text(&now); timezoned_time_lines = timezones_text(&now);
} }
auto tooltip_format = config_["tooltip-format"].asString(); auto tooltip_format = config_["tooltip-format"].asString();
text = fmt::format(tooltip_format, shifted_wtime, text = fmt::format(locale_, fmt::runtime(tooltip_format), shifted_ztime,
fmt::arg(kCalendarPlaceholder.c_str(), calendar_lines), fmt::arg(kCalendarPlaceholder.c_str(), calendar_lines),
fmt::arg(KTimezonedTimeListPlaceholder.c_str(), timezoned_time_lines)); fmt::arg(KTimezonedTimeListPlaceholder.c_str(), timezoned_time_lines));
label_.set_tooltip_markup(text); label_.set_tooltip_markup(text);
@ -159,6 +203,21 @@ auto waybar::modules::Clock::update() -> void {
ALabel::update(); 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) { bool waybar::modules::Clock::handleScroll(GdkEventScroll* e) {
// defer to user commands if set // defer to user commands if set
if (config_["on-scroll-up"].isString() || config_["on-scroll-down"].isString()) { if (config_["on-scroll-up"].isString() || config_["on-scroll-down"].isString()) {
@ -168,11 +227,11 @@ bool waybar::modules::Clock::handleScroll(GdkEventScroll* e) {
auto dir = AModule::getScrollDir(e); auto dir = AModule::getScrollDir(e);
// Shift calendar date // Shift calendar date
if (calendar_shift_init_.count() != 0) { if (cldShift_.count() != 0) {
if (dir == SCROLL_DIR::UP) if (dir == SCROLL_DIR::UP)
calendar_shift_ += calendar_shift_init_; cldCurrShift_ += ((cldMode_ == CldMode::YEAR) ? 12 : 1) * cldShift_;
else else
calendar_shift_ -= calendar_shift_init_; cldCurrShift_ -= ((cldMode_ == CldMode::YEAR) ? 12 : 1) * cldShift_;
} else { } else {
// Change time zone // Change time zone
if (dir != SCROLL_DIR::UP && dir != SCROLL_DIR::DOWN) { if (dir != SCROLL_DIR::UP && dir != SCROLL_DIR::DOWN) {
@ -196,126 +255,214 @@ bool waybar::modules::Clock::handleScroll(GdkEventScroll* e) {
return true; return true;
} }
auto waybar::modules::Clock::calendar_text(const waybar_time& wtime) -> std::string { // The number of weeks in calendar month layout plus 1 more for calendar titles
const auto daypoint = date::floor<date::days>(wtime.ztime.get_local_time()); unsigned cldRowsInMonth(date::year_month const ym, date::weekday const firstdow) {
const auto ymd{date::year_month_day{daypoint}}; using namespace date;
return static_cast<unsigned>(
if (calendar_cached_ymd_ == ymd) { ceil<weeks>((weekday{ym / 1} - firstdow) + ((ym / last).day() - day{0})).count()) +
return calendar_cached_text_; 2;
}
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 first_dow = first_day_of_week();
std::stringstream os;
enum class WeeksSide {
LEFT,
RIGHT,
HIDDEN,
};
WeeksSide weeks_pos = WeeksSide::HIDDEN;
if (config_["calendar-weeks-pos"].isString()) {
if (config_["calendar-weeks-pos"].asString() == "left") {
weeks_pos = WeeksSide::LEFT;
// Add paddings before the header
os << std::string(3 + fmt_weeks_left_pad_, ' ');
} else if (config_["calendar-weeks-pos"].asString() == "right") {
weeks_pos = WeeksSide::RIGHT;
}
}
weekdays_header(first_dow, os);
// First week day prefixed with spaces if needed.
date::sys_days print_wd{ym / 1};
auto wd{date::weekday{print_wd}};
auto empty_days = (wd - first_dow).count();
/* Print weeknumber on the left for the first row*/
if (weeks_pos == WeeksSide::LEFT) {
os << fmt::format(fmt_str_weeks_, print_wd) << ' ';
}
if (empty_days > 0) {
os << std::string(empty_days * 3 - 1, ' ');
}
const auto last_day = (ym / date::literals::last).day();
for (auto d{date::day{1}}; d <= last_day; ++d, ++wd) {
if (wd != first_dow) {
os << ' ';
} else if (unsigned(d) != 1) {
if (weeks_pos == WeeksSide::RIGHT) {
os << ' ' << fmt::format(fmt_str_weeks_, print_wd);
}
os << '\n';
print_wd = (ym / d);
if (weeks_pos == WeeksSide::LEFT) {
os << fmt::format(fmt_str_weeks_, print_wd) << ' ';
}
}
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 {
os << fmt::format(fmt_str_calendar_, date::format("%e", d));
}
/*Print weeks on the right when the endings with spaces*/
if (weeks_pos == WeeksSide::RIGHT && d == last_day) {
empty_days = 6 - (wd.c_encoding() - first_dow.c_encoding());
if (empty_days > 0 && empty_days < 7) {
os << std::string(empty_days * 3, ' ');
}
os << ' ' << fmt::format(fmt_str_weeks_, print_wd);
}
}
auto result = os.str();
calendar_cached_ymd_ = ymd;
calendar_cached_text_ = result;
return result;
} }
auto waybar::modules::Clock::weekdays_header(const date::weekday& first_week_day, std::ostream& os) auto cldGetWeekForLine(date::year_month const ym, date::weekday const firstdow, unsigned const line)
-> void { -> const date::year_month_weekday {
std::stringstream res; unsigned index = line - 2;
auto wd = first_week_day; auto sd = date::sys_days{ym / 1};
do { if (date::weekday{sd} == firstdow) ++index;
if (wd != first_week_day) { auto ymdw = ym / firstdow[index];
res << ' '; return ymdw;
} }
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--;
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()) { auto getCalendarLine(date::year_month_day const currDate, date::year_month const ym,
os << fmt::format(config_["format-calendar-weekdays"].asString(), res.str()); unsigned const line, date::weekday const firstdow,
} else const std::locale* const locale_) -> std::string {
os << res.str(); using namespace date::literals;
std::ostringstream res;
switch (line) {
case 0: {
// Output month and year title
res << date::format(*locale_, "%B %Y", ym);
break;
}
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;
clen = ustring_clen(wd_ustring);
}
const std::string pad(2 - clen, ' ');
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) auto waybar::modules::Clock::timezones_text(std::chrono::system_clock::time_point* now)
@ -324,7 +471,6 @@ auto waybar::modules::Clock::timezones_text(std::chrono::system_clock::time_poin
return ""; return "";
} }
std::stringstream os; std::stringstream os;
waybar_time wtime;
for (size_t time_zone_idx = 0; time_zone_idx < time_zones_.size(); ++time_zone_idx) { 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_) { if (static_cast<int>(time_zone_idx) == current_time_zone_idx_) {
continue; continue;
@ -333,8 +479,8 @@ auto waybar::modules::Clock::timezones_text(std::chrono::system_clock::time_poin
if (!timezone) { if (!timezone) {
timezone = date::current_zone(); timezone = date::current_zone();
} }
wtime = {locale_, date::make_zoned(timezone, date::floor<std::chrono::seconds>(*now))}; auto ztime = date::zoned_time{timezone, date::floor<std::chrono::seconds>(*now)};
os << fmt::format(format_, wtime) << '\n'; os << fmt::format(locale_, fmt::runtime(format_), ztime) << '\n';
} }
return os.str(); return os.str();
} }

View File

@ -126,7 +126,7 @@ auto waybar::modules::Custom::update() -> void {
} else { } else {
parseOutputRaw(); parseOutputRaw();
} }
auto str = fmt::format(format_, text_, fmt::arg("alt", alt_), auto str = fmt::format(fmt::runtime(format_), text_, fmt::arg("alt", alt_),
fmt::arg("icon", getIcon(percentage_, alt_)), fmt::arg("icon", getIcon(percentage_, alt_)),
fmt::arg("percentage", percentage_)); fmt::arg("percentage", percentage_));
if (str.empty()) { if (str.empty()) {
@ -209,8 +209,8 @@ void waybar::modules::Custom::parseOutputJson() {
class_.push_back(c.asString()); class_.push_back(c.asString());
} }
} }
if (!parsed["percentage"].asString().empty() && parsed["percentage"].isUInt()) { if (!parsed["percentage"].asString().empty() && parsed["percentage"].isNumeric()) {
percentage_ = parsed["percentage"].asUInt(); percentage_ = (int)lround(parsed["percentage"].asFloat());
} else { } else {
percentage_ = 0; percentage_ = 0;
} }

View File

@ -58,11 +58,11 @@ auto waybar::modules::Disk::update() -> void {
event_box_.hide(); event_box_.hide();
} else { } else {
event_box_.show(); event_box_.show();
label_.set_markup( label_.set_markup(fmt::format(
fmt::format(format, stats.f_bavail * 100 / stats.f_blocks, fmt::arg("free", free), 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("percentage_free", stats.f_bavail * 100 / stats.f_blocks), fmt::arg("used", used),
fmt::arg("used", used), fmt::arg("percentage_used", percentage_used), fmt::arg("percentage_used", percentage_used), fmt::arg("total", total),
fmt::arg("total", total), fmt::arg("path", path_))); fmt::arg("path", path_)));
} }
if (tooltipEnabled()) { if (tooltipEnabled()) {
@ -70,11 +70,11 @@ auto waybar::modules::Disk::update() -> void {
if (config_["tooltip-format"].isString()) { if (config_["tooltip-format"].isString()) {
tooltip_format = config_["tooltip-format"].asString(); tooltip_format = config_["tooltip-format"].asString();
} }
label_.set_tooltip_text( label_.set_tooltip_text(fmt::format(
fmt::format(tooltip_format, stats.f_bavail * 100 / stats.f_blocks, fmt::arg("free", free), fmt::runtime(tooltip_format), stats.f_bavail * 100 / stats.f_blocks, fmt::arg("free", free),
fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks), fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks), fmt::arg("used", used),
fmt::arg("used", used), fmt::arg("percentage_used", percentage_used), fmt::arg("percentage_used", percentage_used), fmt::arg("total", total),
fmt::arg("total", total), fmt::arg("path", path_))); fmt::arg("path", path_)));
} }
// Call parent update // Call parent update
ALabel::update(); ALabel::update();

View File

@ -213,14 +213,14 @@ auto Gamemode::update() -> void {
// Tooltip // Tooltip
if (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); box_.set_tooltip_text(text);
} }
// Label format // Label format
std::string str = std::string str = fmt::format(fmt::runtime(showAltText ? format_alt : format),
fmt::format(showAltText ? format_alt : format, fmt::arg("glyph", useIcon ? "" : glyph), fmt::arg("glyph", useIcon ? "" : glyph),
fmt::arg("count", gameCount > 0 ? std::to_string(gameCount) : "")); fmt::arg("count", gameCount > 0 ? std::to_string(gameCount) : ""));
label_.set_markup(str); label_.set_markup(str);
if (useIcon) { if (useIcon) {

View File

@ -49,23 +49,23 @@ auto Language::update() -> void {
void Language::onEvent(const std::string& ev) { void Language::onEvent(const std::string& ev) {
std::lock_guard<std::mutex> lg(mutex_); std::lock_guard<std::mutex> lg(mutex_);
auto kbName = ev.substr(ev.find_last_of('>') + 1, ev.find_first_of(',')); 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); auto layoutName = ev.substr(ev.find_first_of(',') + 1);
if (config_.isMember("keyboard-name") && kbName != config_["keyboard-name"].asString()) if (config_.isMember("keyboard-name") && kbName != config_["keyboard-name"].asString())
return; // ignore return; // ignore
layoutName = waybar::util::sanitize_string(layoutName);
const auto briefName = getShortFrom(layoutName); const auto briefName = getShortFrom(layoutName);
if (config_.isMember("format-" + briefName)) { if (config_.isMember("format-" + briefName)) {
const auto propName = "format-" + briefName; const auto propName = "format-" + briefName;
layoutName = fmt::format(format_, config_[propName].asString()); layoutName = fmt::format(fmt::runtime(format_), config_[propName].asString());
} else { } else {
layoutName = fmt::format(format_, layoutName); layoutName = fmt::format(fmt::runtime(format_), layoutName);
} }
layoutName = waybar::util::sanitize_string(layoutName);
if (layoutName == layoutName_) return; if (layoutName == layoutName_) return;
layoutName_ = layoutName; layoutName_ = layoutName;
@ -87,18 +87,18 @@ void Language::initLanguage() {
searcher = searcher.substr(searcher.find("keymap:") + 8); searcher = searcher.substr(searcher.find("keymap:") + 8);
searcher = searcher.substr(0, searcher.find_first_of("\n\t")); searcher = searcher.substr(0, searcher.find_first_of("\n\t"));
searcher = waybar::util::sanitize_string(searcher);
auto layoutName = std::string{}; auto layoutName = std::string{};
const auto briefName = getShortFrom(searcher); const auto briefName = getShortFrom(searcher);
if (config_.isMember("format-" + briefName)) { if (config_.isMember("format-" + briefName)) {
const auto propName = "format-" + briefName; const auto propName = "format-" + briefName;
layoutName = fmt::format(format_, config_[propName].asString()); layoutName = fmt::format(fmt::runtime(format_), config_[propName].asString());
} else { } else {
layoutName = fmt::format(format_, searcher); layoutName = fmt::format(fmt::runtime(format_), searcher);
} }
layoutName = waybar::util::sanitize_string(layoutName);
layoutName_ = layoutName; layoutName_ = layoutName;
spdlog::debug("hyprland language initLanguage found {}", layoutName_); spdlog::debug("hyprland language initLanguage found {}", layoutName_);

View File

@ -19,6 +19,7 @@ Submap::Submap(const std::string& id, const Bar& bar, const Json::Value& config)
// register for hyprland ipc // register for hyprland ipc
gIPC->registerForIPC("submap", this); gIPC->registerForIPC("submap", this);
dp.emit();
} }
Submap::~Submap() { Submap::~Submap() {
@ -33,7 +34,7 @@ auto Submap::update() -> void {
if (submap_.empty()) { if (submap_.empty()) {
event_box_.hide(); event_box_.hide();
} else { } else {
label_.set_markup(fmt::format(format_, submap_)); label_.set_markup(fmt::format(fmt::runtime(format_), submap_));
if (tooltipEnabled()) { if (tooltipEnabled()) {
label_.set_tooltip_text(submap_); label_.set_tooltip_text(submap_);
} }

View File

@ -40,8 +40,8 @@ auto Window::update() -> void {
if (!format_.empty()) { if (!format_.empty()) {
label_.show(); label_.show();
label_.set_markup( label_.set_markup(fmt::format(fmt::runtime(format_),
fmt::format(format_, waybar::util::rewriteTitle(lastView, config_["rewrite"]))); waybar::util::rewriteTitle(lastView, config_["rewrite"])));
} else { } else {
label_.hide(); label_.hide();
} }

View File

@ -63,21 +63,15 @@ auto waybar::modules::IdleInhibitor::update() -> void {
} }
std::string status_text = status ? "activated" : "deactivated"; 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)))); fmt::arg("icon", getIcon(0, status_text))));
label_.get_style_context()->add_class(status_text); label_.get_style_context()->add_class(status_text);
if (tooltipEnabled()) { if (tooltipEnabled()) {
label_.set_tooltip_markup( auto config = config_[status ? "tooltip-format-activated" : "tooltip-format-deactivated"];
status ? fmt::format(config_["tooltip-format-activated"].isString() auto tooltip_format = config.isString() ? config.asString() : "{status}";
? config_["tooltip-format-activated"].asString() label_.set_tooltip_markup(fmt::format(fmt::runtime(tooltip_format),
: "{status}", fmt::arg("status", status_text),
fmt::arg("status", status_text), fmt::arg("icon", getIcon(0, status_text))));
fmt::arg("icon", getIcon(0, status_text)))
: fmt::format(config_["tooltip-format-deactivated"].isString()
? config_["tooltip-format-deactivated"].asString()
: "{status}",
fmt::arg("status", status_text),
fmt::arg("icon", getIcon(0, status_text))));
} }
// Call parent update // Call parent update
ALabel::update(); ALabel::update();

View File

@ -1,15 +1,16 @@
#include "modules/image.hpp" #include "modules/image.hpp"
#include <spdlog/spdlog.h> waybar::modules::Image::Image(const std::string& id, const Json::Value& config)
: AModule(config, "image", id), box_(Gtk::ORIENTATION_HORIZONTAL, 0) {
waybar::modules::Image::Image(const std::string& name, const std::string& id, box_.pack_start(image_);
const Json::Value& config) box_.set_name("image");
: AModule(config, "image-" + name, id, "{}") { if (!id.empty()) {
event_box_.add(image_); box_.get_style_context()->add_class(id);
}
event_box_.add(box_);
dp.emit(); dp.emit();
path_ = config["path"].asString();
size_ = config["size"].asInt(); size_ = config["size"].asInt();
interval_ = config_["interval"].asInt(); interval_ = config_["interval"].asInt();
@ -40,8 +41,17 @@ void waybar::modules::Image::refresh(int sig) {
} }
auto waybar::modules::Image::update() -> void { 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)) if (Glib::file_test(path_, Glib::FILE_TEST_EXISTS))
pixbuf = Gdk::Pixbuf::create_from_file(path_, size_, size_); pixbuf = Gdk::Pixbuf::create_from_file(path_, size_, size_);
else else

View File

@ -118,7 +118,7 @@ auto Inhibitor::update() -> void {
std::string status_text = activated() ? "activated" : "deactivated"; std::string status_text = activated() ? "activated" : "deactivated";
label_.get_style_context()->remove_class(activated() ? "deactivated" : "activated"); 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)))); fmt::arg("icon", getIcon(0, status_text))));
label_.get_style_context()->add_class(status_text); label_.get_style_context()->add_class(status_text);

View File

@ -72,7 +72,7 @@ auto JACK::update() -> void {
} else } else
format = "{load}%"; 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("bufsize", bufsize_), fmt::arg("samplerate", samplerate_),
fmt::arg("latency", fmt::format("{:.2f}", latency)), fmt::arg("latency", fmt::format("{:.2f}", latency)),
fmt::arg("xruns", xruns_))); fmt::arg("xruns", xruns_)));
@ -81,9 +81,9 @@ auto JACK::update() -> void {
std::string tooltip_format = "{bufsize}/{samplerate} {latency}ms"; std::string tooltip_format = "{bufsize}/{samplerate} {latency}ms";
if (config_["tooltip-format"].isString()) tooltip_format = config_["tooltip-format"].asString(); if (config_["tooltip-format"].isString()) tooltip_format = config_["tooltip-format"].asString();
label_.set_tooltip_text(fmt::format( label_.set_tooltip_text(fmt::format(
tooltip_format, fmt::arg("load", std::round(load_)), fmt::arg("bufsize", bufsize_), fmt::runtime(tooltip_format), fmt::arg("load", std::round(load_)),
fmt::arg("samplerate", samplerate_), fmt::arg("latency", fmt::format("{:.2f}", latency)), fmt::arg("bufsize", bufsize_), fmt::arg("samplerate", samplerate_),
fmt::arg("xruns", xruns_))); fmt::arg("latency", fmt::format("{:.2f}", latency)), fmt::arg("xruns", xruns_)));
} }
// Call parent update // Call parent update

View File

@ -278,7 +278,7 @@ auto waybar::modules::KeyboardState::update() -> void {
}; };
for (auto& label_state : label_states) { for (auto& label_state : label_states) {
std::string text; 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("icon", label_state.state ? icon_locked_ : icon_unlocked_),
fmt::arg("name", label_state.name)); fmt::arg("name", label_state.name));
label_state.label.set_markup(text); label_state.label.set_markup(text);

View File

@ -56,7 +56,8 @@ auto waybar::modules::Memory::update() -> void {
event_box_.show(); event_box_.show();
auto icons = std::vector<std::string>{state}; auto icons = std::vector<std::string>{state};
label_.set_markup(fmt::format( 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("total", total_ram_gigabytes), fmt::arg("swapTotal", total_swap_gigabytes),
fmt::arg("percentage", used_ram_percentage), fmt::arg("percentage", used_ram_percentage),
fmt::arg("swapPercentage", used_swap_percentage), fmt::arg("used", used_ram_gigabytes), 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()) { if (config_["tooltip-format"].isString()) {
auto tooltip_format = config_["tooltip-format"].asString(); auto tooltip_format = config_["tooltip-format"].asString();
label_.set_tooltip_text(fmt::format( label_.set_tooltip_text(fmt::format(
tooltip_format, used_ram_percentage, fmt::arg("total", total_ram_gigabytes), fmt::runtime(tooltip_format), used_ram_percentage,
fmt::arg("swapTotal", total_swap_gigabytes), fmt::arg("total", total_ram_gigabytes), fmt::arg("swapTotal", total_swap_gigabytes),
fmt::arg("percentage", used_ram_percentage), fmt::arg("percentage", used_ram_percentage),
fmt::arg("swapPercentage", used_swap_percentage), fmt::arg("used", used_ram_gigabytes), fmt::arg("swapPercentage", used_swap_percentage), fmt::arg("used", used_ram_gigabytes),
fmt::arg("swapUsed", used_swap_gigabytes), fmt::arg("avail", available_ram_gigabytes), fmt::arg("swapUsed", used_swap_gigabytes), fmt::arg("avail", available_ram_gigabytes),

View File

@ -174,14 +174,14 @@ void waybar::modules::MPD::setLabel() {
try { try {
auto text = fmt::format( auto text = fmt::format(
format, fmt::arg("artist", artist.raw()), fmt::arg("albumArtist", album_artist.raw()), fmt::runtime(format), fmt::arg("artist", artist.raw()),
fmt::arg("album", album.raw()), fmt::arg("title", title.raw()), fmt::arg("date", date), fmt::arg("albumArtist", album_artist.raw()), fmt::arg("album", album.raw()),
fmt::arg("volume", volume), fmt::arg("elapsedTime", elapsedTime), fmt::arg("title", title.raw()), fmt::arg("date", date), fmt::arg("volume", volume),
fmt::arg("totalTime", totalTime), fmt::arg("songPosition", song_pos), fmt::arg("elapsedTime", elapsedTime), fmt::arg("totalTime", totalTime),
fmt::arg("queueLength", queue_length), fmt::arg("stateIcon", stateIcon), fmt::arg("songPosition", song_pos), fmt::arg("queueLength", queue_length),
fmt::arg("consumeIcon", consumeIcon), fmt::arg("randomIcon", randomIcon), fmt::arg("stateIcon", stateIcon), fmt::arg("consumeIcon", consumeIcon),
fmt::arg("repeatIcon", repeatIcon), fmt::arg("singleIcon", singleIcon), fmt::arg("randomIcon", randomIcon), fmt::arg("repeatIcon", repeatIcon),
fmt::arg("filename", filename)); fmt::arg("singleIcon", singleIcon), fmt::arg("filename", filename));
if (text.empty()) { if (text.empty()) {
label_.hide(); label_.hide();
} else { } else {
@ -198,7 +198,7 @@ void waybar::modules::MPD::setLabel() {
: "MPD (connected)"; : "MPD (connected)";
try { try {
auto tooltip_text = 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("albumArtist", album_artist.raw()), fmt::arg("album", album.raw()),
fmt::arg("title", title.raw()), fmt::arg("date", date), fmt::arg("title", title.raw()), fmt::arg("date", date),
fmt::arg("volume", volume), fmt::arg("elapsedTime", elapsedTime), fmt::arg("volume", volume), fmt::arg("elapsedTime", elapsedTime),

View File

@ -378,10 +378,10 @@ auto Mpris::update() -> void {
break; break;
} }
auto label_format = auto label_format =
fmt::format(formatstr, fmt::arg("player", info.name), fmt::arg("status", info.status_string), fmt::format(fmt::runtime(formatstr), fmt::arg("player", info.name),
fmt::arg("artist", *info.artist), fmt::arg("title", *info.title), fmt::arg("status", info.status_string), fmt::arg("artist", *info.artist),
fmt::arg("album", *info.album), fmt::arg("length", *info.length), fmt::arg("title", *info.title), fmt::arg("album", *info.album),
fmt::arg("dynamic", dynamic.str()), fmt::arg("length", *info.length), fmt::arg("dynamic", dynamic.str()),
fmt::arg("player_icon", getIcon(config_["player-icons"], info.name)), fmt::arg("player_icon", getIcon(config_["player-icons"], info.name)),
fmt::arg("status_icon", getIcon(config_["status-icons"], info.status_string))); fmt::arg("status_icon", getIcon(config_["status-icons"], info.status_string)));
label_.set_markup(label_format); label_.set_markup(label_format);

View File

@ -331,7 +331,7 @@ auto waybar::modules::Network::update() -> void {
getState(signal_strength_); getState(signal_strength_);
auto text = fmt::format( 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("signalStrength", signal_strength_),
fmt::arg("signalStrengthApp", signal_strength_app_), fmt::arg("ifname", ifname_), fmt::arg("signalStrengthApp", signal_strength_app_), fmt::arg("ifname", ifname_),
fmt::arg("netmask", netmask_), fmt::arg("ipaddr", ipaddr_), fmt::arg("gwaddr", gwaddr_), 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()) { if (!tooltip_format.empty()) {
auto tooltip_text = fmt::format( auto tooltip_text = fmt::format(
tooltip_format, fmt::arg("essid", essid_), fmt::arg("signaldBm", signal_strength_dbm_), fmt::runtime(tooltip_format), fmt::arg("essid", essid_),
fmt::arg("signalStrength", signal_strength_), fmt::arg("signaldBm", signal_strength_dbm_), fmt::arg("signalStrength", signal_strength_),
fmt::arg("signalStrengthApp", signal_strength_app_), fmt::arg("ifname", ifname_), fmt::arg("signalStrengthApp", signal_strength_app_), fmt::arg("ifname", ifname_),
fmt::arg("netmask", netmask_), fmt::arg("ipaddr", ipaddr_), fmt::arg("gwaddr", gwaddr_), fmt::arg("netmask", netmask_), fmt::arg("ipaddr", ipaddr_), fmt::arg("gwaddr", gwaddr_),
fmt::arg("cidr", cidr_), fmt::arg("frequency", fmt::format("{:.1f}", frequency_)), fmt::arg("cidr", cidr_), fmt::arg("frequency", fmt::format("{:.1f}", frequency_)),

View File

@ -294,9 +294,9 @@ auto waybar::modules::Pulseaudio::update() -> void {
format_source = config_["format-source"].asString(); 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( 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("format_source", format_source), fmt::arg("source_volume", source_volume_),
fmt::arg("source_desc", source_desc_), fmt::arg("icon", getIcon(volume_, getPulseIcon()))); fmt::arg("source_desc", source_desc_), fmt::arg("icon", getIcon(volume_, getPulseIcon())));
if (text.empty()) { if (text.empty()) {
@ -313,7 +313,7 @@ auto waybar::modules::Pulseaudio::update() -> void {
} }
if (!tooltip_format.empty()) { if (!tooltip_format.empty()) {
label_.set_tooltip_text(fmt::format( 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("format_source", format_source), fmt::arg("source_volume", source_volume_),
fmt::arg("source_desc", source_desc_), fmt::arg("source_desc", source_desc_),
fmt::arg("icon", getIcon(volume_, getPulseIcon())))); fmt::arg("icon", getIcon(volume_, getPulseIcon()))));

View File

@ -103,7 +103,7 @@ void Mode::handle_mode(const char *mode) {
} }
label_.get_style_context()->add_class(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(); label_.show();
} }

View File

@ -106,7 +106,7 @@ void Window::handle_focused_view(const char *title) {
label_.hide(); // hide empty labels or labels with empty format label_.hide(); // hide empty labels or labels with empty format
} else { } else {
label_.show(); 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(); ALabel::update();

View File

@ -110,7 +110,8 @@ auto Sndio::update() -> void {
label_.get_style_context()->remove_class("muted"); 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()) { if (text.empty()) {
label_.hide(); label_.hide();
} else { } else {

View File

@ -2,6 +2,8 @@
#include <fcntl.h> #include <fcntl.h>
#include <stdexcept>
namespace waybar::modules::sway { namespace waybar::modules::sway {
Ipc::Ipc() { Ipc::Ipc() {

View File

@ -96,14 +96,14 @@ void Language::onEvent(const struct Ipc::ipc_response& res) {
auto Language::update() -> void { auto Language::update() -> void {
std::lock_guard<std::mutex> lock(mutex_); std::lock_guard<std::mutex> lock(mutex_);
auto display_layout = trim(fmt::format( 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("shortDescription", layout_.short_description), fmt::arg("long", layout_.full_name),
fmt::arg("variant", layout_.variant), fmt::arg("flag", layout_.country_flag()))); fmt::arg("variant", layout_.variant), fmt::arg("flag", layout_.country_flag())));
label_.set_markup(display_layout); label_.set_markup(display_layout);
if (tooltipEnabled()) { if (tooltipEnabled()) {
if (tooltip_format_ != "") { if (tooltip_format_ != "") {
auto tooltip_display_layout = trim( 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("shortDescription", layout_.short_description),
fmt::arg("long", layout_.full_name), fmt::arg("variant", layout_.variant), fmt::arg("long", layout_.full_name), fmt::arg("variant", layout_.variant),
fmt::arg("flag", layout_.country_flag()))); fmt::arg("flag", layout_.country_flag())));

View File

@ -42,7 +42,7 @@ auto Mode::update() -> void {
if (mode_.empty()) { if (mode_.empty()) {
event_box_.hide(); event_box_.hide();
} else { } else {
label_.set_markup(fmt::format(format_, mode_)); label_.set_markup(fmt::format(fmt::runtime(format_), mode_));
if (tooltipEnabled()) { if (tooltipEnabled()) {
label_.set_tooltip_text(mode_); label_.set_tooltip_text(mode_);
} }

View File

@ -32,7 +32,8 @@ auto Scratchpad::update() -> void {
if (count_ || show_empty_) { if (count_ || show_empty_) {
event_box_.show(); event_box_.show();
label_.set_markup( 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_))); fmt::arg("count", count_)));
if (tooltip_enabled_) { if (tooltip_enabled_) {
label_.set_tooltip_markup(tooltip_text_); label_.set_tooltip_markup(tooltip_text_);
@ -64,7 +65,7 @@ auto Scratchpad::onCmd(const struct Ipc::ipc_response& res) -> void {
if (tooltip_enabled_) { if (tooltip_enabled_) {
tooltip_text_.clear(); tooltip_text_.clear();
for (const auto& window : tree["nodes"][0]["nodes"][0]["floating_nodes"]) { 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("app", window["app_id"].asString()),
fmt::arg("title", window["name"].asString()))); fmt::arg("title", window["name"].asString())));
} }

View File

@ -17,7 +17,7 @@
namespace waybar::modules::sway { namespace waybar::modules::sway {
Window::Window(const std::string& id, const Bar& bar, const Json::Value& config) Window::Window(const std::string& id, const Bar& bar, const Json::Value& config)
: AIconLabel(config, "window", id, "{title}", 0, true), bar_(bar), windowId_(-1) { : AIconLabel(config, "window", id, "{}", 0, true), bar_(bar), windowId_(-1) {
// Icon size // Icon size
if (config_["icon-size"].isUInt()) { if (config_["icon-size"].isUInt()) {
app_icon_size_ = config["icon-size"].asUInt(); 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(); ipc_.handleEvent();
} catch (const std::exception& e) { } catch (const std::exception& e) {
spdlog::error("Window: {}", e.what()); 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_); std::lock_guard<std::mutex> lock(mutex_);
auto payload = parser_.parse(res.payload); auto payload = parser_.parse(res.payload);
auto output = payload["output"].isString() ? payload["output"].asString() : ""; 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); getFocusedNode(payload["nodes"], output);
updateAppIconName(); updateAppIconName();
dp.emit(); dp.emit();
} catch (const std::exception& e) { } catch (const std::exception& e) {
spdlog::error("Window: {}", e.what()); spdlog::error("Window: {}", e.what());
spdlog::trace("Window::onCmd exception");
} }
} }
@ -156,30 +158,56 @@ void Window::updateAppIcon() {
} }
auto Window::update() -> void { auto Window::update() -> void {
if (!old_app_id_.empty()) { spdlog::trace("workspace layout {}, tiled count {}, floating count {}", layout_, app_nb_,
bar_.window.get_style_context()->remove_class(old_app_id_); floating_count_);
}
int mode = 0;
if (app_nb_ == 0) { if (app_nb_ == 0) {
bar_.window.get_style_context()->remove_class("solo"); if (floating_count_ == 0) {
if (!bar_.window.get_style_context()->has_class("empty")) { mode += 1;
bar_.window.get_style_context()->add_class("empty"); } else {
mode += 4;
} }
} else if (app_nb_ == 1) { } else if (app_nb_ == 1) {
bar_.window.get_style_context()->remove_class("empty"); mode += 2;
if (!bar_.window.get_style_context()->has_class("solo")) { } else {
bar_.window.get_style_context()->add_class("solo"); 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_)) { if (!app_id_.empty() && !bar_.window.get_style_context()->has_class(app_id_)) {
bar_.window.get_style_context()->add_class(app_id_); bar_.window.get_style_context()->add_class(app_id_);
old_app_id_ = 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_) &&
fmt::arg("app_id", app_id_), fmt::arg("shell", shell_))); 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()) { if (tooltipEnabled()) {
label_.set_tooltip_text(window_); label_.set_tooltip_text(window_);
} }
@ -190,71 +218,143 @@ auto Window::update() -> void {
AIconLabel::update(); 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& nodes = node["nodes"];
auto const& floating_nodes = node["floating_nodes"]; auto const& floating_nodes = node["floating_nodes"];
if (nodes.empty() && floating_nodes.empty()) { if (nodes.empty() && floating_nodes.empty()) {
if (node["type"] == "workspace") if (node["type"].asString() == "workspace")
return 0; return {0, 0};
else else if (node["type"].asString() == "floating_con") {
return 1; return {0, 1};
} else {
return {1, 0};
}
} }
int sum = 0; int sum = 0;
if (!nodes.empty()) { int floating_sum = 0;
for (auto const& node : nodes) sum += leafNodesInWorkspace(node);
}
if (!floating_nodes.empty()) {
for (auto const& node : floating_nodes) sum += leafNodesInWorkspace(node);
}
return 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) {
for (auto const& node : nodes) { for (auto const& node : nodes) {
if (node["output"].isString()) { std::pair all_leaf_nodes = leafNodesInWorkspace(node);
output = node["output"].asString(); sum += all_leaf_nodes.first;
} floating_sum += all_leaf_nodes.second;
// 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()) {
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};
}
}
// 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};
}
} }
return {0, -1, "", "", "", ""}; 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, floating_sum};
} }
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>
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["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
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();
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
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};
}
}
// 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, int, std::string, std::string, std::string, std::string, std::string>
Window::getFocusedNode(const Json::Value& nodes, std::string& output) { Window::getFocusedNode(const Json::Value& nodes, std::string& output) {
Json::Value placeholder = 0; Json::Value placeholder = Json::Value::null;
return gfnWithWorkspace(nodes, output, config_, bar_, placeholder); return gfnWithWorkspace(nodes, output, config_, bar_, placeholder, placeholder);
} }
void Window::getTree() { void Window::getTree() {
@ -262,6 +362,7 @@ void Window::getTree() {
ipc_.sendCmd(IPC_GET_TREE); ipc_.sendCmd(IPC_GET_TREE);
} catch (const std::exception& e) { } catch (const std::exception& e) {
spdlog::error("Window: {}", e.what()); spdlog::error("Window: {}", e.what());
spdlog::trace("Window::getTree exception");
} }
} }

View File

@ -233,7 +233,7 @@ auto Workspaces::update() -> void {
std::string output = (*it)["name"].asString(); std::string output = (*it)["name"].asString();
if (config_["format"].isString()) { if (config_["format"].isString()) {
auto format = config_["format"].asString(); 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("value", output), fmt::arg("name", trimWorkspaceName(output)),
fmt::arg("index", (*it)["num"].asString())); fmt::arg("index", (*it)["num"].asString()));
} }
@ -259,11 +259,9 @@ Gtk::Button &Workspaces::addButton(const Json::Value &node) {
try { try {
if (node["target_output"].isString()) { if (node["target_output"].isString()) {
ipc_.sendCmd(IPC_COMMAND, ipc_.sendCmd(IPC_COMMAND,
fmt::format(workspace_switch_cmd_ + "; move workspace to output \"{}\"; " + fmt::format(persistent_workspace_switch_cmd_, "--no-auto-back-and-forth",
workspace_switch_cmd_, node["name"].asString(), node["target_output"].asString(),
"--no-auto-back-and-forth", node["name"].asString(), "--no-auto-back-and-forth", node["name"].asString()));
node["target_output"].asString(), "--no-auto-back-and-forth",
node["name"].asString()));
} else { } else {
ipc_.sendCmd(IPC_COMMAND, fmt::format("workspace {} \"{}\"", ipc_.sendCmd(IPC_COMMAND, fmt::format("workspace {} \"{}\"",
config_["disable-auto-back-and-forth"].asBool() config_["disable-auto-back-and-forth"].asBool()

View File

@ -55,7 +55,7 @@ auto waybar::modules::Temperature::update() -> void {
} }
auto max_temp = config_["critical-threshold"].isInt() ? config_["critical-threshold"].asInt() : 0; 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("temperatureF", temperature_f),
fmt::arg("temperatureK", temperature_k), fmt::arg("temperatureK", temperature_k),
fmt::arg("icon", getIcon(temperature_c, "", max_temp)))); fmt::arg("icon", getIcon(temperature_c, "", max_temp))));
@ -64,9 +64,9 @@ auto waybar::modules::Temperature::update() -> void {
if (config_["tooltip-format"].isString()) { if (config_["tooltip-format"].isString()) {
tooltip_format = config_["tooltip-format"].asString(); tooltip_format = config_["tooltip-format"].asString();
} }
label_.set_tooltip_text(fmt::format(tooltip_format, fmt::arg("temperatureC", temperature_c), label_.set_tooltip_text(fmt::format(
fmt::arg("temperatureF", temperature_f), fmt::runtime(tooltip_format), fmt::arg("temperatureC", temperature_c),
fmt::arg("temperatureK", temperature_k))); fmt::arg("temperatureF", temperature_f), fmt::arg("temperatureK", temperature_k)));
} }
// Call parent update // Call parent update
ALabel::update(); ALabel::update();

View File

@ -336,8 +336,8 @@ auto UPower::update() -> void {
break; break;
} }
std::string label_format = std::string label_format =
fmt::format(showAltText ? format_alt : format, fmt::arg("percentage", percentString), fmt::format(fmt::runtime(showAltText ? format_alt : format),
fmt::arg("time", time_format)); fmt::arg("percentage", percentString), fmt::arg("time", time_format));
// Only set the label text if it doesn't only contain spaces // Only set the label text if it doesn't only contain spaces
bool onlySpaces = true; bool onlySpaces = true;
for (auto& character : label_format) { for (auto& character : label_format) {

View File

@ -29,7 +29,7 @@ UPowerTooltip::~UPowerTooltip() {}
uint UPowerTooltip::updateTooltip(Devices& devices) { uint UPowerTooltip::updateTooltip(Devices& devices) {
// Removes all old devices // Removes all old devices
for (auto child : contentBox->get_children()) { for (auto child : contentBox->get_children()) {
child->~Widget(); delete child;
} }
uint deviceCount = 0; uint deviceCount = 0;

View File

@ -127,16 +127,16 @@ auto User::update() -> void {
auto startSystemTime = currentSystemTime - workSystemTimeSeconds; auto startSystemTime = currentSystemTime - workSystemTimeSeconds;
long workSystemDays = uptimeSeconds / 86400; long workSystemDays = uptimeSeconds / 86400;
auto label = fmt::format(ALabel::format_, fmt::arg("up_H", fmt::format("{:%H}", startSystemTime)), auto label = fmt::format(
fmt::arg("up_M", fmt::format("{:%M}", startSystemTime)), fmt::runtime(ALabel::format_), fmt::arg("up_H", fmt::format("{:%H}", startSystemTime)),
fmt::arg("up_d", fmt::format("{:%d}", startSystemTime)), fmt::arg("up_M", fmt::format("{:%M}", startSystemTime)),
fmt::arg("up_m", fmt::format("{:%m}", startSystemTime)), fmt::arg("up_d", fmt::format("{:%d}", startSystemTime)),
fmt::arg("up_Y", fmt::format("{:%Y}", startSystemTime)), fmt::arg("up_m", fmt::format("{:%m}", 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_H", fmt::format("{:%H}", workSystemTimeSeconds)),
fmt::arg("work_M", fmt::format("{:%M}", workSystemTimeSeconds)), fmt::arg("work_M", fmt::format("{:%M}", workSystemTimeSeconds)),
fmt::arg("work_S", fmt::format("{:%S}", workSystemTimeSeconds)), fmt::arg("work_S", fmt::format("{:%S}", workSystemTimeSeconds)),
fmt::arg("user", systemUser)); fmt::arg("user", systemUser));
ALabel::label_.set_markup(label); ALabel::label_.set_markup(label);
AIconLabel::update(); AIconLabel::update();
} }

View File

@ -1,15 +1,22 @@
#include "modules/wireplumber.hpp" #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) waybar::modules::Wireplumber::Wireplumber(const std::string& id, const Json::Value& config)
: ALabel(config, "wireplumber", id, "{volume}%"), : ALabel(config, "wireplumber", id, "{volume}%"),
wp_core_(nullptr), wp_core_(nullptr),
apis_(nullptr), apis_(nullptr),
om_(nullptr), om_(nullptr),
mixer_api_(nullptr),
def_nodes_api_(nullptr),
default_node_name_(nullptr),
pending_plugins_(0), pending_plugins_(0),
muted_(false), muted_(false),
volume_(0.0), volume_(0.0),
node_id_(0) { node_id_(0) {
wp_init(WP_INIT_ALL); wp_init(WP_INIT_PIPEWIRE);
wp_core_ = wp_core_new(NULL, NULL); wp_core_ = wp_core_new(NULL, NULL);
apis_ = g_ptr_array_new_with_free_func(g_object_unref); apis_ = g_ptr_array_new_with_free_func(g_object_unref);
om_ = wp_object_manager_new(); om_ = wp_object_manager_new();
@ -18,10 +25,15 @@ waybar::modules::Wireplumber::Wireplumber(const std::string& id, const Json::Val
loadRequiredApiModules(); loadRequiredApiModules();
spdlog::debug("[{}]: connecting to pipewire...", this->name_);
if (!wp_core_connect(wp_core_)) { 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"); throw std::runtime_error("Could not connect to PipeWire\n");
} }
spdlog::debug("[{}]: connected!", this->name_);
g_signal_connect_swapped(om_, "installed", (GCallback)onObjectManagerInstalled, this); g_signal_connect_swapped(om_, "installed", (GCallback)onObjectManagerInstalled, this);
activatePlugins(); activatePlugins();
@ -33,33 +45,26 @@ waybar::modules::Wireplumber::~Wireplumber() {
g_clear_pointer(&apis_, g_ptr_array_unref); g_clear_pointer(&apis_, g_ptr_array_unref);
g_clear_object(&om_); g_clear_object(&om_);
g_clear_object(&wp_core_); 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) { void waybar::modules::Wireplumber::updateNodeName(waybar::modules::Wireplumber* self, uint32_t id) {
uint32_t id; spdlog::debug("[{}]: updating node name with node.id {}", self->name_, id);
g_autoptr(WpPlugin) def_nodes_api = wp_plugin_find(self->wp_core_, "default-nodes-api");
if (!def_nodes_api) { if (!isValidNodeId(id)) {
throw std::runtime_error("Default nodes API is not loaded\n"); 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); 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 (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));
if (!proxy) { 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 = g_autoptr(WpProperties) properties =
@ -73,15 +78,24 @@ void waybar::modules::Wireplumber::updateNodeName(waybar::modules::Wireplumber*
auto description = wp_properties_get(properties, "node.description"); auto description = wp_properties_get(properties, "node.description");
self->node_name_ = nick ? nick : 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; double vol;
GVariant* variant = NULL; 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) { 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); throw std::runtime_error(err);
} }
@ -93,22 +107,121 @@ void waybar::modules::Wireplumber::updateVolume(waybar::modules::Wireplumber* se
self->dp.emit(); 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) { void waybar::modules::Wireplumber::onObjectManagerInstalled(waybar::modules::Wireplumber* self) {
self->node_id_ = spdlog::debug("[{}]: onObjectManagerInstalled", self->name_);
self->config_["node-id"].isInt() ? self->config_["node-id"].asInt() : getDefaultNodeId(self);
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); if (!self->def_nodes_api_) {
updateNodeName(self); spdlog::error("[{}]: default nodes api is not loaded.", self->name_);
g_signal_connect_swapped(mixer_api, "changed", (GCallback)updateVolume, self); 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, void waybar::modules::Wireplumber::onPluginActivated(WpObject* p, GAsyncResult* res,
waybar::modules::Wireplumber* self) { 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; g_autoptr(GError) error = NULL;
if (!wp_object_activate_finish(p, res, &error)) { if (!wp_object_activate_finish(p, res, &error)) {
spdlog::error("[{}]: error activating plugin: {}", self->name_, error->message);
throw std::runtime_error(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() { void waybar::modules::Wireplumber::activatePlugins() {
spdlog::debug("[{}]: activating plugins", name_);
for (uint16_t i = 0; i < apis_->len; i++) { for (uint16_t i = 0; i < apis_->len; i++) {
WpPlugin* plugin = static_cast<WpPlugin*>(g_ptr_array_index(apis_, i)); WpPlugin* plugin = static_cast<WpPlugin*>(g_ptr_array_index(apis_, i));
pending_plugins_++; pending_plugins_++;
@ -127,13 +241,13 @@ void waybar::modules::Wireplumber::activatePlugins() {
} }
void waybar::modules::Wireplumber::prepare() { void waybar::modules::Wireplumber::prepare() {
wp_object_manager_add_interest(om_, WP_TYPE_NODE, NULL); spdlog::debug("[{}]: preparing object manager", name_);
wp_object_manager_add_interest(om_, WP_TYPE_GLOBAL_PROXY, NULL); wp_object_manager_add_interest(om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_PW_PROPERTY, "media.class",
wp_object_manager_request_object_features(om_, WP_TYPE_GLOBAL_PROXY, "=s", "Audio/Sink", NULL);
WP_PIPEWIRE_OBJECT_FEATURES_MINIMAL);
} }
void waybar::modules::Wireplumber::loadRequiredApiModules() { void waybar::modules::Wireplumber::loadRequiredApiModules() {
spdlog::debug("[{}]: loading required modules", name_);
g_autoptr(GError) error = NULL; g_autoptr(GError) error = NULL;
if (!wp_core_load_component(wp_core_, "libwireplumber-module-default-nodes-api", "module", 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"); 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_))); fmt::arg("volume", volume_), fmt::arg("icon", getIcon(volume_)));
label_.set_markup(markup); label_.set_markup(markup);
@ -177,9 +291,9 @@ auto waybar::modules::Wireplumber::update() -> void {
} }
if (!tooltip_format.empty()) { if (!tooltip_format.empty()) {
label_.set_tooltip_text(fmt::format(tooltip_format, fmt::arg("node_name", node_name_), label_.set_tooltip_text(
fmt::arg("volume", volume_), fmt::format(fmt::runtime(tooltip_format), fmt::arg("node_name", node_name_),
fmt::arg("icon", getIcon(volume_)))); fmt::arg("volume", volume_), fmt::arg("icon", getIcon(volume_))));
} else { } else {
label_.set_tooltip_text(node_name_); label_.set_tooltip_text(node_name_);
} }

View File

@ -102,8 +102,11 @@ Glib::RefPtr<Gio::DesktopAppInfo> get_desktop_app_info(const std::string &app_id
desktop_file = desktop_list[0][i]; desktop_file = desktop_list[0][i];
} else { } else {
auto tmp_info = Gio::DesktopAppInfo::create(desktop_list[0][i]); 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) { if (startup_class == app_id) {
desktop_file = desktop_list[0][i]; desktop_file = desktop_list[0][i];
break; break;
@ -615,9 +618,10 @@ void Task::update() {
app_id = Glib::Markup::escape_text(app_id); app_id = Glib::Markup::escape_text(app_id);
} }
if (!format_before_.empty()) { if (!format_before_.empty()) {
auto txt = fmt::format(format_before_, fmt::arg("title", title), fmt::arg("name", name), auto txt =
fmt::arg("app_id", app_id), fmt::arg("state", state_string()), fmt::format(fmt::runtime(format_before_), fmt::arg("title", title), fmt::arg("name", name),
fmt::arg("short_state", state_string(true))); fmt::arg("app_id", app_id), fmt::arg("state", state_string()),
fmt::arg("short_state", state_string(true)));
if (markup) if (markup)
text_before_.set_markup(txt); text_before_.set_markup(txt);
else else
@ -625,9 +629,10 @@ void Task::update() {
text_before_.show(); text_before_.show();
} }
if (!format_after_.empty()) { if (!format_after_.empty()) {
auto txt = fmt::format(format_after_, fmt::arg("title", title), fmt::arg("name", name), auto txt =
fmt::arg("app_id", app_id), fmt::arg("state", state_string()), fmt::format(fmt::runtime(format_after_), fmt::arg("title", title), fmt::arg("name", name),
fmt::arg("short_state", state_string(true))); fmt::arg("app_id", app_id), fmt::arg("state", state_string()),
fmt::arg("short_state", state_string(true)));
if (markup) if (markup)
text_after_.set_markup(txt); text_after_.set_markup(txt);
else else
@ -636,9 +641,10 @@ void Task::update() {
} }
if (!format_tooltip_.empty()) { if (!format_tooltip_.empty()) {
auto txt = fmt::format(format_tooltip_, fmt::arg("title", title), fmt::arg("name", name), auto txt =
fmt::arg("app_id", app_id), fmt::arg("state", state_string()), fmt::format(fmt::runtime(format_tooltip_), fmt::arg("title", title), fmt::arg("name", name),
fmt::arg("short_state", state_string(true))); fmt::arg("app_id", app_id), fmt::arg("state", state_string()),
fmt::arg("short_state", state_string(true)));
if (markup) if (markup)
button_.set_tooltip_markup(txt); button_.set_tooltip_markup(txt);
else else

View File

@ -169,7 +169,7 @@ WorkspaceManager::~WorkspaceManager() {
wl_display *display = Client::inst()->wl_display; wl_display *display = Client::inst()->wl_display;
// Send `stop` request and wait for one roundtrip. This is not quite correct as // 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 // the protocol encourages us to wait for the .finished event, but it should work
// with wlroots workspace manager implementation. // with wlroots workspace manager implementation.
zext_workspace_manager_v1_stop(workspace_manager_); zext_workspace_manager_v1_stop(workspace_manager_);
@ -379,7 +379,7 @@ Workspace::~Workspace() {
} }
auto Workspace::update() -> void { 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() : ""))); fmt::arg("icon", with_icon_ ? get_icon() : "")));
} }

View File

@ -2,7 +2,11 @@
#include <glibmm.h> #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 <thread>
#include <type_traits> #include <type_traits>

View File

@ -1,6 +1,10 @@
#include "config.hpp" #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]") { TEST_CASE("Load simple config", "[config]") {
waybar::Config conf; waybar::Config conf;

162
test/date.cpp Normal file
View File

@ -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");
}
}
}

View File

@ -3,8 +3,13 @@
#include <spdlog/sinks/stdout_sinks.h> #include <spdlog/sinks/stdout_sinks.h>
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
#if __has_include(<catch2/catch_all.hpp>)
#include <catch2/catch_all.hpp> #include <catch2/catch_all.hpp>
#include <catch2/reporters/catch_reporter_tap.hpp> #include <catch2/reporters/catch_reporter_tap.hpp>
#else
#include <catch2/catch.hpp>
#include <catch2/catch_reporter_tap.hpp>
#endif
#include <memory> #include <memory>
int main(int argc, char* argv[]) { int main(int argc, char* argv[]) {
@ -13,10 +18,16 @@ int main(int argc, char* argv[]) {
session.applyCommandLine(argc, argv); session.applyCommandLine(argc, argv);
const auto logger = spdlog::default_logger(); const auto logger = spdlog::default_logger();
#if CATCH_VERSION_MAJOR >= 3
for (const auto& spec : session.config().getReporterSpecs()) { 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"); spdlog::set_pattern("# [%l] %v");
} else if (spec.name() == "compact") { } else if (reporter_name == "compact") {
logger->sinks().clear(); logger->sinks().clear();
} else { } else {
logger->sinks().assign({std::make_shared<spdlog::sinks::stderr_sink_st>()}); logger->sinks().assign({std::make_shared<spdlog::sinks::stderr_sink_st>()});

View File

@ -15,7 +15,7 @@ test_src = files(
if tz_dep.found() if tz_dep.found()
test_dep += tz_dep test_dep += tz_dep
test_src += files('waybar_time.cpp') test_src += files('date.cpp')
endif endif
waybar_test = executable( waybar_test = executable(

View File

@ -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
}
}
}