From d5a86526bca19ec9032976a5397e34915edd5324 Mon Sep 17 00:00:00 2001 From: Narice Date: Tue, 6 Dec 2022 18:44:26 +0100 Subject: [PATCH 01/29] dev: Added Nix Flake support - Enables Nix users to get the git version of waybar - Enables Nix users to develop waybar easily - Adds a fully reproducible development environment - The user only has to install Nix, no other depencencies - Automatic dev env on directory entry through .envrc --- .envrc | 1 + .gitignore | 1 + flake.lock | 94 ++++++++++++++++++++++++++++++++ flake.nix | 65 ++++++++++++++++++++++ nix/default.nix | 139 ++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 300 insertions(+) create mode 100644 .envrc create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 nix/default.nix diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..3550a30 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.gitignore b/.gitignore index 56a2f73..11cc390 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,4 @@ packagecache *.exe *.out *.app +/.direnv/ diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..6d3b6b8 --- /dev/null +++ b/flake.lock @@ -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 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..d909186 --- /dev/null +++ b/flake.nix @@ -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; [ + ]; + }; + }); +} diff --git a/nix/default.nix b/nix/default.nix new file mode 100644 index 0000000..2665446 --- /dev/null +++ b/nix/default.nix @@ -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"; + }; +} From 0bc5314e089de7b30d166676e7fd1148023dfbdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20G=C3=BCnzler?= Date: Mon, 1 Nov 2021 19:17:29 +0100 Subject: [PATCH 02/29] Add mpris module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Uses libplayerctl to use the MPRIS dbus protocol to query, listen and control media players. Signed-off-by: Robert Günzler --- include/factory.hpp | 3 + include/modules/mpris/mpris.hpp | 67 ++++++ man/waybar-mpris.5.scd | 103 +++++++++ man/waybar.5.scd.in | 1 + meson.build | 13 +- meson_options.txt | 1 + src/factory.cpp | 5 + src/modules/mpris/mpris.cpp | 394 ++++++++++++++++++++++++++++++++ 8 files changed, 586 insertions(+), 1 deletion(-) create mode 100644 include/modules/mpris/mpris.hpp create mode 100644 man/waybar-mpris.5.scd create mode 100644 src/modules/mpris/mpris.cpp diff --git a/include/factory.hpp b/include/factory.hpp index d69930f..688b9ac 100644 --- a/include/factory.hpp +++ b/include/factory.hpp @@ -41,6 +41,9 @@ #ifdef HAVE_DBUSMENU #include "modules/sni/tray.hpp" #endif +#ifdef HAVE_MPRIS +#include "modules/mpris/mpris.hpp" +#endif #ifdef HAVE_LIBNL #include "modules/network.hpp" #endif diff --git a/include/modules/mpris/mpris.hpp b/include/modules/mpris/mpris.hpp new file mode 100644 index 0000000..4f8ddb1 --- /dev/null +++ b/include/modules/mpris/mpris.hpp @@ -0,0 +1,67 @@ +#pragma once + +#include +#include + +#include "gtkmm/box.h" +#include "gtkmm/label.h" + +extern "C" { +#include +} + +#include "ALabel.hpp" +#include "util/sleeper_thread.hpp" + +namespace waybar::modules::mpris { + +class Mpris : public AModule { + public: + Mpris(const std::string&, const Json::Value&); + ~Mpris(); + auto update() -> void; + bool handleToggle(GdkEventButton* const&); + + private: + static auto onPlayerNameAppeared(PlayerctlPlayerManager*, PlayerctlPlayerName*, gpointer) -> void; + static auto onPlayerNameVanished(PlayerctlPlayerManager*, PlayerctlPlayerName*, gpointer) -> void; + static auto onPlayerPlay(PlayerctlPlayer*, gpointer) -> void; + static auto onPlayerPause(PlayerctlPlayer*, gpointer) -> void; + static auto onPlayerStop(PlayerctlPlayer*, gpointer) -> void; + static auto onPlayerMetadata(PlayerctlPlayer*, GVariant*, gpointer) -> void; + + struct PlayerInfo { + std::string name; + PlayerctlPlaybackStatus status; + std::string status_string; + + std::optional artist; + std::optional album; + std::optional title; + std::optional length; // as HH:MM:SS + }; + + auto getPlayerInfo() -> std::optional; + auto getIcon(const Json::Value&, const std::string&) -> std::string; + + Gtk::Box box_; + Gtk::Label label_; + + // config + std::string format_; + std::string format_playing_; + std::string format_paused_; + std::string format_stopped_; + std::chrono::seconds interval_; + std::string player_; + std::vector ignored_players_; + + PlayerctlPlayerManager* manager; + PlayerctlPlayer* player; + std::string lastStatus; + std::string lastPlayer; + + util::SleeperThread thread_; +}; + +} // namespace waybar::modules::mpris diff --git a/man/waybar-mpris.5.scd b/man/waybar-mpris.5.scd new file mode 100644 index 0000000..d2a72d9 --- /dev/null +++ b/man/waybar-mpris.5.scd @@ -0,0 +1,103 @@ +waybar-mpris(5) + +# NAME + +waybar - MPRIS module + +# DESCRIPTION + +The *mpris* module displays currently playing media via libplayerctl. + +# CONFIGURATION + +*player*: ++ + typeof: string ++ + default: playerctld ++ + Name of the MPRIS player to attach to. Using the default value always + follows the currenly active player. + +*ignored-players*: ++ + typeof: []string ++ + Ignore updates of the listed players, when using playerctld. + +*interval*: ++ + typeof: integer ++ + Refresh MPRIS information on a timer. + +*format*: ++ + typeof: string ++ + default: {player} ({status}) {dynamic} ++ + The text format. + +*format-[status]*: ++ + typeof: string ++ + The status-specific text format. + +*on-click*: ++ + typeof: string ++ + default: play-pause ++ + Overwrite default action toggles. + +*on-middle-click*: ++ + typeof: string ++ + default: previous track ++ + Overwrite default action toggles. + +*on-right-click*: ++ + typeof: string ++ + default: next track ++ + Overwrite default action toggles. + +*player-icons*: ++ + typeof: map[string]string + Allows setting _{player-icon}_ based on player-name property. + +*status-icons*: ++ + typeof: map[string]string + Allows setting _{status-icon}_ based on player status (playing, paused, + stopped). + + +# FORMAT REPLACEMENTS + +*{player}*: The name of the current media player + +*{status}*: The current status (playing, paused, stopped) + +*{artist}*: The artist of the current track + +*{album}*: The album title of the current track + +*{title}*: The title of the current track + +*{length}*: Length of the track, formatted as HH:MM:SS + +*{dynamic}*: Use _{artist}_, _{album}_, _{title}_ and _{length}_, automatically omit++ + empty values + +*{player-icon}*: Chooses an icon from _player-icons_ based on _{player}_ + +*{status-icon}*: Chooses an icon from _status-icons_ based on _{status}_ + +# EXAMPLES + +``` +"mpris": { + "format": "DEFAULT: {player_icon} {dynamic}", + "format-paused": "DEFAULT: {status_icon} {dynamic}", + "player-icons": { + "default": "▶", + "mpv": "🎵" + }, + "status-icons": { + "paused": "⏸" + }, + // "ignored-players": ["firefox"] +} +``` + +# STYLE + +- *#mpris* +- *#mpris.${status}* +- *#mpris.${player}* diff --git a/man/waybar.5.scd.in b/man/waybar.5.scd.in index 54340f2..b1ed4c5 100644 --- a/man/waybar.5.scd.in +++ b/man/waybar.5.scd.in @@ -266,6 +266,7 @@ A module group is defined by specifying a module named "group/some-group-name". - *waybar-keyboard-state(5)* - *waybar-memory(5)* - *waybar-mpd(5)* +- *waybar-mpris(5)* - *waybar-network(5)* - *waybar-pulseaudio(5)* - *waybar-river-mode(5)* diff --git a/meson.build b/meson.build index 557a02d..83f5998 100644 --- a/meson.build +++ b/meson.build @@ -86,7 +86,10 @@ wayland_cursor = dependency('wayland-cursor') wayland_protos = dependency('wayland-protocols') gtkmm = dependency('gtkmm-3.0', version : ['>=3.22.0']) dbusmenu_gtk = dependency('dbusmenu-gtk3-0.4', required: get_option('dbusmenu-gtk')) -giounix = dependency('gio-unix-2.0', required: (get_option('dbusmenu-gtk').enabled() or get_option('logind').enabled() or get_option('upower_glib').enabled())) +giounix = dependency('gio-unix-2.0', required: (get_option('dbusmenu-gtk').enabled() or + get_option('logind').enabled() or + get_option('upower_glib').enabled() or + get_option('mpris').enabled())) jsoncpp = dependency('jsoncpp', version : ['>=1.9.2'], fallback : ['jsoncpp', 'jsoncpp_dep']) sigcpp = dependency('sigc++-2.0') libinotify = dependency('libinotify', required: false) @@ -95,6 +98,7 @@ libinput = dependency('libinput', required: get_option('libinput')) libnl = dependency('libnl-3.0', required: get_option('libnl')) libnlgen = dependency('libnl-genl-3.0', required: get_option('libnl')) upower_glib = dependency('upower-glib', required: get_option('upower_glib')) +playerctl = dependency('playerctl', version : ['>=2.0.0'], required: get_option('mpris')) libpulse = dependency('libpulse', required: get_option('pulseaudio')) libudev = dependency('libudev', required: get_option('libudev')) libevdev = dependency('libevdev', required: get_option('libevdev')) @@ -238,6 +242,11 @@ if (upower_glib.found() and giounix.found() and not get_option('logind').disable src_files += 'src/modules/upower/upower_tooltip.cpp' endif +if (playerctl.found() and giounix.found() and not get_option('logind').disabled()) + add_project_arguments('-DHAVE_MPRIS', language: 'cpp') + src_files += 'src/modules/mpris/mpris.cpp' +endif + if libpulse.found() add_project_arguments('-DHAVE_LIBPULSE', language: 'cpp') src_files += 'src/modules/pulseaudio.cpp' @@ -334,6 +343,7 @@ executable( libnl, libnlgen, upower_glib, + playerctl, libpulse, libjack, libwireplumber, @@ -387,6 +397,7 @@ if scdoc.found() 'waybar-keyboard-state.5.scd', 'waybar-memory.5.scd', 'waybar-mpd.5.scd', + 'waybar-mpris.5.scd', 'waybar-network.5.scd', 'waybar-pulseaudio.5.scd', 'waybar-river-mode.5.scd', diff --git a/meson_options.txt b/meson_options.txt index 402912f..98cd494 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -5,6 +5,7 @@ option('libudev', type: 'feature', value: 'auto', description: 'Enable libudev s option('libevdev', type: 'feature', value: 'auto', description: 'Enable libevdev support for evdev related features') option('pulseaudio', type: 'feature', value: 'auto', description: 'Enable support for pulseaudio') option('upower_glib', type: 'feature', value: 'auto', description: 'Enable support for upower') +option('mpris', type: 'feature', value: 'auto', description: 'Enable support for mpris') option('systemd', type: 'feature', value: 'auto', description: 'Install systemd user service unit') option('dbusmenu-gtk', type: 'feature', value: 'auto', description: 'Enable support for tray') option('man-pages', type: 'feature', value: 'auto', description: 'Generate and install man pages') diff --git a/src/factory.cpp b/src/factory.cpp index d16cb52..3ccf258 100644 --- a/src/factory.cpp +++ b/src/factory.cpp @@ -22,6 +22,11 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const { return new waybar::modules::upower::UPower(id, config_[name]); } #endif +#ifdef HAVE_MPRIS + if (ref == "mpris") { + return new waybar::modules::mpris::Mpris(id, config_[name]); + } +#endif #ifdef HAVE_SWAY if (ref == "sway/mode") { return new waybar::modules::sway::Mode(id, config_[name]); diff --git a/src/modules/mpris/mpris.cpp b/src/modules/mpris/mpris.cpp new file mode 100644 index 0000000..651dfd5 --- /dev/null +++ b/src/modules/mpris/mpris.cpp @@ -0,0 +1,394 @@ +#include "modules/mpris/mpris.hpp" + +#include + +#include +#include +#include + +extern "C" { +#include +} + +#include + +namespace waybar::modules::mpris { + +const std::string DEFAULT_FORMAT = "{player} ({status}): {dynamic}"; + +Mpris::Mpris(const std::string& id, const Json::Value& config) + : AModule(config, "mpris", id), + box_(Gtk::ORIENTATION_HORIZONTAL, 0), + label_(), + format_(DEFAULT_FORMAT), + interval_(0), + player_("playerctld"), + manager(), + player() { + box_.pack_start(label_); + box_.set_name(name_); + event_box_.add(box_); + event_box_.signal_button_press_event().connect(sigc::mem_fun(*this, &Mpris::handleToggle)); + + if (config_["format"].isString()) { + format_ = config_["format"].asString(); + } + if (config_["format-playing"].isString()) { + format_playing_ = config_["format-playing"].asString(); + } + if (config_["format-paused"].isString()) { + format_paused_ = config_["format-paused"].asString(); + } + if (config_["format-stopped"].isString()) { + format_stopped_ = config_["format-stopped"].asString(); + } + if (config_["interval"].isUInt()) { + interval_ = std::chrono::seconds(config_["interval"].asUInt()); + } + if (config_["player"].isString()) { + player_ = config_["player"].asString(); + } + if (config_["ignored-players"].isArray()) { + for (auto it = config_["ignored-players"].begin(); it != config_["ignored-players"].end(); + ++it) { + ignored_players_.push_back(it->asString()); + } + } + + GError* error = nullptr; + manager = playerctl_player_manager_new(&error); + if (error) { + throw std::runtime_error(fmt::format("unable to create MPRIS client: {}", error->message)); + } + + g_object_connect(manager, "signal::name-appeared", G_CALLBACK(onPlayerNameAppeared), this, NULL); + g_object_connect(manager, "signal::name-vanished", G_CALLBACK(onPlayerNameVanished), this, NULL); + + if (player_ == "playerctld") { + // use playerctld proxy + PlayerctlPlayerName name = { + .instance = (gchar*)player_.c_str(), + .source = PLAYERCTL_SOURCE_DBUS_SESSION, + }; + player = playerctl_player_new_from_name(&name, &error); + + } else { + GList* players = playerctl_list_players(&error); + if (error) { + auto e = fmt::format("unable to list players: {}", error->message); + g_error_free(error); + throw std::runtime_error(e); + } + + for (auto p = players; p != NULL; p = p->next) { + auto pn = static_cast(p->data); + if (strcmp(pn->name, player_.c_str()) == 0) { + player = playerctl_player_new_from_name(pn, &error); + break; + } + } + } + + if (error) { + throw std::runtime_error( + fmt::format("unable to connect to player {}: {}", player_, error->message)); + } + + if (player) { + g_object_connect(player, "signal::play", G_CALLBACK(onPlayerPlay), this, "signal::pause", + G_CALLBACK(onPlayerPause), this, "signal::stop", G_CALLBACK(onPlayerStop), + this, "signal::stop", G_CALLBACK(onPlayerStop), this, "signal::metadata", + G_CALLBACK(onPlayerMetadata), this, NULL); + } + + // allow setting an interval count that triggers periodic refreshes + if (interval_.count() > 0) { + thread_ = [this] { + dp.emit(); + thread_.sleep_for(interval_); + }; + } + + // trigger initial update + dp.emit(); +} + +Mpris::~Mpris() { + if (manager != NULL) g_object_unref(manager); + if (player != NULL) g_object_unref(player); +} + +auto Mpris::getIcon(const Json::Value& icons, const std::string& key) -> std::string { + if (icons.isObject()) { + if (icons[key].isString()) { + return icons[key].asString(); + } else if (icons["default"].isString()) { + return icons["default"].asString(); + } + } + return ""; +} + +auto Mpris::onPlayerNameAppeared(PlayerctlPlayerManager* manager, PlayerctlPlayerName* player_name, + gpointer data) -> void { + Mpris* mpris = static_cast(data); + if (!mpris) return; + + spdlog::debug("mpris: name-appeared callback: {}", player_name->name); + + if (std::string(player_name->name) != mpris->player_) { + return; + } + + GError* error = nullptr; + mpris->player = playerctl_player_new_from_name(player_name, &error); + g_object_connect(mpris->player, "signal::play", G_CALLBACK(onPlayerPlay), mpris, "signal::pause", + G_CALLBACK(onPlayerPause), mpris, "signal::stop", G_CALLBACK(onPlayerStop), + mpris, "signal::stop", G_CALLBACK(onPlayerStop), mpris, "signal::metadata", + G_CALLBACK(onPlayerMetadata), mpris, NULL); + + mpris->dp.emit(); +} + +auto Mpris::onPlayerNameVanished(PlayerctlPlayerManager* manager, PlayerctlPlayerName* player_name, + gpointer data) -> void { + Mpris* mpris = static_cast(data); + if (!mpris) return; + + spdlog::debug("mpris: player-vanished callback: {}", player_name->name); + + if (std::string(player_name->name) == mpris->player_) { + mpris->player = nullptr; + mpris->dp.emit(); + } +} + +auto Mpris::onPlayerPlay(PlayerctlPlayer* player, gpointer data) -> void { + Mpris* mpris = static_cast(data); + if (!mpris) return; + + spdlog::debug("mpris: player-play callback"); + // update widget + mpris->dp.emit(); +} + +auto Mpris::onPlayerPause(PlayerctlPlayer* player, gpointer data) -> void { + Mpris* mpris = static_cast(data); + if (!mpris) return; + + spdlog::debug("mpris: player-pause callback"); + // update widget + mpris->dp.emit(); +} + +auto Mpris::onPlayerStop(PlayerctlPlayer* player, gpointer data) -> void { + Mpris* mpris = static_cast(data); + if (!mpris) return; + + spdlog::debug("mpris: player-stop callback"); + + // hide widget + mpris->event_box_.set_visible(false); + // update widget + mpris->dp.emit(); +} + +auto Mpris::onPlayerMetadata(PlayerctlPlayer* player, GVariant* metadata, gpointer data) -> void { + Mpris* mpris = static_cast(data); + if (!mpris) return; + + spdlog::debug("mpris: player-metadata callback"); + // update widget + mpris->dp.emit(); +} + +auto Mpris::getPlayerInfo() -> std::optional { + if (!player) { + return std::nullopt; + } + + GError* error = nullptr; + + char* player_status = nullptr; + auto player_playback_status = PLAYERCTL_PLAYBACK_STATUS_STOPPED; + g_object_get(player, "status", &player_status, "playback-status", &player_playback_status, NULL); + + std::string player_name = player_; + if (player_name == "playerctld") { + GList* players = playerctl_list_players(&error); + if (error) { + auto e = fmt::format("unable to list players: {}", error->message); + g_error_free(error); + throw std::runtime_error(e); + } + // > get the list of players [..] in order of activity + // https://github.com/altdesktop/playerctl/blob/b19a71cb9dba635df68d271bd2b3f6a99336a223/playerctl/playerctl-common.c#L248-L249 + players = g_list_first(players); + if (players) player_name = static_cast(players->data)->name; + } + + if (std::any_of(ignored_players_.begin(), ignored_players_.end(), + [&](const std::string& pn) { return player_name == pn; })) { + spdlog::warn("mpris[{}]: ignoring player update", player_name); + return std::nullopt; + } + + // make status lowercase + player_status[0] = std::tolower(player_status[0]); + + PlayerInfo info = { + .name = player_name, + .status = player_playback_status, + .status_string = player_status, + }; + + if (auto artist_ = playerctl_player_get_artist(player, &error)) { + spdlog::debug("mpris[{}]: artist = {}", info.name, artist_); + info.artist = Glib::Markup::escape_text(artist_); + g_free(artist_); + } + if (error) goto errorexit; + + if (auto album_ = playerctl_player_get_album(player, &error)) { + spdlog::debug("mpris[{}]: album = {}", info.name, album_); + info.album = Glib::Markup::escape_text(album_); + g_free(album_); + } + if (error) goto errorexit; + + if (auto title_ = playerctl_player_get_title(player, &error)) { + spdlog::debug("mpris[{}]: title = {}", info.name, title_); + info.title = Glib::Markup::escape_text(title_); + g_free(title_); + } + if (error) goto errorexit; + + if (auto length_ = playerctl_player_print_metadata_prop(player, "mpris:length", &error)) { + spdlog::debug("mpris[{}]: mpris:length = {}", info.name, length_); + std::chrono::microseconds len = std::chrono::microseconds(std::strtol(length_, nullptr, 10)); + auto len_h = std::chrono::duration_cast(len); + auto len_m = std::chrono::duration_cast(len - len_h); + auto len_s = std::chrono::duration_cast(len - len_m); + info.length = fmt::format("{:02}:{:02}:{:02}", len_h.count(), len_m.count(), len_s.count()); + g_free(length_); + } + if (error) goto errorexit; + + return info; + +errorexit: + spdlog::error("mpris[{}]: {}", info.name, error->message); + g_error_free(error); + return std::nullopt; +} + +bool Mpris::handleToggle(GdkEventButton* const& e) { + GError* error = nullptr; + + auto info = getPlayerInfo(); + if (!info) return false; + + if (e->type == GdkEventType::GDK_BUTTON_PRESS) { + switch (e->button) { + case 1: // left-click + if (config_["on-click"].isString()) { + return AModule::handleToggle(e); + } + playerctl_player_play_pause(player, &error); + break; + case 2: // middle-click + if (config_["on-middle-click"].isString()) { + return AModule::handleToggle(e); + } + playerctl_player_previous(player, &error); + break; + case 3: // right-click + if (config_["on-right-click"].isString()) { + return AModule::handleToggle(e); + } + playerctl_player_next(player, &error); + break; + } + } + if (error) { + spdlog::error("mpris[{}]: error running builtin on-click action: {}", (*info).name, + error->message); + g_error_free(error); + return false; + } + return true; +} + +auto Mpris::update() -> void { + auto opt = getPlayerInfo(); + if (!opt) { + event_box_.set_visible(false); + AModule::update(); + return; + } + auto info = *opt; + + if (info.status == PLAYERCTL_PLAYBACK_STATUS_STOPPED) { + spdlog::debug("mpris[{}]: player stopped, skipping update", info.name); + return; + } + + spdlog::debug("mpris[{}]: running update", info.name); + + // dynamic is the auto-formatted string containing a nice out-of-the-box + // format text + std::stringstream dynamic; + if (info.artist) dynamic << *info.artist << " - "; + if (info.album) dynamic << *info.album << " - "; + if (info.title) dynamic << *info.title; + if (info.length) + dynamic << " " + << "" + << "[" << *info.length << "]" + << ""; + + // set css class for player status + if (!lastStatus.empty() && box_.get_style_context()->has_class(lastStatus)) { + box_.get_style_context()->remove_class(lastStatus); + } + if (!box_.get_style_context()->has_class(info.status_string)) { + box_.get_style_context()->add_class(info.status_string); + } + lastStatus = info.status_string; + + // set css class for player name + if (!lastPlayer.empty() && box_.get_style_context()->has_class(lastPlayer)) { + box_.get_style_context()->remove_class(lastPlayer); + } + if (!box_.get_style_context()->has_class(info.name)) { + box_.get_style_context()->add_class(info.name); + } + lastPlayer = info.name; + + auto formatstr = format_; + switch (info.status) { + case PLAYERCTL_PLAYBACK_STATUS_PLAYING: + if (!format_playing_.empty()) formatstr = format_playing_; + break; + case PLAYERCTL_PLAYBACK_STATUS_PAUSED: + if (!format_paused_.empty()) formatstr = format_paused_; + break; + case PLAYERCTL_PLAYBACK_STATUS_STOPPED: + if (!format_stopped_.empty()) formatstr = format_stopped_; + break; + } + auto label_format = + fmt::format(formatstr, fmt::arg("player", info.name), fmt::arg("status", info.status_string), + fmt::arg("artist", *info.artist), fmt::arg("title", *info.title), + fmt::arg("album", *info.album), fmt::arg("length", *info.length), + fmt::arg("dynamic", dynamic.str()), + fmt::arg("player_icon", getIcon(config_["player-icons"], info.name)), + fmt::arg("status_icon", getIcon(config_["status-icons"], info.status_string))); + label_.set_markup(label_format); + + event_box_.set_visible(true); + // call parent update + AModule::update(); +} + +} // namespace waybar::modules::mpris From b3b5d8f9ab73668664a439a1966f5e9a13fbd84d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20G=C3=BCnzler?= Date: Sat, 7 Jan 2023 01:42:42 +0100 Subject: [PATCH 03/29] Activate ci for mpris module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Robert Günzler --- Dockerfiles/alpine | 2 +- Dockerfiles/archlinux | 2 +- Dockerfiles/debian | 2 +- Dockerfiles/fedora | 3 ++- Dockerfiles/gentoo | 2 +- Dockerfiles/opensuse | 2 +- 6 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Dockerfiles/alpine b/Dockerfiles/alpine index 03836aa..d9a3dd5 100644 --- a/Dockerfiles/alpine +++ b/Dockerfiles/alpine @@ -2,4 +2,4 @@ FROM alpine:latest -RUN apk add --no-cache git meson alpine-sdk libinput-dev wayland-dev wayland-protocols mesa-dev libxkbcommon-dev eudev-dev pixman-dev gtkmm3-dev jsoncpp-dev pugixml-dev libnl3-dev pulseaudio-dev libmpdclient-dev sndio-dev scdoc libxkbcommon tzdata +RUN apk add --no-cache git meson alpine-sdk libinput-dev wayland-dev wayland-protocols mesa-dev libxkbcommon-dev eudev-dev pixman-dev gtkmm3-dev jsoncpp-dev pugixml-dev libnl3-dev pulseaudio-dev libmpdclient-dev sndio-dev scdoc libxkbcommon tzdata playerctl-dev diff --git a/Dockerfiles/archlinux b/Dockerfiles/archlinux index 40a1b2e..e7cbba6 100644 --- a/Dockerfiles/archlinux +++ b/Dockerfiles/archlinux @@ -3,4 +3,4 @@ FROM archlinux:base-devel RUN pacman -Syu --noconfirm && \ - pacman -S git meson base-devel libinput wayland wayland-protocols pixman libxkbcommon mesa gtkmm3 jsoncpp pugixml scdoc libpulse libdbusmenu-gtk3 libmpdclient gobject-introspection --noconfirm libxkbcommon + pacman -S --noconfirm git meson base-devel libinput wayland wayland-protocols pixman libxkbcommon mesa gtkmm3 jsoncpp pugixml scdoc libpulse libdbusmenu-gtk3 libmpdclient gobject-introspection libxkbcommon playerctl diff --git a/Dockerfiles/debian b/Dockerfiles/debian index 026d8fd..578588c 100644 --- a/Dockerfiles/debian +++ b/Dockerfiles/debian @@ -3,5 +3,5 @@ FROM debian:sid RUN apt-get update && \ - apt-get install -y build-essential meson ninja-build git pkg-config libinput10 libpugixml-dev libinput-dev wayland-protocols libwayland-client0 libwayland-cursor0 libwayland-dev libegl1-mesa-dev libgles2-mesa-dev libgbm-dev libxkbcommon-dev libudev-dev libpixman-1-dev libgtkmm-3.0-dev libjsoncpp-dev scdoc libdbusmenu-gtk3-dev libnl-3-dev libnl-genl-3-dev libpulse-dev libmpdclient-dev gobject-introspection libgirepository1.0-dev libxkbcommon-dev libxkbregistry-dev libxkbregistry0 && \ + apt-get install -y build-essential meson ninja-build git pkg-config libinput10 libpugixml-dev libinput-dev wayland-protocols libwayland-client0 libwayland-cursor0 libwayland-dev libegl1-mesa-dev libgles2-mesa-dev libgbm-dev libxkbcommon-dev libudev-dev libpixman-1-dev libgtkmm-3.0-dev libjsoncpp-dev scdoc libdbusmenu-gtk3-dev libnl-3-dev libnl-genl-3-dev libpulse-dev libmpdclient-dev gobject-introspection libgirepository1.0-dev libxkbcommon-dev libxkbregistry-dev libxkbregistry0 libplayerctl-dev && \ apt-get clean diff --git a/Dockerfiles/fedora b/Dockerfiles/fedora index a61dcd3..e1abd44 100644 --- a/Dockerfiles/fedora +++ b/Dockerfiles/fedora @@ -8,5 +8,6 @@ RUN dnf install -y @c-development git-core meson scdoc 'pkgconfig(date)' \ 'pkgconfig(jsoncpp)' 'pkgconfig(libinput)' 'pkgconfig(libmpdclient)' \ 'pkgconfig(libnl-3.0)' 'pkgconfig(libnl-genl-3.0)' 'pkgconfig(libpulse)' \ 'pkgconfig(libudev)' 'pkgconfig(pugixml)' 'pkgconfig(sigc++-2.0)' 'pkgconfig(spdlog)' \ - 'pkgconfig(wayland-client)' 'pkgconfig(wayland-cursor)' 'pkgconfig(wayland-protocols)' 'pkgconfig(xkbregistry)' && \ + 'pkgconfig(wayland-client)' 'pkgconfig(wayland-cursor)' 'pkgconfig(wayland-protocols)' 'pkgconfig(xkbregistry)' \ + 'pkgconfig(playerctl)' && \ dnf clean all -y diff --git a/Dockerfiles/gentoo b/Dockerfiles/gentoo index 536ef63..2b68398 100644 --- a/Dockerfiles/gentoo +++ b/Dockerfiles/gentoo @@ -8,4 +8,4 @@ RUN export FEATURES="-ipc-sandbox -network-sandbox -pid-sandbox -sandbox -usersa emerge --verbose --update --deep --with-bdeps=y --backtrack=30 --newuse @world && \ USE="wayland gtk3 gtk -doc X" emerge dev-vcs/git dev-libs/wayland dev-libs/wayland-protocols =dev-cpp/gtkmm-3.24.6 x11-libs/libxkbcommon \ x11-libs/gtk+:3 dev-libs/libdbusmenu dev-libs/libnl sys-power/upower media-libs/libpulse dev-libs/libevdev media-libs/libmpdclient \ - media-sound/sndio gui-libs/gtk-layer-shell app-text/scdoc + media-sound/sndio gui-libs/gtk-layer-shell app-text/scdoc media-sound/playerctl diff --git a/Dockerfiles/opensuse b/Dockerfiles/opensuse index 49dea27..bdb42fb 100644 --- a/Dockerfiles/opensuse +++ b/Dockerfiles/opensuse @@ -6,4 +6,4 @@ RUN zypper -n up && \ zypper addrepo https://download.opensuse.org/repositories/X11:Wayland/openSUSE_Tumbleweed/X11:Wayland.repo | echo 'a' && \ zypper -n refresh && \ zypper -n install -t pattern devel_C_C++ && \ - zypper -n install git meson clang libinput10 libinput-devel pugixml-devel libwayland-client0 libwayland-cursor0 wayland-protocols-devel wayland-devel Mesa-libEGL-devel Mesa-libGLESv2-devel libgbm-devel libxkbcommon-devel libudev-devel libpixman-1-0-devel gtkmm3-devel jsoncpp-devel libxkbregistry-devel scdoc + zypper -n install git meson clang libinput10 libinput-devel pugixml-devel libwayland-client0 libwayland-cursor0 wayland-protocols-devel wayland-devel Mesa-libEGL-devel Mesa-libGLESv2-devel libgbm-devel libxkbcommon-devel libudev-devel libpixman-1-0-devel gtkmm3-devel jsoncpp-devel libxkbregistry-devel scdoc playerctl-devel From 24d03d13cebe998bee510c570f9e73a0c593a5ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20G=C3=BCnzler?= Date: Sat, 7 Jan 2023 01:42:57 +0100 Subject: [PATCH 04/29] mpris: fix build errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit to address https://github.com/Alexays/Waybar/pull/1520#issuecomment-1374229080 Signed-off-by: Robert Günzler --- include/modules/mpris/mpris.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/modules/mpris/mpris.hpp b/include/modules/mpris/mpris.hpp index 4f8ddb1..040401f 100644 --- a/include/modules/mpris/mpris.hpp +++ b/include/modules/mpris/mpris.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include "gtkmm/box.h" From 2045aac5b05fab8cf620fbc72c3e79b5d1ecb2ff Mon Sep 17 00:00:00 2001 From: Julian Schuler <31921487+julianschuler@users.noreply.github.com> Date: Sun, 8 Jan 2023 18:49:24 +0100 Subject: [PATCH 05/29] Fix crash upon reconnecting monitor --- src/modules/wlr/workspace_manager.cpp | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/modules/wlr/workspace_manager.cpp b/src/modules/wlr/workspace_manager.cpp index 6f11e1f..aa80b54 100644 --- a/src/modules/wlr/workspace_manager.cpp +++ b/src/modules/wlr/workspace_manager.cpp @@ -9,6 +9,7 @@ #include #include +#include "client.hpp" #include "gtkmm/widget.h" #include "modules/wlr/workspace_manager_binding.hpp" @@ -166,8 +167,20 @@ WorkspaceManager::~WorkspaceManager() { return; } - zext_workspace_manager_v1_destroy(workspace_manager_); - workspace_manager_ = nullptr; + wl_display *display = Client::inst()->wl_display; + + // Send `stop` request and wait for one roundtrip. This is not quite correct as + // the protocol encourages us to wait for the .finished event, but it should work + // with wlroots workspace manager implementation. + zext_workspace_manager_v1_stop(workspace_manager_); + wl_display_roundtrip(display); + + // If the .finished handler is still not executed, destroy the workspace manager here. + if (workspace_manager_) { + spdlog::warn("Foreign toplevel manager destroyed before .finished event"); + zext_workspace_manager_v1_destroy(workspace_manager_); + workspace_manager_ = nullptr; + } } auto WorkspaceManager::remove_workspace_group(uint32_t id) -> void { From d6bd44002772148a8436d2c6b188b6b1860fd5cf Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 9 Jan 2023 15:48:31 +0100 Subject: [PATCH 06/29] fix: lint --- src/modules/wlr/workspace_manager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/wlr/workspace_manager.cpp b/src/modules/wlr/workspace_manager.cpp index aa80b54..ade0269 100644 --- a/src/modules/wlr/workspace_manager.cpp +++ b/src/modules/wlr/workspace_manager.cpp @@ -169,7 +169,7 @@ WorkspaceManager::~WorkspaceManager() { 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 // with wlroots workspace manager implementation. zext_workspace_manager_v1_stop(workspace_manager_); From f0bead34d4f3a36cb9c94d60152d22dc6a773d60 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 11 Jan 2023 11:39:30 +0100 Subject: [PATCH 07/29] chore: 0.9.17 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 5b3c475..ebf68d4 100644 --- a/meson.build +++ b/meson.build @@ -1,6 +1,6 @@ project( 'waybar', 'cpp', 'c', - version: '0.9.16', + version: '0.9.17', license: 'MIT', meson_version: '>= 0.49.0', default_options : [ From df0fdce92b34406262ee522ad3910cefcc6ffd9e Mon Sep 17 00:00:00 2001 From: "Victor \"multun\" Collod" Date: Wed, 11 Jan 2023 23:02:09 +0100 Subject: [PATCH 08/29] get_desktop_app_info: fix crash on failed DesktopAppInfo::create Even though it makes little sense for this call to fail, it sometimes randomly does, and takes down waybar with it. --- src/modules/wlr/taskbar.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/modules/wlr/taskbar.cpp b/src/modules/wlr/taskbar.cpp index 97d84bd..5460244 100644 --- a/src/modules/wlr/taskbar.cpp +++ b/src/modules/wlr/taskbar.cpp @@ -102,8 +102,11 @@ Glib::RefPtr get_desktop_app_info(const std::string &app_id desktop_file = desktop_list[0][i]; } else { auto tmp_info = Gio::DesktopAppInfo::create(desktop_list[0][i]); - auto startup_class = tmp_info->get_startup_wm_class(); + if (!tmp_info) + // see https://github.com/Alexays/Waybar/issues/1446 + continue; + auto startup_class = tmp_info->get_startup_wm_class(); if (startup_class == app_id) { desktop_file = desktop_list[0][i]; break; From 328573332f7f48c0c2befd3a5d35ba4371c69d27 Mon Sep 17 00:00:00 2001 From: "Rene D. Obermueller" Date: Sun, 17 Apr 2022 07:13:35 +0200 Subject: [PATCH 09/29] sway-window, Issue 1399: new style classes Provides CSS classes empty, floating, tabbed, tiled, solo, stacked and app_id. Adds offscreen-css bool option (default false), only effective when "all-outputs" is true. This adds styles on outputs without focused node, according to its focused workspaces window situation. Adds an "offscreen-css-text" string option (default empty), only effective when "all-outputs" and "offscreen-style" are set. This is shown as a text on outputs without a focused node. Adds a "show-focused-workspace" bool option (default false) to indicate the workspace name if the whole workspace is focused when nodes are also present. If not set, empty text is shown, but css classes according to nodes in the workspace are still applied. Limitation: When the top level layout changes, there is no sway event so the module cannot react. Perhaps in the future recurring polling can be added to go around this limitation. --- include/modules/sway/window.hpp | 7 +- man/waybar-sway-window.5.scd | 27 +++- src/modules/sway/window.cpp | 238 +++++++++++++++++++++++--------- 3 files changed, 199 insertions(+), 73 deletions(-) diff --git a/include/modules/sway/window.hpp b/include/modules/sway/window.hpp index c13d5ce..e99e94f 100644 --- a/include/modules/sway/window.hpp +++ b/include/modules/sway/window.hpp @@ -19,10 +19,11 @@ class Window : public AIconLabel, public sigc::trackable { auto update() -> void; private: + void setClass(std::string classname, bool enable); void onEvent(const struct Ipc::ipc_response&); void onCmd(const struct Ipc::ipc_response&); - std::tuple getFocusedNode( - const Json::Value& nodes, std::string& output); + std::tuple + getFocusedNode(const Json::Value& nodes, std::string& output); void getTree(); void updateAppIconName(); void updateAppIcon(); @@ -32,12 +33,14 @@ class Window : public AIconLabel, public sigc::trackable { int windowId_; std::string app_id_; std::string app_class_; + std::string layout_; std::string old_app_id_; std::size_t app_nb_; std::string shell_; unsigned app_icon_size_{24}; bool update_app_icon_{true}; std::string app_icon_name_; + int floating_count_; util::JsonParser parser_; std::mutex mutex_; Ipc ipc_; diff --git a/man/waybar-sway-window.5.scd b/man/waybar-sway-window.5.scd index 6e5ebdb..2ad1a2b 100644 --- a/man/waybar-sway-window.5.scd +++ b/man/waybar-sway-window.5.scd @@ -66,6 +66,25 @@ Addressed by *sway/window* default: true ++ Option to disable tooltip on hover. +*all-outputs*: ++ + typeof: bool ++ + default: false ++ + Option to show the focused window along with its workspace styles on all outputs. + +*offscreen-css*: ++ + typeof: bool ++ + default: false ++ + Only effective when all-outputs is true. Adds style according to present windows on unfocused outputs instead of showing the focused window and style. + +*offscreen-css-text*: ++ + typeof: string ++ + Only effective when both all-outputs and offscreen-style are true. On screens currently not focused, show the given text along with that workspaces styles. + +*show-focused-workspace-name*: ++ + typeof: bool ++ + default: false ++ + If the workspace itself is focused and the workspace contains nodes or floating_nodes, show the workspace name. If not set, text remains empty but styles according to nodes in the workspace are still applied. + *rewrite*: ++ typeof: object ++ Rules to rewrite window title. See *rewrite rules*. @@ -117,6 +136,10 @@ Invalid expressions (e.g., mismatched parentheses) are skipped. # STYLE - *#window* -- *window#waybar.empty* When no windows is in the workspace -- *window#waybar.solo* When one window is in the workspace +- *window#waybar.empty* When no windows are in the workspace, or screen is not focused and offscreen-text option is not set +- *window#waybar.solo* When one tiled window is in the workspace +- *window#waybar.floating* When there are only floating windows in the workspace +- *window#waybar.stacked* When there is more than one window in the workspace and the workspace layout is stacked +- *window#waybar.tabbed* When there is more than one window in the workspace and the workspace layout is tabbed +- *window#waybar.tiled* When there is more than one window in the workspace and the workspace layout is splith or splitv - *window#waybar.* Where *app_id* is the app_id or *instance* name like (*chromium*) of the only window in the workspace diff --git a/src/modules/sway/window.cpp b/src/modules/sway/window.cpp index 5da7d3d..0e74b76 100644 --- a/src/modules/sway/window.cpp +++ b/src/modules/sway/window.cpp @@ -17,7 +17,7 @@ namespace waybar::modules::sway { Window::Window(const std::string& id, const Bar& bar, const Json::Value& config) - : AIconLabel(config, "window", id, "{title}", 0, true), bar_(bar), windowId_(-1) { + : AIconLabel(config, "window", id, "{}", 0, true), bar_(bar), windowId_(-1) { // Icon size if (config_["icon-size"].isUInt()) { app_icon_size_ = config["icon-size"].asUInt(); @@ -35,6 +35,7 @@ Window::Window(const std::string& id, const Bar& bar, const Json::Value& config) ipc_.handleEvent(); } catch (const std::exception& e) { spdlog::error("Window: {}", e.what()); + spdlog::trace("Window::Window exception"); } }); } @@ -46,12 +47,13 @@ void Window::onCmd(const struct Ipc::ipc_response& res) { std::lock_guard lock(mutex_); auto payload = parser_.parse(res.payload); auto output = payload["output"].isString() ? payload["output"].asString() : ""; - std::tie(app_nb_, windowId_, window_, app_id_, app_class_, shell_) = + std::tie(app_nb_, floating_count_, windowId_, window_, app_id_, app_class_, shell_, layout_) = getFocusedNode(payload["nodes"], output); updateAppIconName(); dp.emit(); } catch (const std::exception& e) { spdlog::error("Window: {}", e.what()); + spdlog::trace("Window::onCmd exception"); } } @@ -156,27 +158,52 @@ void Window::updateAppIcon() { } auto Window::update() -> void { - if (!old_app_id_.empty()) { - bar_.window.get_style_context()->remove_class(old_app_id_); - } + spdlog::trace("workspace layout {}, tiled count {}, floating count {}", layout_, app_nb_, + floating_count_); + + int mode = 0; if (app_nb_ == 0) { - bar_.window.get_style_context()->remove_class("solo"); - if (!bar_.window.get_style_context()->has_class("empty")) { - bar_.window.get_style_context()->add_class("empty"); + if (floating_count_ == 0) { + mode += 1; + } else { + mode += 4; } } else if (app_nb_ == 1) { - bar_.window.get_style_context()->remove_class("empty"); - if (!bar_.window.get_style_context()->has_class("solo")) { - bar_.window.get_style_context()->add_class("solo"); + mode += 2; + } else { + if (layout_ == "tabbed") { + mode += 8; + } else if (layout_ == "stacked") { + mode += 16; + } else { + mode += 32; } if (!app_id_.empty() && !bar_.window.get_style_context()->has_class(app_id_)) { bar_.window.get_style_context()->add_class(app_id_); old_app_id_ = app_id_; } - } else { - bar_.window.get_style_context()->remove_class("solo"); - bar_.window.get_style_context()->remove_class("empty"); } + + if (!old_app_id_.empty() && ((mode & 2) == 0 || old_app_id_ != app_id_) && + bar_.window.get_style_context()->has_class(old_app_id_)) { + spdlog::trace("Removing app_id class: {}", old_app_id_); + bar_.window.get_style_context()->remove_class(old_app_id_); + old_app_id_ = ""; + } + + setClass("empty", ((mode & 1) > 0)); + setClass("solo", ((mode & 2) > 0)); + setClass("floating", ((mode & 4) > 0)); + setClass("tabbed", ((mode & 8) > 0)); + setClass("stacked", ((mode & 16) > 0)); + setClass("tiled", ((mode & 32) > 0)); + + if ((mode & 2) > 0 && !app_id_.empty() && !bar_.window.get_style_context()->has_class(app_id_)) { + spdlog::trace("Adding app_id class: {}", app_id_); + bar_.window.get_style_context()->add_class(app_id_); + old_app_id_ = app_id_; + } + label_.set_markup(fmt::format( format_, fmt::arg("title", waybar::util::rewriteTitle(window_, config_["rewrite"])), fmt::arg("app_id", app_id_), fmt::arg("shell", shell_))); @@ -190,71 +217,143 @@ auto Window::update() -> void { AIconLabel::update(); } -int leafNodesInWorkspace(const Json::Value& node) { +void Window::setClass(std::string classname, bool enable) { + if (enable) { + if (!bar_.window.get_style_context()->has_class(classname)) { + bar_.window.get_style_context()->add_class(classname); + } + } else { + bar_.window.get_style_context()->remove_class(classname); + } +} + +std::pair leafNodesInWorkspace(const Json::Value& node) { auto const& nodes = node["nodes"]; auto const& floating_nodes = node["floating_nodes"]; if (nodes.empty() && floating_nodes.empty()) { - if (node["type"] == "workspace") - return 0; - else - return 1; + if (node["type"].asString() == "workspace") + return {0, 0}; + else if (node["type"].asString() == "floating_con") { + return {0, 1}; + } else { + return {1, 0}; + } } int sum = 0; - if (!nodes.empty()) { - for (auto const& node : nodes) sum += leafNodesInWorkspace(node); - } - if (!floating_nodes.empty()) { - for (auto const& node : floating_nodes) sum += leafNodesInWorkspace(node); - } - return sum; -} - -std::tuple gfnWithWorkspace( - const Json::Value& nodes, std::string& output, const Json::Value& config_, const Bar& bar_, - Json::Value& parentWorkspace) { + int floating_sum = 0; for (auto const& node : nodes) { - if (node["output"].isString()) { - output = node["output"].asString(); - } - // 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}; - } + std::pair all_leaf_nodes = leafNodesInWorkspace(node); + sum += all_leaf_nodes.first; + floating_sum += all_leaf_nodes.second; } - 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::tuple +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 Window::getFocusedNode(const Json::Value& nodes, std::string& output) { - Json::Value placeholder = 0; - return gfnWithWorkspace(nodes, output, config_, bar_, placeholder); + Json::Value placeholder = Json::Value::null; + return gfnWithWorkspace(nodes, output, config_, bar_, placeholder, placeholder); } void Window::getTree() { @@ -262,6 +361,7 @@ void Window::getTree() { ipc_.sendCmd(IPC_GET_TREE); } catch (const std::exception& e) { spdlog::error("Window: {}", e.what()); + spdlog::trace("Window::getTree exception"); } } From 120cba0f5e897d85cf634400442db5e083408fd2 Mon Sep 17 00:00:00 2001 From: Cyril LEVIS Date: Fri, 13 Jan 2023 09:29:49 +0100 Subject: [PATCH 10/29] fix: battery time remaining time is reported in second and should be divided by 3600 and not 1000. --- src/modules/battery.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/battery.cpp b/src/modules/battery.cpp index 97ff0a5..b3e51a6 100644 --- a/src/modules/battery.cpp +++ b/src/modules/battery.cpp @@ -505,12 +505,12 @@ const std::tuple waybar::modules::Battery::g float time_remaining{0.0f}; if (status == "Discharging" && time_to_empty_now_exists) { - if (time_to_empty_now != 0) time_remaining = (float)time_to_empty_now / 1000.0f; + if (time_to_empty_now != 0) time_remaining = (float)time_to_empty_now / 3600.0f; } else if (status == "Discharging" && total_power_exists && total_energy_exists) { if (total_power != 0) time_remaining = (float)total_energy / total_power; } else if (status == "Charging" && time_to_full_now_exists) { if (time_to_full_now_exists && (time_to_full_now != 0)) - time_remaining = -(float)time_to_full_now / 1000.0f; + time_remaining = -(float)time_to_full_now / 3600.0f; // If we've turned positive it means the battery is past 100% and so just report that as no // time remaining if (time_remaining > 0.0f) time_remaining = 0.0f; From 544c6deb885b9bcf27442b6869085740c32232a1 Mon Sep 17 00:00:00 2001 From: "Rene D. Obermueller" Date: Fri, 13 Jan 2023 15:08:59 +0100 Subject: [PATCH 11/29] sway/window: fix manpage --- man/waybar-sway-window.5.scd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/man/waybar-sway-window.5.scd b/man/waybar-sway-window.5.scd index 2ad1a2b..19e0cd2 100644 --- a/man/waybar-sway-window.5.scd +++ b/man/waybar-sway-window.5.scd @@ -136,7 +136,7 @@ Invalid expressions (e.g., mismatched parentheses) are skipped. # STYLE - *#window* -- *window#waybar.empty* When no windows are in the workspace, or screen is not focused and offscreen-text option is not set +- *window#waybar.empty* When no windows are in the workspace, or screen is not focused and offscreen-css option is not set - *window#waybar.solo* When one tiled window is in the workspace - *window#waybar.floating* When there are only floating windows in the workspace - *window#waybar.stacked* When there is more than one window in the workspace and the workspace layout is stacked From a4b1b0a211ac3f601cdaafb1a560319246cd14b0 Mon Sep 17 00:00:00 2001 From: PolpOnline Date: Fri, 13 Jan 2023 22:39:59 +0100 Subject: [PATCH 12/29] modules/custom: Added percentage rounding --- src/modules/custom.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/custom.cpp b/src/modules/custom.cpp index 23dba38..39d93d4 100644 --- a/src/modules/custom.cpp +++ b/src/modules/custom.cpp @@ -209,8 +209,8 @@ void waybar::modules::Custom::parseOutputJson() { class_.push_back(c.asString()); } } - if (!parsed["percentage"].asString().empty() && parsed["percentage"].isUInt()) { - percentage_ = parsed["percentage"].asUInt(); + if (!parsed["percentage"].asString().empty() && parsed["percentage"].isNumeric()) { + percentage_ = (int) lround(parsed["percentage"].asFloat()); } else { percentage_ = 0; } From 4e8ccf36b54cacf5281726d23ea14312a133f977 Mon Sep 17 00:00:00 2001 From: Sasha Moak Date: Thu, 12 Jan 2023 16:17:11 -0800 Subject: [PATCH 13/29] fix(wireplumber): waybar crashes when default node changes In order to fix the issue, the default node name is cached rather than the default node id. This is due to ids being unstable. So now when the object manager is installed (ie ready), the default node name is retrieved and stored for later. Now when the mixer changed signal is emitted, the id of the changed node is used to get the node from the object manager. The nodes name is grabbed off that node and compared against the default node name, if they match the volume is updated. Some safeguarding has been added such that if the node cannot be found off the object manager, it's ignored. Additionally, the "changed" signal on the default nodes api is now utilized to update the default node name if it has changed. This way if the default node changes, the module will be updated with the correct volume and node.nick. This adds additional debug logging for helping diagnose wireplumber issues. This also adds the wireplumber man page entry to the main waybar supported section. --- include/modules/wireplumber.hpp | 10 +- man/waybar.5.scd.in | 1 + src/modules/wireplumber.cpp | 184 ++++++++++++++++++++++++++------ 3 files changed, 157 insertions(+), 38 deletions(-) diff --git a/include/modules/wireplumber.hpp b/include/modules/wireplumber.hpp index c0ee7f0..fa988fc 100644 --- a/include/modules/wireplumber.hpp +++ b/include/modules/wireplumber.hpp @@ -20,15 +20,19 @@ class Wireplumber : public ALabel { void loadRequiredApiModules(); void prepare(); void activatePlugins(); - static void updateVolume(waybar::modules::Wireplumber* self); - static void updateNodeName(waybar::modules::Wireplumber* self); - static uint32_t getDefaultNodeId(waybar::modules::Wireplumber* self); + static void updateVolume(waybar::modules::Wireplumber* self, uint32_t id); + static void updateNodeName(waybar::modules::Wireplumber* self, uint32_t id); static void onPluginActivated(WpObject* p, GAsyncResult* res, waybar::modules::Wireplumber* self); static void onObjectManagerInstalled(waybar::modules::Wireplumber* self); + static void onMixerChanged(waybar::modules::Wireplumber* self, uint32_t id); + static void onDefaultNodesApiChanged(waybar::modules::Wireplumber* self); WpCore* wp_core_; GPtrArray* apis_; WpObjectManager* om_; + WpPlugin* mixer_api_; + WpPlugin* def_nodes_api_; + gchar* default_node_name_; uint32_t pending_plugins_; bool muted_; double volume_; diff --git a/man/waybar.5.scd.in b/man/waybar.5.scd.in index b1ed4c5..7566dd0 100644 --- a/man/waybar.5.scd.in +++ b/man/waybar.5.scd.in @@ -277,6 +277,7 @@ A module group is defined by specifying a module named "group/some-group-name". - *waybar-sway-scratchpad(5)* - *waybar-sway-window(5)* - *waybar-sway-workspaces(5)* +- *waybar-wireplumber(5)* - *waybar-wlr-taskbar(5)* - *waybar-wlr-workspaces(5)* - *waybar-temperature(5)* diff --git a/src/modules/wireplumber.cpp b/src/modules/wireplumber.cpp index 9a12a9b..9652e1e 100644 --- a/src/modules/wireplumber.cpp +++ b/src/modules/wireplumber.cpp @@ -1,15 +1,22 @@ #include "modules/wireplumber.hpp" +#include + +bool isValidNodeId(uint32_t id) { return id > 0 && id < G_MAXUINT32; } + waybar::modules::Wireplumber::Wireplumber(const std::string& id, const Json::Value& config) : ALabel(config, "wireplumber", id, "{volume}%"), wp_core_(nullptr), apis_(nullptr), om_(nullptr), + mixer_api_(nullptr), + def_nodes_api_(nullptr), + default_node_name_(nullptr), pending_plugins_(0), muted_(false), volume_(0.0), node_id_(0) { - wp_init(WP_INIT_ALL); + wp_init(WP_INIT_PIPEWIRE); wp_core_ = wp_core_new(NULL, NULL); apis_ = g_ptr_array_new_with_free_func(g_object_unref); om_ = wp_object_manager_new(); @@ -18,10 +25,15 @@ waybar::modules::Wireplumber::Wireplumber(const std::string& id, const Json::Val loadRequiredApiModules(); + spdlog::debug("[{}]: connecting to pipewire...", this->name_); + if (!wp_core_connect(wp_core_)) { + spdlog::error("[{}]: Could not connect to PipeWire", this->name_); throw std::runtime_error("Could not connect to PipeWire\n"); } + spdlog::debug("[{}]: connected!", this->name_); + g_signal_connect_swapped(om_, "installed", (GCallback)onObjectManagerInstalled, this); activatePlugins(); @@ -33,33 +45,26 @@ waybar::modules::Wireplumber::~Wireplumber() { g_clear_pointer(&apis_, g_ptr_array_unref); g_clear_object(&om_); g_clear_object(&wp_core_); + g_clear_object(&mixer_api_); + g_clear_object(&def_nodes_api_); + g_free(&default_node_name_); } -uint32_t waybar::modules::Wireplumber::getDefaultNodeId(waybar::modules::Wireplumber* self) { - uint32_t id; - g_autoptr(WpPlugin) def_nodes_api = wp_plugin_find(self->wp_core_, "default-nodes-api"); +void waybar::modules::Wireplumber::updateNodeName(waybar::modules::Wireplumber* self, uint32_t id) { + spdlog::debug("[{}]: updating node name with node.id {}", self->name_, id); - if (!def_nodes_api) { - throw std::runtime_error("Default nodes API is not loaded\n"); + if (!isValidNodeId(id)) { + spdlog::warn("[{}]: '{}' is not a valid node ID. Ignoring node name update.", self->name_, id); + return; } - g_signal_emit_by_name(def_nodes_api, "get-default-node", "Audio/Sink", &id); - - if (id <= 0 || id >= G_MAXUINT32) { - auto err = fmt::format("'{}' is not a valid ID (returned by default-nodes-api)\n", id); - throw std::runtime_error(err); - } - - return id; -} - -void waybar::modules::Wireplumber::updateNodeName(waybar::modules::Wireplumber* self) { - auto proxy = static_cast( - wp_object_manager_lookup(self->om_, WP_TYPE_GLOBAL_PROXY, WP_CONSTRAINT_TYPE_G_PROPERTY, - "bound-id", "=u", self->node_id_, NULL)); + auto proxy = static_cast(wp_object_manager_lookup( + self->om_, WP_TYPE_GLOBAL_PROXY, WP_CONSTRAINT_TYPE_G_PROPERTY, "bound-id", "=u", id, NULL)); if (!proxy) { - throw std::runtime_error(fmt::format("Object '{}' not found\n", self->node_id_)); + auto err = fmt::format("Object '{}' not found\n", id); + spdlog::error("[{}]: {}", self->name_, err); + throw std::runtime_error(err); } g_autoptr(WpProperties) properties = @@ -73,15 +78,24 @@ void waybar::modules::Wireplumber::updateNodeName(waybar::modules::Wireplumber* auto description = wp_properties_get(properties, "node.description"); self->node_name_ = nick ? nick : description; + spdlog::debug("[{}]: Updating node name to: {}", self->name_, self->node_name_); } -void waybar::modules::Wireplumber::updateVolume(waybar::modules::Wireplumber* self) { +void waybar::modules::Wireplumber::updateVolume(waybar::modules::Wireplumber* self, uint32_t id) { + spdlog::debug("[{}]: updating volume", self->name_); double vol; GVariant* variant = NULL; - g_autoptr(WpPlugin) mixer_api = wp_plugin_find(self->wp_core_, "mixer-api"); - g_signal_emit_by_name(mixer_api, "get-volume", self->node_id_, &variant); + + if (!isValidNodeId(id)) { + spdlog::error("[{}]: '{}' is not a valid node ID. Ignoring volume update.", self->name_, id); + return; + } + + g_signal_emit_by_name(self->mixer_api_, "get-volume", id, &variant); + if (!variant) { - auto err = fmt::format("Node {} does not support volume\n", self->node_id_); + auto err = fmt::format("Node {} does not support volume\n", id); + spdlog::error("[{}]: {}", self->name_, err); throw std::runtime_error(err); } @@ -93,22 +107,121 @@ void waybar::modules::Wireplumber::updateVolume(waybar::modules::Wireplumber* se self->dp.emit(); } +void waybar::modules::Wireplumber::onMixerChanged(waybar::modules::Wireplumber* self, uint32_t id) { + spdlog::debug("[{}]: (onMixerChanged) - id: {}", self->name_, id); + + g_autoptr(WpNode) node = static_cast(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( + wp_object_manager_lookup(self->om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_G_PROPERTY, "bound-id", + "=u", default_node_id, NULL)); + + if (!node) { + spdlog::warn("[{}]: (onDefaultNodesApiChanged) - Object with id {} not found", self->name_, + default_node_id); + return; + } + + const gchar* default_node_name = + wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(node), "node.name"); + + spdlog::debug( + "[{}]: (onDefaultNodesApiChanged) - got the following default node: Node(name: {}, id: {})", + self->name_, default_node_name, default_node_id); + + if (g_strcmp0(self->default_node_name_, default_node_name) == 0) { + spdlog::debug( + "[{}]: (onDefaultNodesApiChanged) - Default node has not changed. Node(name: {}, id: {}). " + "Ignoring.", + self->name_, self->default_node_name_, default_node_id); + return; + } + + spdlog::debug( + "[{}]: (onDefaultNodesApiChanged) - Default node changed to -> Node(name: {}, id: {})", + self->name_, default_node_name, default_node_id); + + self->default_node_name_ = g_strdup(default_node_name); + updateVolume(self, default_node_id); + updateNodeName(self, default_node_id); +} + void waybar::modules::Wireplumber::onObjectManagerInstalled(waybar::modules::Wireplumber* self) { - self->node_id_ = - self->config_["node-id"].isInt() ? self->config_["node-id"].asInt() : getDefaultNodeId(self); + spdlog::debug("[{}]: onObjectManagerInstalled", self->name_); - g_autoptr(WpPlugin) mixer_api = wp_plugin_find(self->wp_core_, "mixer-api"); + self->def_nodes_api_ = wp_plugin_find(self->wp_core_, "default-nodes-api"); - updateVolume(self); - updateNodeName(self); - g_signal_connect_swapped(mixer_api, "changed", (GCallback)updateVolume, self); + if (!self->def_nodes_api_) { + spdlog::error("[{}]: default nodes api is not loaded.", self->name_); + throw std::runtime_error("Default nodes API is not loaded\n"); + } + + self->mixer_api_ = wp_plugin_find(self->wp_core_, "mixer-api"); + + if (!self->mixer_api_) { + spdlog::error("[{}]: mixer api is not loaded.", self->name_); + throw std::runtime_error("Mixer api is not loaded\n"); + } + + uint32_t default_node_id; + g_signal_emit_by_name(self->def_nodes_api_, "get-default-configured-node-name", "Audio/Sink", + &self->default_node_name_); + g_signal_emit_by_name(self->def_nodes_api_, "get-default-node", "Audio/Sink", &default_node_id); + + if (self->default_node_name_) { + spdlog::debug("[{}]: (onObjectManagerInstalled) - default configured node name: {} and id: {}", + self->name_, self->default_node_name_, default_node_id); + } + + updateVolume(self, default_node_id); + updateNodeName(self, default_node_id); + + g_signal_connect_swapped(self->mixer_api_, "changed", (GCallback)onMixerChanged, self); + g_signal_connect_swapped(self->def_nodes_api_, "changed", (GCallback)onDefaultNodesApiChanged, + self); } void waybar::modules::Wireplumber::onPluginActivated(WpObject* p, GAsyncResult* res, waybar::modules::Wireplumber* self) { + auto plugin_name = wp_plugin_get_name(WP_PLUGIN(p)); + spdlog::debug("[{}]: onPluginActivated: {}", self->name_, plugin_name); g_autoptr(GError) error = NULL; if (!wp_object_activate_finish(p, res, &error)) { + spdlog::error("[{}]: error activating plugin: {}", self->name_, error->message); throw std::runtime_error(error->message); } @@ -118,6 +231,7 @@ void waybar::modules::Wireplumber::onPluginActivated(WpObject* p, GAsyncResult* } void waybar::modules::Wireplumber::activatePlugins() { + spdlog::debug("[{}]: activating plugins", name_); for (uint16_t i = 0; i < apis_->len; i++) { WpPlugin* plugin = static_cast(g_ptr_array_index(apis_, i)); pending_plugins_++; @@ -127,13 +241,13 @@ void waybar::modules::Wireplumber::activatePlugins() { } void waybar::modules::Wireplumber::prepare() { - wp_object_manager_add_interest(om_, WP_TYPE_NODE, NULL); - wp_object_manager_add_interest(om_, WP_TYPE_GLOBAL_PROXY, NULL); - wp_object_manager_request_object_features(om_, WP_TYPE_GLOBAL_PROXY, - WP_PIPEWIRE_OBJECT_FEATURES_MINIMAL); + spdlog::debug("[{}]: preparing object manager", name_); + wp_object_manager_add_interest(om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_PW_PROPERTY, "media.class", + "=s", "Audio/Sink", NULL); } void waybar::modules::Wireplumber::loadRequiredApiModules() { + spdlog::debug("[{}]: loading required modules", name_); g_autoptr(GError) error = NULL; if (!wp_core_load_component(wp_core_, "libwireplumber-module-default-nodes-api", "module", NULL, From 6e9f21fc6bc4b57e53b3a5bbcd517097e1acd01f Mon Sep 17 00:00:00 2001 From: Maxim Baz Date: Fri, 20 Jan 2023 23:40:08 +0100 Subject: [PATCH 14/29] hyprland/submap: run initial render on startup --- src/modules/hyprland/submap.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/modules/hyprland/submap.cpp b/src/modules/hyprland/submap.cpp index 6eb0942..d61c8d4 100644 --- a/src/modules/hyprland/submap.cpp +++ b/src/modules/hyprland/submap.cpp @@ -19,6 +19,7 @@ Submap::Submap(const std::string& id, const Bar& bar, const Json::Value& config) // register for hyprland ipc gIPC->registerForIPC("submap", this); + dp.emit(); } Submap::~Submap() { From ca9d237b00b4d01f341b0d7bc938afb10a4f8cad Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Mon, 16 Jan 2023 10:27:21 -0800 Subject: [PATCH 15/29] fix(sway): add missing includes for GCC 13 See also: https://gcc.gnu.org/gcc-13/porting_to.html --- include/modules/sway/ipc/client.hpp | 1 + src/modules/sway/ipc/client.cpp | 2 ++ 2 files changed, 3 insertions(+) diff --git a/include/modules/sway/ipc/client.hpp b/include/modules/sway/ipc/client.hpp index 77dab08..a6705ea 100644 --- a/include/modules/sway/ipc/client.hpp +++ b/include/modules/sway/ipc/client.hpp @@ -8,6 +8,7 @@ #include #include #include +#include #include "ipc.hpp" #include "util/sleeper_thread.hpp" diff --git a/src/modules/sway/ipc/client.cpp b/src/modules/sway/ipc/client.cpp index 4d6495c..5c3df7b 100644 --- a/src/modules/sway/ipc/client.cpp +++ b/src/modules/sway/ipc/client.cpp @@ -2,6 +2,8 @@ #include +#include + namespace waybar::modules::sway { Ipc::Ipc() { From 43d52c59d99e7de50a28362780cc9068eb2fecc8 Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Mon, 16 Jan 2023 11:04:56 -0800 Subject: [PATCH 16/29] test: fix build with Catch2 v2.x Use smaller includes for Catch2 v3. --- meson.build | 2 +- test/SafeSignal.cpp | 6 +++++- test/config.cpp | 6 +++++- test/main.cpp | 15 +++++++++++++-- test/waybar_time.cpp | 7 ++++++- 5 files changed, 30 insertions(+), 6 deletions(-) diff --git a/meson.build b/meson.build index ebf68d4..7b63181 100644 --- a/meson.build +++ b/meson.build @@ -447,7 +447,7 @@ endif catch2 = dependency( 'catch2', - version: '>=3.0.0', + version: '>=2.0.0', fallback: ['catch2', 'catch2_dep'], required: get_option('tests'), ) diff --git a/test/SafeSignal.cpp b/test/SafeSignal.cpp index 7ff6f2a..f496d7a 100644 --- a/test/SafeSignal.cpp +++ b/test/SafeSignal.cpp @@ -2,7 +2,11 @@ #include -#include +#if __has_include() +#include +#else +#include +#endif #include #include diff --git a/test/config.cpp b/test/config.cpp index cdc96b0..3d0f007 100644 --- a/test/config.cpp +++ b/test/config.cpp @@ -1,6 +1,10 @@ #include "config.hpp" -#include +#if __has_include() +#include +#else +#include +#endif TEST_CASE("Load simple config", "[config]") { waybar::Config conf; diff --git a/test/main.cpp b/test/main.cpp index 7970c26..daeee69 100644 --- a/test/main.cpp +++ b/test/main.cpp @@ -3,8 +3,13 @@ #include #include +#if __has_include() #include #include +#else +#include +#include +#endif #include int main(int argc, char* argv[]) { @@ -13,10 +18,16 @@ int main(int argc, char* argv[]) { session.applyCommandLine(argc, argv); const auto logger = spdlog::default_logger(); +#if CATCH_VERSION_MAJOR >= 3 for (const auto& spec : session.config().getReporterSpecs()) { - if (spec.name() == "tap") { + const auto& reporter_name = spec.name(); +#else + { + const auto& reporter_name = session.config().getReporterName(); +#endif + if (reporter_name == "tap") { spdlog::set_pattern("# [%l] %v"); - } else if (spec.name() == "compact") { + } else if (reporter_name == "compact") { logger->sinks().clear(); } else { logger->sinks().assign({std::make_shared()}); diff --git a/test/waybar_time.cpp b/test/waybar_time.cpp index 79469d4..9f9f5dc 100644 --- a/test/waybar_time.cpp +++ b/test/waybar_time.cpp @@ -3,7 +3,12 @@ #include #include -#include +#if __has_include() +#include +#include +#else +#include +#endif #include #include From ba498869c5a2a35c1b389c7067926be1c4617828 Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Wed, 18 Jan 2023 06:17:55 -0800 Subject: [PATCH 17/29] fix(clock): delete outdated warning --- src/modules/clock.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/modules/clock.cpp b/src/modules/clock.cpp index 55f2c5b..9871024 100644 --- a/src/modules/clock.cpp +++ b/src/modules/clock.cpp @@ -41,12 +41,6 @@ waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config) time_zones_.push_back(nullptr); } - if (!is_timezone_fixed()) { - spdlog::warn( - "As using a timezone, some format args may be missing as the date library haven't got a " - "release since 2018."); - } - // Check if a particular placeholder is present in the tooltip format, to know what to calculate // on update. if (config_["tooltip-format"].isString()) { From 67efe1af892cff2cc37f9aeb0fd7420b7d4eb621 Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Mon, 16 Jan 2023 16:48:30 -0800 Subject: [PATCH 18/29] refactor(clock): remove struct waybar_time The structure was used to pass the locale instance to the date formatter. All the supported versions of `fmt` are passing the locale parameter via `FormatContext.locale()` so we can remove the struct and simplify the code. While we at it, drop `date::make_zoned` in favor of CTAD on a `date::zoned_time` constructor. --- include/modules/clock.hpp | 11 +-- include/util/{waybar_time.hpp => date.hpp} | 21 ++--- src/modules/clock.cpp | 27 +++--- test/date.cpp | 94 +++++++++++++++++++++ test/meson.build | 2 +- test/waybar_time.cpp | 95 ---------------------- 6 files changed, 117 insertions(+), 133 deletions(-) rename include/util/{waybar_time.hpp => date.hpp} (51%) create mode 100644 test/date.cpp delete mode 100644 test/waybar_time.cpp diff --git a/include/modules/clock.hpp b/include/modules/clock.hpp index ef129fb..c97565d 100644 --- a/include/modules/clock.hpp +++ b/include/modules/clock.hpp @@ -5,11 +5,7 @@ #include "ALabel.hpp" #include "util/sleeper_thread.hpp" -namespace waybar { - -struct waybar_time; - -namespace modules { +namespace waybar::modules { const std::string kCalendarPlaceholder = "calendar"; const std::string KTimezonedTimeListPlaceholder = "timezoned_time_list"; @@ -36,12 +32,11 @@ class Clock : public ALabel { 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 calendar_text(const date::zoned_seconds& ztime) -> std::string; auto weekdays_header(const date::weekday& first_dow, std::ostream& os) -> void; auto first_day_of_week() -> date::weekday; const date::time_zone* current_timezone(); bool is_timezone_fixed(); auto timezones_text(std::chrono::system_clock::time_point* now) -> std::string; }; -} // namespace modules -} // namespace waybar +} // namespace waybar::modules diff --git a/include/util/waybar_time.hpp b/include/util/date.hpp similarity index 51% rename from include/util/waybar_time.hpp rename to include/util/date.hpp index b9f9ea9..ec948bd 100644 --- a/include/util/waybar_time.hpp +++ b/include/util/date.hpp @@ -3,17 +3,8 @@ #include #include -namespace waybar { - -struct waybar_time { - std::locale locale; - date::zoned_seconds ztime; -}; - -} // namespace waybar - -template <> -struct fmt::formatter { +template +struct fmt::formatter> { std::string_view specs; template @@ -33,7 +24,11 @@ struct fmt::formatter { } template - auto format(const waybar::waybar_time& t, FormatContext& ctx) { - return format_to(ctx.out(), "{}", date::format(t.locale, fmt::to_string(specs), t.ztime)); + auto format(const date::zoned_time& ztime, FormatContext& ctx) { + if (ctx.locale()) { + const auto loc = ctx.locale().template get(); + 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)); } }; diff --git a/src/modules/clock.cpp b/src/modules/clock.cpp index 9871024..360b746 100644 --- a/src/modules/clock.cpp +++ b/src/modules/clock.cpp @@ -9,15 +9,13 @@ #include #include +#include "util/date.hpp" #include "util/ustring_clen.hpp" -#include "util/waybar_time.hpp" #ifdef HAVE_LANGINFO_1STDAY #include #include #endif -using waybar::waybar_time; - waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config) : ALabel(config, "clock", id, "{:%H:%M}", 60, false, false, true), current_time_zone_idx_(0), @@ -110,15 +108,13 @@ bool waybar::modules::Clock::is_timezone_fixed() { } auto waybar::modules::Clock::update() -> void { - auto time_zone = current_timezone(); + const auto* time_zone = current_timezone(); auto now = std::chrono::system_clock::now(); - waybar_time wtime = {locale_, - date::make_zoned(time_zone, date::floor(now))}; + auto ztime = date::zoned_time{time_zone, date::floor(now)}; auto shifted_date = date::year_month_day{date::floor(now)} + calendar_shift_; auto now_shifted = date::sys_days{shifted_date} + (now - date::floor(now)); - waybar_time shifted_wtime = { - locale_, date::make_zoned(time_zone, date::floor(now_shifted))}; + auto shifted_ztime = date::zoned_time{time_zone, date::floor(now_shifted)}; std::string text = ""; if (!is_timezone_fixed()) { @@ -127,7 +123,7 @@ auto waybar::modules::Clock::update() -> void { auto localtime = fmt::localtime(std::chrono::system_clock::to_time_t(now)); text = fmt::format(locale_, format_, localtime); } else { - text = fmt::format(format_, wtime); + text = fmt::format(locale_, format_, ztime); } label_.set_markup(text); @@ -136,13 +132,13 @@ auto waybar::modules::Clock::update() -> void { std::string calendar_lines{""}; std::string timezoned_time_lines{""}; if (is_calendar_in_tooltip_) { - calendar_lines = calendar_text(shifted_wtime); + calendar_lines = calendar_text(shifted_ztime); } if (is_timezoned_list_in_tooltip_) { timezoned_time_lines = timezones_text(&now); } auto tooltip_format = config_["tooltip-format"].asString(); - text = fmt::format(tooltip_format, shifted_wtime, + text = fmt::format(locale_, tooltip_format, shifted_ztime, fmt::arg(kCalendarPlaceholder.c_str(), calendar_lines), fmt::arg(KTimezonedTimeListPlaceholder.c_str(), timezoned_time_lines)); label_.set_tooltip_markup(text); @@ -190,8 +186,8 @@ bool waybar::modules::Clock::handleScroll(GdkEventScroll* e) { return true; } -auto waybar::modules::Clock::calendar_text(const waybar_time& wtime) -> std::string { - const auto daypoint = date::floor(wtime.ztime.get_local_time()); +auto waybar::modules::Clock::calendar_text(const date::zoned_seconds& ztime) -> std::string { + const auto daypoint = date::floor(ztime.get_local_time()); const auto ymd{date::year_month_day{daypoint}}; if (calendar_cached_ymd_ == ymd) { @@ -318,7 +314,6 @@ auto waybar::modules::Clock::timezones_text(std::chrono::system_clock::time_poin return ""; } std::stringstream os; - waybar_time wtime; for (size_t time_zone_idx = 0; time_zone_idx < time_zones_.size(); ++time_zone_idx) { if (static_cast(time_zone_idx) == current_time_zone_idx_) { continue; @@ -327,8 +322,8 @@ auto waybar::modules::Clock::timezones_text(std::chrono::system_clock::time_poin if (!timezone) { timezone = date::current_zone(); } - wtime = {locale_, date::make_zoned(timezone, date::floor(*now))}; - os << fmt::format(format_, wtime) << '\n'; + auto ztime = date::zoned_time{timezone, date::floor(*now)}; + os << fmt::format(locale_, format_, ztime) << '\n'; } return os.str(); } diff --git a/test/date.cpp b/test/date.cpp new file mode 100644 index 0000000..704feb2 --- /dev/null +++ b/test/date.cpp @@ -0,0 +1,94 @@ +#include "util/date.hpp" + +#if __has_include() +#include +#include +#else +#include +#endif +#include +#include + +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 TEST_TIME = date::zoned_time{ + "UTC", date::local_days{date::Monday[1] / date::January / 2022} + 13h + 4min + 5s}; + +TEST_CASE("Format UTC time", "[clock][util]") { + const auto loc = std::locale("C"); + const auto tm = TEST_TIME; + + REQUIRE(fmt::format(loc, "{}", tm).empty()); // no format specified + REQUIRE(fmt::format(loc, "{:%c %Z}", tm) == "Mon Jan 3 13:04:05 2022 UTC"); + REQUIRE(fmt::format(loc, "{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 { + const auto loc = std::locale("en_US"); + + REQUIRE(fmt::format(loc, "{}", tm).empty()); // no format specified + REQUIRE_THAT(fmt::format(loc, "{:%c}", tm), // HowardHinnant/date#704 + Catch::Matchers::StartsWith("Mon 03 Jan 2022 01:04:05 PM")); + REQUIRE(fmt::format(loc, "{:%x %X}", tm) == "01/03/2022 01:04:05 PM"); + REQUIRE(fmt::format(loc, "{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 { + const auto loc = std::locale("en_GB"); + + REQUIRE(fmt::format(loc, "{}", tm).empty()); // no format specified + REQUIRE_THAT(fmt::format(loc, "{:%c}", tm), // HowardHinnant/date#704 + Catch::Matchers::StartsWith("Mon 03 Jan 2022 13:04:05")); + REQUIRE(fmt::format(loc, "{:%x %X}", tm) == "03/01/22 13:04:05"); + REQUIRE(fmt::format(loc, "{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]") { + const auto loc = std::locale("C"); + const auto tm = date::zoned_time{"America/New_York", TEST_TIME}; + + REQUIRE(fmt::format(loc, "{}", tm).empty()); // no format specified + REQUIRE(fmt::format(loc, "{:%c %Z}", tm) == "Mon Jan 3 08:04:05 2022 EST"); + REQUIRE(fmt::format(loc, "{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 { + const auto loc = std::locale("en_US"); + + REQUIRE(fmt::format(loc, "{}", tm).empty()); // no format specified + REQUIRE_THAT(fmt::format(loc, "{:%c}", tm), // HowardHinnant/date#704 + Catch::Matchers::StartsWith("Mon 03 Jan 2022 08:04:05 AM")); + REQUIRE(fmt::format(loc, "{:%x %X}", tm) == "01/03/2022 08:04:05 AM"); + REQUIRE(fmt::format(loc, "{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 { + const auto loc = std::locale("en_GB"); + + REQUIRE(fmt::format(loc, "{}", tm).empty()); // no format specified + REQUIRE_THAT(fmt::format(loc, "{:%c}", tm), // HowardHinnant/date#704 + Catch::Matchers::StartsWith("Mon 03 Jan 2022 08:04:05")); + REQUIRE(fmt::format(loc, "{:%x %X}", tm) == "03/01/22 08:04:05"); + REQUIRE(fmt::format(loc, "{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103080405"); + } catch (const std::runtime_error&) { + // locale not found; ignore + } + } +} diff --git a/test/meson.build b/test/meson.build index b1e1123..02cbb2a 100644 --- a/test/meson.build +++ b/test/meson.build @@ -15,7 +15,7 @@ test_src = files( if tz_dep.found() test_dep += tz_dep - test_src += files('waybar_time.cpp') + test_src += files('date.cpp') endif waybar_test = executable( diff --git a/test/waybar_time.cpp b/test/waybar_time.cpp deleted file mode 100644 index 9f9f5dc..0000000 --- a/test/waybar_time.cpp +++ /dev/null @@ -1,95 +0,0 @@ -#include "util/waybar_time.hpp" - -#include -#include - -#if __has_include() -#include -#include -#else -#include -#endif -#include -#include - -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 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 - } - } -} From ea17a66dfc46e2349416d1fa6ff89fe901e95e62 Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Mon, 16 Jan 2023 13:24:55 -0800 Subject: [PATCH 19/29] fix: compilation errors with cpp_std=c++20 There were two main issues with fmtlib and C++20 mode: - `fmt::format` defaults to compile-time argument checking and requires using `fmt::runtime(format_string)` to bypass that. - `std::format` implementation introduces conflicting declarations and we have to specify the namespace for all `format`/`format_to` calls. --- include/modules/sway/workspaces.hpp | 5 ++++- include/util/format.hpp | 8 ++++---- src/modules/backlight.cpp | 9 +++++---- src/modules/battery.cpp | 11 ++++++----- src/modules/bluetooth.cpp | 7 ++++--- src/modules/clock.cpp | 23 ++++++++++++----------- src/modules/custom.cpp | 2 +- src/modules/disk.cpp | 20 ++++++++++---------- src/modules/gamemode.cpp | 8 ++++---- src/modules/hyprland/language.cpp | 8 ++++---- src/modules/hyprland/submap.cpp | 2 +- src/modules/hyprland/window.cpp | 4 ++-- src/modules/idle_inhibitor.cpp | 18 ++++++------------ src/modules/inhibitor.cpp | 2 +- src/modules/jack.cpp | 8 ++++---- src/modules/keyboard_state.cpp | 2 +- src/modules/memory/common.cpp | 7 ++++--- src/modules/mpd/mpd.cpp | 18 +++++++++--------- src/modules/mpris/mpris.cpp | 8 ++++---- src/modules/network.cpp | 6 +++--- src/modules/pulseaudio.cpp | 6 +++--- src/modules/river/mode.cpp | 2 +- src/modules/river/window.cpp | 2 +- src/modules/sndio.cpp | 3 ++- src/modules/sway/language.cpp | 4 ++-- src/modules/sway/mode.cpp | 2 +- src/modules/sway/scratchpad.cpp | 5 +++-- src/modules/sway/window.cpp | 7 ++++--- src/modules/sway/workspaces.cpp | 10 ++++------ src/modules/temperature.cpp | 8 ++++---- src/modules/upower/upower.cpp | 4 ++-- src/modules/user.cpp | 20 ++++++++++---------- src/modules/wireplumber.cpp | 8 ++++---- src/modules/wlr/taskbar.cpp | 21 ++++++++++++--------- src/modules/wlr/workspace_manager.cpp | 2 +- 35 files changed, 143 insertions(+), 137 deletions(-) diff --git a/include/modules/sway/workspaces.hpp b/include/modules/sway/workspaces.hpp index e6df067..f8a55fa 100644 --- a/include/modules/sway/workspaces.hpp +++ b/include/modules/sway/workspaces.hpp @@ -4,6 +4,7 @@ #include #include +#include #include #include "AModule.hpp" @@ -21,7 +22,9 @@ class Workspaces : public AModule, public sigc::trackable { auto update() -> void; private: - static inline const std::string workspace_switch_cmd_ = "workspace {} \"{}\""; + static constexpr std::string_view workspace_switch_cmd_ = "workspace {} \"{}\""; + static constexpr std::string_view persistent_workspace_switch_cmd_ = + R"(workspace {} "{}"; move workspace to output "{}"; workspace {} "{}")"; static int convertWorkspaceNameToNum(std::string name); diff --git a/include/util/format.hpp b/include/util/format.hpp index fac0377..00b6a31 100644 --- a/include/util/format.hpp +++ b/include/util/format.hpp @@ -66,9 +66,9 @@ struct formatter { std::string string; switch (spec) { case '>': - return format_to(ctx.out(), "{:>{}}", fmt::format("{}", s), max_width); + return fmt::format_to(ctx.out(), "{:>{}}", fmt::format("{}", s), max_width); case '<': - return format_to(ctx.out(), "{:<{}}", fmt::format("{}", s), max_width); + return fmt::format_to(ctx.out(), "{:<{}}", fmt::format("{}", s), max_width); case '=': format = "{coefficient:<{number_width}.1f}{padding}{prefix}{unit}"; break; @@ -77,8 +77,8 @@ struct formatter { format = "{coefficient:.1f}{prefix}{unit}"; break; } - return format_to( - ctx.out(), format, fmt::arg("coefficient", fraction), + return fmt::format_to( + ctx.out(), fmt::runtime(format), fmt::arg("coefficient", fraction), fmt::arg("number_width", number_width), fmt::arg("prefix", std::string() + units[pow] + ((s.binary_ && pow) ? "i" : "")), fmt::arg("unit", s.unit_), diff --git a/src/modules/backlight.cpp b/src/modules/backlight.cpp index aa734a4..77c1dc0 100644 --- a/src/modules/backlight.cpp +++ b/src/modules/backlight.cpp @@ -48,13 +48,13 @@ struct UdevMonitorDeleter { void check_eq(int rc, int expected, const char *message = "eq, rc was: ") { if (rc != expected) { - throw std::runtime_error(fmt::format(message, rc)); + throw std::runtime_error(fmt::format(fmt::runtime(message), rc)); } } void check_neq(int rc, int bad_rc, const char *message = "neq, rc was: ") { if (rc == bad_rc) { - throw std::runtime_error(fmt::format(message, rc)); + throw std::runtime_error(fmt::format(fmt::runtime(message), rc)); } } @@ -62,7 +62,7 @@ void check0(int rc, const char *message = "rc wasn't 0") { check_eq(rc, 0, messa void check_gte(int rc, int gte, const char *message = "rc was: ") { if (rc < gte) { - throw std::runtime_error(fmt::format(message, rc)); + throw std::runtime_error(fmt::format(fmt::runtime(message), rc)); } } @@ -181,7 +181,8 @@ auto waybar::modules::Backlight::update() -> void { event_box_.show(); const uint8_t percent = best->get_max() == 0 ? 100 : round(best->get_actual() * 100.0f / best->get_max()); - label_.set_markup(fmt::format(format_, fmt::arg("percent", std::to_string(percent)), + label_.set_markup(fmt::format(fmt::runtime(format_), + fmt::arg("percent", std::to_string(percent)), fmt::arg("icon", getIcon(percent)))); getState(percent); } else { diff --git a/src/modules/battery.cpp b/src/modules/battery.cpp index b3e51a6..abd1240 100644 --- a/src/modules/battery.cpp +++ b/src/modules/battery.cpp @@ -604,7 +604,7 @@ const std::string waybar::modules::Battery::formatTimeRemaining(float hoursRemai format = config_["format-time"].asString(); } std::string zero_pad_minutes = fmt::format("{:02d}", minutes); - return fmt::format(format, fmt::arg("H", full_hours), fmt::arg("M", minutes), + return fmt::format(fmt::runtime(format), fmt::arg("H", full_hours), fmt::arg("M", minutes), fmt::arg("m", zero_pad_minutes)); } @@ -644,7 +644,8 @@ auto waybar::modules::Battery::update() -> void { } else if (config_["tooltip-format"].isString()) { tooltip_format = config_["tooltip-format"].asString(); } - label_.set_tooltip_text(fmt::format(tooltip_format, fmt::arg("timeTo", tooltip_text_default), + label_.set_tooltip_text(fmt::format(fmt::runtime(tooltip_format), + fmt::arg("timeTo", tooltip_text_default), fmt::arg("power", power), fmt::arg("capacity", capacity), fmt::arg("time", time_remaining_formatted))); } @@ -665,9 +666,9 @@ auto waybar::modules::Battery::update() -> void { } else { event_box_.show(); auto icons = std::vector{status + "-" + state, status, state}; - label_.set_markup(fmt::format(format, fmt::arg("capacity", capacity), fmt::arg("power", power), - fmt::arg("icon", getIcon(capacity, icons)), - fmt::arg("time", time_remaining_formatted))); + label_.set_markup(fmt::format( + fmt::runtime(format), fmt::arg("capacity", capacity), fmt::arg("power", power), + fmt::arg("icon", getIcon(capacity, icons)), fmt::arg("time", time_remaining_formatted))); } // Call parent update ALabel::update(); diff --git a/src/modules/bluetooth.cpp b/src/modules/bluetooth.cpp index e6a1fe3..c3a2547 100644 --- a/src/modules/bluetooth.cpp +++ b/src/modules/bluetooth.cpp @@ -206,7 +206,8 @@ auto waybar::modules::Bluetooth::update() -> void { state_ = state; label_.set_markup(fmt::format( - format_, fmt::arg("status", state_), fmt::arg("num_connections", connected_devices_.size()), + fmt::runtime(format_), fmt::arg("status", state_), + fmt::arg("num_connections", connected_devices_.size()), fmt::arg("controller_address", cur_controller_.address), fmt::arg("controller_address_type", cur_controller_.address_type), fmt::arg("controller_alias", cur_controller_.alias), @@ -234,7 +235,7 @@ auto waybar::modules::Bluetooth::update() -> void { enumerate_format = config_["tooltip-format-enumerate-connected"].asString(); } ss << fmt::format( - enumerate_format, fmt::arg("device_address", dev.address), + fmt::runtime(enumerate_format), fmt::arg("device_address", dev.address), fmt::arg("device_address_type", dev.address_type), fmt::arg("device_alias", dev.alias), fmt::arg("icon", enumerate_icon), fmt::arg("device_battery_percentage", dev.battery_percentage.value_or(0))); @@ -247,7 +248,7 @@ auto waybar::modules::Bluetooth::update() -> void { } } label_.set_tooltip_text(fmt::format( - tooltip_format, fmt::arg("status", state_), + fmt::runtime(tooltip_format), fmt::arg("status", state_), fmt::arg("num_connections", connected_devices_.size()), fmt::arg("controller_address", cur_controller_.address), fmt::arg("controller_address_type", cur_controller_.address_type), diff --git a/src/modules/clock.cpp b/src/modules/clock.cpp index 360b746..0dbd255 100644 --- a/src/modules/clock.cpp +++ b/src/modules/clock.cpp @@ -121,9 +121,9 @@ auto waybar::modules::Clock::update() -> void { // As date dep is not fully compatible, prefer fmt tzset(); auto localtime = fmt::localtime(std::chrono::system_clock::to_time_t(now)); - text = fmt::format(locale_, format_, localtime); + text = fmt::format(locale_, fmt::runtime(format_), localtime); } else { - text = fmt::format(locale_, format_, ztime); + text = fmt::format(locale_, fmt::runtime(format_), ztime); } label_.set_markup(text); @@ -138,7 +138,7 @@ auto waybar::modules::Clock::update() -> void { timezoned_time_lines = timezones_text(&now); } auto tooltip_format = config_["tooltip-format"].asString(); - text = fmt::format(locale_, tooltip_format, shifted_ztime, + text = fmt::format(locale_, fmt::runtime(tooltip_format), shifted_ztime, fmt::arg(kCalendarPlaceholder.c_str(), calendar_lines), fmt::arg(KTimezonedTimeListPlaceholder.c_str(), timezoned_time_lines)); label_.set_tooltip_markup(text); @@ -228,7 +228,7 @@ auto waybar::modules::Clock::calendar_text(const date::zoned_seconds& ztime) -> /* Print weeknumber on the left for the first row*/ if (weeks_pos == WeeksSide::LEFT) { - os << fmt::format(fmt_str_weeks_, print_wd) << ' '; + os << fmt::format(fmt::runtime(fmt_str_weeks_), print_wd) << ' '; } if (empty_days > 0) { @@ -242,7 +242,7 @@ auto waybar::modules::Clock::calendar_text(const date::zoned_seconds& ztime) -> os << ' '; } else if (unsigned(d) != 1) { if (weeks_pos == WeeksSide::RIGHT) { - os << ' ' << fmt::format(fmt_str_weeks_, print_wd); + os << ' ' << fmt::format(fmt::runtime(fmt_str_weeks_), print_wd); } os << '\n'; @@ -250,19 +250,19 @@ auto waybar::modules::Clock::calendar_text(const date::zoned_seconds& ztime) -> print_wd = (ym / d); if (weeks_pos == WeeksSide::LEFT) { - os << fmt::format(fmt_str_weeks_, print_wd) << ' '; + os << fmt::format(fmt::runtime(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)); + os << fmt::format(fmt::runtime(today_format), date::format("%e", d)); } else { os << "" << date::format("%e", d) << ""; } } else { - os << fmt::format(fmt_str_calendar_, date::format("%e", d)); + os << fmt::format(fmt::runtime(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) { @@ -271,7 +271,7 @@ auto waybar::modules::Clock::calendar_text(const date::zoned_seconds& ztime) -> os << std::string(empty_days * 3, ' '); } - os << ' ' << fmt::format(fmt_str_weeks_, print_wd); + os << ' ' << fmt::format(fmt::runtime(fmt_str_weeks_), print_wd); } } @@ -303,7 +303,8 @@ auto waybar::modules::Clock::weekdays_header(const date::weekday& first_week_day res << '\n'; if (config_["format-calendar-weekdays"].isString()) { - os << fmt::format(config_["format-calendar-weekdays"].asString(), res.str()); + auto weekdays_format = config_["format-calendar-weekdays"].asString(); + os << fmt::format(fmt::runtime(weekdays_format), res.str()); } else os << res.str(); } @@ -323,7 +324,7 @@ auto waybar::modules::Clock::timezones_text(std::chrono::system_clock::time_poin timezone = date::current_zone(); } auto ztime = date::zoned_time{timezone, date::floor(*now)}; - os << fmt::format(locale_, format_, ztime) << '\n'; + os << fmt::format(locale_, fmt::runtime(format_), ztime) << '\n'; } return os.str(); } diff --git a/src/modules/custom.cpp b/src/modules/custom.cpp index 23dba38..3100adc 100644 --- a/src/modules/custom.cpp +++ b/src/modules/custom.cpp @@ -126,7 +126,7 @@ auto waybar::modules::Custom::update() -> void { } else { 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("percentage", percentage_)); if (str.empty()) { diff --git a/src/modules/disk.cpp b/src/modules/disk.cpp index 5578dc2..eb4d902 100644 --- a/src/modules/disk.cpp +++ b/src/modules/disk.cpp @@ -58,11 +58,11 @@ auto waybar::modules::Disk::update() -> void { event_box_.hide(); } else { event_box_.show(); - label_.set_markup( - fmt::format(format, stats.f_bavail * 100 / stats.f_blocks, fmt::arg("free", free), - fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks), - fmt::arg("used", used), fmt::arg("percentage_used", percentage_used), - fmt::arg("total", total), fmt::arg("path", path_))); + label_.set_markup(fmt::format( + fmt::runtime(format), stats.f_bavail * 100 / stats.f_blocks, fmt::arg("free", free), + fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks), fmt::arg("used", used), + fmt::arg("percentage_used", percentage_used), fmt::arg("total", total), + fmt::arg("path", path_))); } if (tooltipEnabled()) { @@ -70,11 +70,11 @@ auto waybar::modules::Disk::update() -> void { if (config_["tooltip-format"].isString()) { tooltip_format = config_["tooltip-format"].asString(); } - label_.set_tooltip_text( - fmt::format(tooltip_format, stats.f_bavail * 100 / stats.f_blocks, fmt::arg("free", free), - fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks), - fmt::arg("used", used), fmt::arg("percentage_used", percentage_used), - fmt::arg("total", total), fmt::arg("path", path_))); + label_.set_tooltip_text(fmt::format( + fmt::runtime(tooltip_format), stats.f_bavail * 100 / stats.f_blocks, fmt::arg("free", free), + fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks), fmt::arg("used", used), + fmt::arg("percentage_used", percentage_used), fmt::arg("total", total), + fmt::arg("path", path_))); } // Call parent update ALabel::update(); diff --git a/src/modules/gamemode.cpp b/src/modules/gamemode.cpp index 7129297..1b8d7fc 100644 --- a/src/modules/gamemode.cpp +++ b/src/modules/gamemode.cpp @@ -213,14 +213,14 @@ auto Gamemode::update() -> void { // Tooltip if (tooltip) { - std::string text = fmt::format(tooltip_format, fmt::arg("count", gameCount)); + std::string text = fmt::format(fmt::runtime(tooltip_format), fmt::arg("count", gameCount)); box_.set_tooltip_text(text); } // Label format - std::string str = - fmt::format(showAltText ? format_alt : format, fmt::arg("glyph", useIcon ? "" : glyph), - fmt::arg("count", gameCount > 0 ? std::to_string(gameCount) : "")); + std::string str = fmt::format(fmt::runtime(showAltText ? format_alt : format), + fmt::arg("glyph", useIcon ? "" : glyph), + fmt::arg("count", gameCount > 0 ? std::to_string(gameCount) : "")); label_.set_markup(str); if (useIcon) { diff --git a/src/modules/hyprland/language.cpp b/src/modules/hyprland/language.cpp index 6cadd62..d398b23 100644 --- a/src/modules/hyprland/language.cpp +++ b/src/modules/hyprland/language.cpp @@ -59,9 +59,9 @@ void Language::onEvent(const std::string& ev) { if (config_.isMember("format-" + briefName)) { const auto propName = "format-" + briefName; - layoutName = fmt::format(format_, config_[propName].asString()); + layoutName = fmt::format(fmt::runtime(format_), config_[propName].asString()); } else { - layoutName = fmt::format(format_, layoutName); + layoutName = fmt::format(fmt::runtime(format_), layoutName); } layoutName = waybar::util::sanitize_string(layoutName); @@ -92,9 +92,9 @@ void Language::initLanguage() { if (config_.isMember("format-" + briefName)) { const auto propName = "format-" + briefName; - layoutName = fmt::format(format_, config_[propName].asString()); + layoutName = fmt::format(fmt::runtime(format_), config_[propName].asString()); } else { - layoutName = fmt::format(format_, searcher); + layoutName = fmt::format(fmt::runtime(format_), searcher); } layoutName = waybar::util::sanitize_string(layoutName); diff --git a/src/modules/hyprland/submap.cpp b/src/modules/hyprland/submap.cpp index d61c8d4..22acbf3 100644 --- a/src/modules/hyprland/submap.cpp +++ b/src/modules/hyprland/submap.cpp @@ -34,7 +34,7 @@ auto Submap::update() -> void { if (submap_.empty()) { event_box_.hide(); } else { - label_.set_markup(fmt::format(format_, submap_)); + label_.set_markup(fmt::format(fmt::runtime(format_), submap_)); if (tooltipEnabled()) { label_.set_tooltip_text(submap_); } diff --git a/src/modules/hyprland/window.cpp b/src/modules/hyprland/window.cpp index d3d06cc..47daae9 100644 --- a/src/modules/hyprland/window.cpp +++ b/src/modules/hyprland/window.cpp @@ -40,8 +40,8 @@ auto Window::update() -> void { if (!format_.empty()) { label_.show(); - label_.set_markup( - fmt::format(format_, waybar::util::rewriteTitle(lastView, config_["rewrite"]))); + label_.set_markup(fmt::format(fmt::runtime(format_), + waybar::util::rewriteTitle(lastView, config_["rewrite"]))); } else { label_.hide(); } diff --git a/src/modules/idle_inhibitor.cpp b/src/modules/idle_inhibitor.cpp index c4109b0..a5fc9ac 100644 --- a/src/modules/idle_inhibitor.cpp +++ b/src/modules/idle_inhibitor.cpp @@ -63,21 +63,15 @@ auto waybar::modules::IdleInhibitor::update() -> void { } std::string status_text = status ? "activated" : "deactivated"; - label_.set_markup(fmt::format(format_, fmt::arg("status", status_text), + label_.set_markup(fmt::format(fmt::runtime(format_), fmt::arg("status", status_text), fmt::arg("icon", getIcon(0, status_text)))); label_.get_style_context()->add_class(status_text); if (tooltipEnabled()) { - label_.set_tooltip_markup( - status ? fmt::format(config_["tooltip-format-activated"].isString() - ? config_["tooltip-format-activated"].asString() - : "{status}", - fmt::arg("status", status_text), - fmt::arg("icon", getIcon(0, status_text))) - : fmt::format(config_["tooltip-format-deactivated"].isString() - ? config_["tooltip-format-deactivated"].asString() - : "{status}", - fmt::arg("status", status_text), - fmt::arg("icon", getIcon(0, status_text)))); + auto config = config_[status ? "tooltip-format-activated" : "tooltip-format-deactivated"]; + auto tooltip_format = config.isString() ? config.asString() : "{status}"; + label_.set_tooltip_markup(fmt::format(fmt::runtime(tooltip_format), + fmt::arg("status", status_text), + fmt::arg("icon", getIcon(0, status_text)))); } // Call parent update ALabel::update(); diff --git a/src/modules/inhibitor.cpp b/src/modules/inhibitor.cpp index e4340b1..fe2a4be 100644 --- a/src/modules/inhibitor.cpp +++ b/src/modules/inhibitor.cpp @@ -118,7 +118,7 @@ auto Inhibitor::update() -> void { std::string status_text = activated() ? "activated" : "deactivated"; label_.get_style_context()->remove_class(activated() ? "deactivated" : "activated"); - label_.set_markup(fmt::format(format_, fmt::arg("status", status_text), + label_.set_markup(fmt::format(fmt::runtime(format_), fmt::arg("status", status_text), fmt::arg("icon", getIcon(0, status_text)))); label_.get_style_context()->add_class(status_text); diff --git a/src/modules/jack.cpp b/src/modules/jack.cpp index 3a92110..9bd6fcd 100644 --- a/src/modules/jack.cpp +++ b/src/modules/jack.cpp @@ -72,7 +72,7 @@ auto JACK::update() -> void { } else format = "{load}%"; - label_.set_markup(fmt::format(format, fmt::arg("load", std::round(load_)), + label_.set_markup(fmt::format(fmt::runtime(format), fmt::arg("load", std::round(load_)), fmt::arg("bufsize", bufsize_), fmt::arg("samplerate", samplerate_), fmt::arg("latency", fmt::format("{:.2f}", latency)), fmt::arg("xruns", xruns_))); @@ -81,9 +81,9 @@ auto JACK::update() -> void { std::string tooltip_format = "{bufsize}/{samplerate} {latency}ms"; if (config_["tooltip-format"].isString()) tooltip_format = config_["tooltip-format"].asString(); label_.set_tooltip_text(fmt::format( - tooltip_format, fmt::arg("load", std::round(load_)), fmt::arg("bufsize", bufsize_), - fmt::arg("samplerate", samplerate_), fmt::arg("latency", fmt::format("{:.2f}", latency)), - fmt::arg("xruns", xruns_))); + fmt::runtime(tooltip_format), fmt::arg("load", std::round(load_)), + fmt::arg("bufsize", bufsize_), fmt::arg("samplerate", samplerate_), + fmt::arg("latency", fmt::format("{:.2f}", latency)), fmt::arg("xruns", xruns_))); } // Call parent update diff --git a/src/modules/keyboard_state.cpp b/src/modules/keyboard_state.cpp index b2750b6..4c081d6 100644 --- a/src/modules/keyboard_state.cpp +++ b/src/modules/keyboard_state.cpp @@ -278,7 +278,7 @@ auto waybar::modules::KeyboardState::update() -> void { }; for (auto& label_state : label_states) { std::string text; - text = fmt::format(label_state.format, + text = fmt::format(fmt::runtime(label_state.format), fmt::arg("icon", label_state.state ? icon_locked_ : icon_unlocked_), fmt::arg("name", label_state.name)); label_state.label.set_markup(text); diff --git a/src/modules/memory/common.cpp b/src/modules/memory/common.cpp index 4a0e634..544d781 100644 --- a/src/modules/memory/common.cpp +++ b/src/modules/memory/common.cpp @@ -56,7 +56,8 @@ auto waybar::modules::Memory::update() -> void { event_box_.show(); auto icons = std::vector{state}; label_.set_markup(fmt::format( - format, used_ram_percentage, fmt::arg("icon", getIcon(used_ram_percentage, icons)), + fmt::runtime(format), used_ram_percentage, + fmt::arg("icon", getIcon(used_ram_percentage, icons)), fmt::arg("total", total_ram_gigabytes), fmt::arg("swapTotal", total_swap_gigabytes), fmt::arg("percentage", used_ram_percentage), fmt::arg("swapPercentage", used_swap_percentage), fmt::arg("used", used_ram_gigabytes), @@ -68,8 +69,8 @@ auto waybar::modules::Memory::update() -> void { if (config_["tooltip-format"].isString()) { auto tooltip_format = config_["tooltip-format"].asString(); label_.set_tooltip_text(fmt::format( - tooltip_format, used_ram_percentage, fmt::arg("total", total_ram_gigabytes), - fmt::arg("swapTotal", total_swap_gigabytes), + fmt::runtime(tooltip_format), used_ram_percentage, + fmt::arg("total", total_ram_gigabytes), fmt::arg("swapTotal", total_swap_gigabytes), fmt::arg("percentage", used_ram_percentage), fmt::arg("swapPercentage", used_swap_percentage), fmt::arg("used", used_ram_gigabytes), fmt::arg("swapUsed", used_swap_gigabytes), fmt::arg("avail", available_ram_gigabytes), diff --git a/src/modules/mpd/mpd.cpp b/src/modules/mpd/mpd.cpp index 401b759..e728897 100644 --- a/src/modules/mpd/mpd.cpp +++ b/src/modules/mpd/mpd.cpp @@ -174,14 +174,14 @@ void waybar::modules::MPD::setLabel() { try { auto text = fmt::format( - format, fmt::arg("artist", artist.raw()), fmt::arg("albumArtist", album_artist.raw()), - fmt::arg("album", album.raw()), fmt::arg("title", title.raw()), fmt::arg("date", date), - fmt::arg("volume", volume), fmt::arg("elapsedTime", elapsedTime), - fmt::arg("totalTime", totalTime), fmt::arg("songPosition", song_pos), - fmt::arg("queueLength", queue_length), fmt::arg("stateIcon", stateIcon), - fmt::arg("consumeIcon", consumeIcon), fmt::arg("randomIcon", randomIcon), - fmt::arg("repeatIcon", repeatIcon), fmt::arg("singleIcon", singleIcon), - fmt::arg("filename", filename)); + fmt::runtime(format), fmt::arg("artist", artist.raw()), + fmt::arg("albumArtist", album_artist.raw()), fmt::arg("album", album.raw()), + fmt::arg("title", title.raw()), fmt::arg("date", date), fmt::arg("volume", volume), + fmt::arg("elapsedTime", elapsedTime), fmt::arg("totalTime", totalTime), + fmt::arg("songPosition", song_pos), fmt::arg("queueLength", queue_length), + fmt::arg("stateIcon", stateIcon), fmt::arg("consumeIcon", consumeIcon), + fmt::arg("randomIcon", randomIcon), fmt::arg("repeatIcon", repeatIcon), + fmt::arg("singleIcon", singleIcon), fmt::arg("filename", filename)); if (text.empty()) { label_.hide(); } else { @@ -198,7 +198,7 @@ void waybar::modules::MPD::setLabel() { : "MPD (connected)"; try { auto tooltip_text = - fmt::format(tooltip_format, fmt::arg("artist", artist.raw()), + fmt::format(fmt::runtime(tooltip_format), fmt::arg("artist", artist.raw()), fmt::arg("albumArtist", album_artist.raw()), fmt::arg("album", album.raw()), fmt::arg("title", title.raw()), fmt::arg("date", date), fmt::arg("volume", volume), fmt::arg("elapsedTime", elapsedTime), diff --git a/src/modules/mpris/mpris.cpp b/src/modules/mpris/mpris.cpp index 651dfd5..f11821f 100644 --- a/src/modules/mpris/mpris.cpp +++ b/src/modules/mpris/mpris.cpp @@ -378,10 +378,10 @@ auto Mpris::update() -> void { break; } auto label_format = - fmt::format(formatstr, fmt::arg("player", info.name), fmt::arg("status", info.status_string), - fmt::arg("artist", *info.artist), fmt::arg("title", *info.title), - fmt::arg("album", *info.album), fmt::arg("length", *info.length), - fmt::arg("dynamic", dynamic.str()), + fmt::format(fmt::runtime(formatstr), fmt::arg("player", info.name), + fmt::arg("status", info.status_string), fmt::arg("artist", *info.artist), + fmt::arg("title", *info.title), fmt::arg("album", *info.album), + fmt::arg("length", *info.length), fmt::arg("dynamic", dynamic.str()), fmt::arg("player_icon", getIcon(config_["player-icons"], info.name)), fmt::arg("status_icon", getIcon(config_["status-icons"], info.status_string))); label_.set_markup(label_format); diff --git a/src/modules/network.cpp b/src/modules/network.cpp index a4797ee..8409311 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -331,7 +331,7 @@ auto waybar::modules::Network::update() -> void { getState(signal_strength_); auto text = fmt::format( - format_, fmt::arg("essid", essid_), fmt::arg("signaldBm", signal_strength_dbm_), + fmt::runtime(format_), fmt::arg("essid", essid_), fmt::arg("signaldBm", signal_strength_dbm_), fmt::arg("signalStrength", signal_strength_), fmt::arg("signalStrengthApp", signal_strength_app_), fmt::arg("ifname", ifname_), fmt::arg("netmask", netmask_), fmt::arg("ipaddr", ipaddr_), fmt::arg("gwaddr", gwaddr_), @@ -363,8 +363,8 @@ auto waybar::modules::Network::update() -> void { } if (!tooltip_format.empty()) { auto tooltip_text = fmt::format( - tooltip_format, fmt::arg("essid", essid_), fmt::arg("signaldBm", signal_strength_dbm_), - fmt::arg("signalStrength", signal_strength_), + fmt::runtime(tooltip_format), fmt::arg("essid", essid_), + fmt::arg("signaldBm", signal_strength_dbm_), fmt::arg("signalStrength", signal_strength_), fmt::arg("signalStrengthApp", signal_strength_app_), fmt::arg("ifname", ifname_), fmt::arg("netmask", netmask_), fmt::arg("ipaddr", ipaddr_), fmt::arg("gwaddr", gwaddr_), fmt::arg("cidr", cidr_), fmt::arg("frequency", fmt::format("{:.1f}", frequency_)), diff --git a/src/modules/pulseaudio.cpp b/src/modules/pulseaudio.cpp index c797997..0630710 100644 --- a/src/modules/pulseaudio.cpp +++ b/src/modules/pulseaudio.cpp @@ -294,9 +294,9 @@ auto waybar::modules::Pulseaudio::update() -> void { format_source = config_["format-source"].asString(); } } - format_source = fmt::format(format_source, fmt::arg("volume", source_volume_)); + format_source = fmt::format(fmt::runtime(format_source), fmt::arg("volume", source_volume_)); auto text = fmt::format( - format, fmt::arg("desc", desc_), fmt::arg("volume", volume_), + fmt::runtime(format), fmt::arg("desc", desc_), fmt::arg("volume", volume_), fmt::arg("format_source", format_source), fmt::arg("source_volume", source_volume_), fmt::arg("source_desc", source_desc_), fmt::arg("icon", getIcon(volume_, getPulseIcon()))); if (text.empty()) { @@ -313,7 +313,7 @@ auto waybar::modules::Pulseaudio::update() -> void { } if (!tooltip_format.empty()) { label_.set_tooltip_text(fmt::format( - tooltip_format, fmt::arg("desc", desc_), fmt::arg("volume", volume_), + fmt::runtime(tooltip_format), fmt::arg("desc", desc_), fmt::arg("volume", volume_), fmt::arg("format_source", format_source), fmt::arg("source_volume", source_volume_), fmt::arg("source_desc", source_desc_), fmt::arg("icon", getIcon(volume_, getPulseIcon())))); diff --git a/src/modules/river/mode.cpp b/src/modules/river/mode.cpp index 4a51c83..1f788e0 100644 --- a/src/modules/river/mode.cpp +++ b/src/modules/river/mode.cpp @@ -103,7 +103,7 @@ void Mode::handle_mode(const char *mode) { } label_.get_style_context()->add_class(mode); - label_.set_markup(fmt::format(format_, Glib::Markup::escape_text(mode).raw())); + label_.set_markup(fmt::format(fmt::runtime(format_), Glib::Markup::escape_text(mode).raw())); label_.show(); } diff --git a/src/modules/river/window.cpp b/src/modules/river/window.cpp index d0f492f..d93938c 100644 --- a/src/modules/river/window.cpp +++ b/src/modules/river/window.cpp @@ -106,7 +106,7 @@ void Window::handle_focused_view(const char *title) { label_.hide(); // hide empty labels or labels with empty format } else { label_.show(); - label_.set_markup(fmt::format(format_, Glib::Markup::escape_text(title).raw())); + label_.set_markup(fmt::format(fmt::runtime(format_), Glib::Markup::escape_text(title).raw())); } ALabel::update(); diff --git a/src/modules/sndio.cpp b/src/modules/sndio.cpp index e6f1bd0..72e7207 100644 --- a/src/modules/sndio.cpp +++ b/src/modules/sndio.cpp @@ -110,7 +110,8 @@ auto Sndio::update() -> void { label_.get_style_context()->remove_class("muted"); } - auto text = fmt::format(format, fmt::arg("volume", vol), fmt::arg("raw_value", volume_)); + auto text = + fmt::format(fmt::runtime(format), fmt::arg("volume", vol), fmt::arg("raw_value", volume_)); if (text.empty()) { label_.hide(); } else { diff --git a/src/modules/sway/language.cpp b/src/modules/sway/language.cpp index d3730a1..a5860bd 100644 --- a/src/modules/sway/language.cpp +++ b/src/modules/sway/language.cpp @@ -96,14 +96,14 @@ void Language::onEvent(const struct Ipc::ipc_response& res) { auto Language::update() -> void { std::lock_guard lock(mutex_); auto display_layout = trim(fmt::format( - format_, fmt::arg("short", layout_.short_name), + fmt::runtime(format_), fmt::arg("short", layout_.short_name), fmt::arg("shortDescription", layout_.short_description), fmt::arg("long", layout_.full_name), fmt::arg("variant", layout_.variant), fmt::arg("flag", layout_.country_flag()))); label_.set_markup(display_layout); if (tooltipEnabled()) { if (tooltip_format_ != "") { auto tooltip_display_layout = trim( - fmt::format(tooltip_format_, fmt::arg("short", layout_.short_name), + fmt::format(fmt::runtime(tooltip_format_), fmt::arg("short", layout_.short_name), fmt::arg("shortDescription", layout_.short_description), fmt::arg("long", layout_.full_name), fmt::arg("variant", layout_.variant), fmt::arg("flag", layout_.country_flag()))); diff --git a/src/modules/sway/mode.cpp b/src/modules/sway/mode.cpp index 7eaa523..b81735e 100644 --- a/src/modules/sway/mode.cpp +++ b/src/modules/sway/mode.cpp @@ -42,7 +42,7 @@ auto Mode::update() -> void { if (mode_.empty()) { event_box_.hide(); } else { - label_.set_markup(fmt::format(format_, mode_)); + label_.set_markup(fmt::format(fmt::runtime(format_), mode_)); if (tooltipEnabled()) { label_.set_tooltip_text(mode_); } diff --git a/src/modules/sway/scratchpad.cpp b/src/modules/sway/scratchpad.cpp index 59e3053..17dc270 100644 --- a/src/modules/sway/scratchpad.cpp +++ b/src/modules/sway/scratchpad.cpp @@ -32,7 +32,8 @@ auto Scratchpad::update() -> void { if (count_ || show_empty_) { event_box_.show(); label_.set_markup( - fmt::format(format_, fmt::arg("icon", getIcon(count_, "", config_["format-icons"].size())), + fmt::format(fmt::runtime(format_), + fmt::arg("icon", getIcon(count_, "", config_["format-icons"].size())), fmt::arg("count", count_))); if (tooltip_enabled_) { label_.set_tooltip_markup(tooltip_text_); @@ -64,7 +65,7 @@ auto Scratchpad::onCmd(const struct Ipc::ipc_response& res) -> void { if (tooltip_enabled_) { tooltip_text_.clear(); for (const auto& window : tree["nodes"][0]["nodes"][0]["floating_nodes"]) { - tooltip_text_.append(fmt::format(tooltip_format_ + '\n', + tooltip_text_.append(fmt::format(fmt::runtime(tooltip_format_ + '\n'), fmt::arg("app", window["app_id"].asString()), fmt::arg("title", window["name"].asString()))); } diff --git a/src/modules/sway/window.cpp b/src/modules/sway/window.cpp index 0e74b76..7d60d29 100644 --- a/src/modules/sway/window.cpp +++ b/src/modules/sway/window.cpp @@ -204,9 +204,10 @@ auto Window::update() -> void { old_app_id_ = app_id_; } - label_.set_markup(fmt::format( - format_, fmt::arg("title", waybar::util::rewriteTitle(window_, config_["rewrite"])), - fmt::arg("app_id", app_id_), fmt::arg("shell", shell_))); + label_.set_markup( + fmt::format(fmt::runtime(format_), + fmt::arg("title", waybar::util::rewriteTitle(window_, config_["rewrite"])), + fmt::arg("app_id", app_id_), fmt::arg("shell", shell_))); if (tooltipEnabled()) { label_.set_tooltip_text(window_); } diff --git a/src/modules/sway/workspaces.cpp b/src/modules/sway/workspaces.cpp index b621b83..08742ae 100644 --- a/src/modules/sway/workspaces.cpp +++ b/src/modules/sway/workspaces.cpp @@ -233,7 +233,7 @@ auto Workspaces::update() -> void { std::string output = (*it)["name"].asString(); if (config_["format"].isString()) { auto format = config_["format"].asString(); - output = fmt::format(format, fmt::arg("icon", getIcon(output, *it)), + output = fmt::format(fmt::runtime(format), fmt::arg("icon", getIcon(output, *it)), fmt::arg("value", output), fmt::arg("name", trimWorkspaceName(output)), fmt::arg("index", (*it)["num"].asString())); } @@ -259,11 +259,9 @@ Gtk::Button &Workspaces::addButton(const Json::Value &node) { try { if (node["target_output"].isString()) { ipc_.sendCmd(IPC_COMMAND, - fmt::format(workspace_switch_cmd_ + "; move workspace to output \"{}\"; " + - workspace_switch_cmd_, - "--no-auto-back-and-forth", node["name"].asString(), - node["target_output"].asString(), "--no-auto-back-and-forth", - node["name"].asString())); + fmt::format(persistent_workspace_switch_cmd_, "--no-auto-back-and-forth", + node["name"].asString(), node["target_output"].asString(), + "--no-auto-back-and-forth", node["name"].asString())); } else { ipc_.sendCmd(IPC_COMMAND, fmt::format("workspace {} \"{}\"", config_["disable-auto-back-and-forth"].asBool() diff --git a/src/modules/temperature.cpp b/src/modules/temperature.cpp index eca05a7..ff722d7 100644 --- a/src/modules/temperature.cpp +++ b/src/modules/temperature.cpp @@ -55,7 +55,7 @@ auto waybar::modules::Temperature::update() -> void { } auto max_temp = config_["critical-threshold"].isInt() ? config_["critical-threshold"].asInt() : 0; - label_.set_markup(fmt::format(format, fmt::arg("temperatureC", temperature_c), + label_.set_markup(fmt::format(fmt::runtime(format), fmt::arg("temperatureC", temperature_c), fmt::arg("temperatureF", temperature_f), fmt::arg("temperatureK", temperature_k), fmt::arg("icon", getIcon(temperature_c, "", max_temp)))); @@ -64,9 +64,9 @@ auto waybar::modules::Temperature::update() -> void { if (config_["tooltip-format"].isString()) { tooltip_format = config_["tooltip-format"].asString(); } - label_.set_tooltip_text(fmt::format(tooltip_format, fmt::arg("temperatureC", temperature_c), - fmt::arg("temperatureF", temperature_f), - fmt::arg("temperatureK", temperature_k))); + label_.set_tooltip_text(fmt::format( + fmt::runtime(tooltip_format), fmt::arg("temperatureC", temperature_c), + fmt::arg("temperatureF", temperature_f), fmt::arg("temperatureK", temperature_k))); } // Call parent update ALabel::update(); diff --git a/src/modules/upower/upower.cpp b/src/modules/upower/upower.cpp index eb29913..38c1f7f 100644 --- a/src/modules/upower/upower.cpp +++ b/src/modules/upower/upower.cpp @@ -336,8 +336,8 @@ auto UPower::update() -> void { break; } std::string label_format = - fmt::format(showAltText ? format_alt : format, fmt::arg("percentage", percentString), - fmt::arg("time", time_format)); + fmt::format(fmt::runtime(showAltText ? format_alt : format), + fmt::arg("percentage", percentString), fmt::arg("time", time_format)); // Only set the label text if it doesn't only contain spaces bool onlySpaces = true; for (auto& character : label_format) { diff --git a/src/modules/user.cpp b/src/modules/user.cpp index 2f7c6e9..418fc58 100644 --- a/src/modules/user.cpp +++ b/src/modules/user.cpp @@ -127,16 +127,16 @@ auto User::update() -> void { auto startSystemTime = currentSystemTime - workSystemTimeSeconds; long workSystemDays = uptimeSeconds / 86400; - auto label = fmt::format(ALabel::format_, fmt::arg("up_H", fmt::format("{:%H}", startSystemTime)), - fmt::arg("up_M", fmt::format("{:%M}", startSystemTime)), - fmt::arg("up_d", fmt::format("{:%d}", startSystemTime)), - fmt::arg("up_m", fmt::format("{:%m}", startSystemTime)), - fmt::arg("up_Y", fmt::format("{:%Y}", startSystemTime)), - fmt::arg("work_d", workSystemDays), - fmt::arg("work_H", fmt::format("{:%H}", workSystemTimeSeconds)), - fmt::arg("work_M", fmt::format("{:%M}", workSystemTimeSeconds)), - fmt::arg("work_S", fmt::format("{:%S}", workSystemTimeSeconds)), - fmt::arg("user", systemUser)); + auto label = fmt::format( + fmt::runtime(ALabel::format_), fmt::arg("up_H", fmt::format("{:%H}", startSystemTime)), + fmt::arg("up_M", fmt::format("{:%M}", startSystemTime)), + fmt::arg("up_d", fmt::format("{:%d}", startSystemTime)), + fmt::arg("up_m", fmt::format("{:%m}", startSystemTime)), + fmt::arg("up_Y", fmt::format("{:%Y}", startSystemTime)), fmt::arg("work_d", workSystemDays), + fmt::arg("work_H", fmt::format("{:%H}", workSystemTimeSeconds)), + fmt::arg("work_M", fmt::format("{:%M}", workSystemTimeSeconds)), + fmt::arg("work_S", fmt::format("{:%S}", workSystemTimeSeconds)), + fmt::arg("user", systemUser)); ALabel::label_.set_markup(label); AIconLabel::update(); } diff --git a/src/modules/wireplumber.cpp b/src/modules/wireplumber.cpp index 9652e1e..fd1a0d3 100644 --- a/src/modules/wireplumber.cpp +++ b/src/modules/wireplumber.cpp @@ -279,7 +279,7 @@ auto waybar::modules::Wireplumber::update() -> void { label_.get_style_context()->remove_class("muted"); } - std::string markup = fmt::format(format, fmt::arg("node_name", node_name_), + std::string markup = fmt::format(fmt::runtime(format), fmt::arg("node_name", node_name_), fmt::arg("volume", volume_), fmt::arg("icon", getIcon(volume_))); label_.set_markup(markup); @@ -291,9 +291,9 @@ auto waybar::modules::Wireplumber::update() -> void { } if (!tooltip_format.empty()) { - label_.set_tooltip_text(fmt::format(tooltip_format, fmt::arg("node_name", node_name_), - fmt::arg("volume", volume_), - fmt::arg("icon", getIcon(volume_)))); + label_.set_tooltip_text( + fmt::format(fmt::runtime(tooltip_format), fmt::arg("node_name", node_name_), + fmt::arg("volume", volume_), fmt::arg("icon", getIcon(volume_)))); } else { label_.set_tooltip_text(node_name_); } diff --git a/src/modules/wlr/taskbar.cpp b/src/modules/wlr/taskbar.cpp index 5460244..427083b 100644 --- a/src/modules/wlr/taskbar.cpp +++ b/src/modules/wlr/taskbar.cpp @@ -618,9 +618,10 @@ void Task::update() { app_id = Glib::Markup::escape_text(app_id); } if (!format_before_.empty()) { - auto txt = fmt::format(format_before_, fmt::arg("title", title), fmt::arg("name", name), - fmt::arg("app_id", app_id), fmt::arg("state", state_string()), - fmt::arg("short_state", state_string(true))); + auto txt = + fmt::format(fmt::runtime(format_before_), fmt::arg("title", title), fmt::arg("name", name), + fmt::arg("app_id", app_id), fmt::arg("state", state_string()), + fmt::arg("short_state", state_string(true))); if (markup) text_before_.set_markup(txt); else @@ -628,9 +629,10 @@ void Task::update() { text_before_.show(); } if (!format_after_.empty()) { - auto txt = fmt::format(format_after_, fmt::arg("title", title), fmt::arg("name", name), - fmt::arg("app_id", app_id), fmt::arg("state", state_string()), - fmt::arg("short_state", state_string(true))); + auto txt = + fmt::format(fmt::runtime(format_after_), fmt::arg("title", title), fmt::arg("name", name), + fmt::arg("app_id", app_id), fmt::arg("state", state_string()), + fmt::arg("short_state", state_string(true))); if (markup) text_after_.set_markup(txt); else @@ -639,9 +641,10 @@ void Task::update() { } if (!format_tooltip_.empty()) { - auto txt = fmt::format(format_tooltip_, fmt::arg("title", title), fmt::arg("name", name), - fmt::arg("app_id", app_id), fmt::arg("state", state_string()), - fmt::arg("short_state", state_string(true))); + auto txt = + fmt::format(fmt::runtime(format_tooltip_), fmt::arg("title", title), fmt::arg("name", name), + fmt::arg("app_id", app_id), fmt::arg("state", state_string()), + fmt::arg("short_state", state_string(true))); if (markup) button_.set_tooltip_markup(txt); else diff --git a/src/modules/wlr/workspace_manager.cpp b/src/modules/wlr/workspace_manager.cpp index ade0269..c1b68c8 100644 --- a/src/modules/wlr/workspace_manager.cpp +++ b/src/modules/wlr/workspace_manager.cpp @@ -379,7 +379,7 @@ Workspace::~Workspace() { } auto Workspace::update() -> void { - label_.set_markup(fmt::format(format_, fmt::arg("name", name_), + label_.set_markup(fmt::format(fmt::runtime(format_), fmt::arg("name", name_), fmt::arg("icon", with_icon_ ? get_icon() : ""))); } From 6225db0a4855badd0a3047b9aa13ec502d223fef Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Fri, 20 Jan 2023 22:46:16 -0800 Subject: [PATCH 20/29] test: refactor date formatter tests. - Add tests for global locale. - Warn about missing locales. - Downgrade REQUIRE to CHECK. - Skip tests if localized formatting does not work as expected. --- test/date.cpp | 150 ++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 109 insertions(+), 41 deletions(-) diff --git a/test/date.cpp b/test/date.cpp index 704feb2..aa6d79b 100644 --- a/test/date.cpp +++ b/test/date.cpp @@ -1,13 +1,23 @@ #include "util/date.hpp" +#include +#include +#include +#include +#include + #if __has_include() #include #include #else #include #endif -#include -#include + +#ifndef SKIP +#define SKIP(...) \ + WARN(__VA_ARGS__); \ + return +#endif using namespace std::literals::chrono_literals; @@ -18,39 +28,79 @@ using namespace std::literals::chrono_literals; const date::zoned_time 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; - REQUIRE(fmt::format(loc, "{}", tm).empty()); // no format specified - REQUIRE(fmt::format(loc, "{:%c %Z}", tm) == "Mon Jan 3 13:04:05 2022 UTC"); - REQUIRE(fmt::format(loc, "{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103130405"); + 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"); + const auto loc = std::locale("en_US.UTF-8"); - REQUIRE(fmt::format(loc, "{}", tm).empty()); // no format specified - REQUIRE_THAT(fmt::format(loc, "{:%c}", tm), // HowardHinnant/date#704 - Catch::Matchers::StartsWith("Mon 03 Jan 2022 01:04:05 PM")); - REQUIRE(fmt::format(loc, "{:%x %X}", tm) == "01/03/2022 01:04:05 PM"); - REQUIRE(fmt::format(loc, "{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103130405"); - } catch (const std::runtime_error&) { - // locale not found; ignore + 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"); + const auto loc = std::locale("en_GB.UTF-8"); - REQUIRE(fmt::format(loc, "{}", tm).empty()); // no format specified - REQUIRE_THAT(fmt::format(loc, "{:%c}", tm), // HowardHinnant/date#704 - Catch::Matchers::StartsWith("Mon 03 Jan 2022 13:04:05")); - REQUIRE(fmt::format(loc, "{:%x %X}", tm) == "03/01/22 13:04:05"); - REQUIRE(fmt::format(loc, "{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103130405"); - } catch (const std::runtime_error&) { - // locale not found; ignore + 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"); } } } @@ -59,36 +109,54 @@ TEST_CASE("Format zoned time", "[clock][util]") { const auto loc = std::locale("C"); const auto tm = date::zoned_time{"America/New_York", TEST_TIME}; - REQUIRE(fmt::format(loc, "{}", tm).empty()); // no format specified - REQUIRE(fmt::format(loc, "{:%c %Z}", tm) == "Mon Jan 3 08:04:05 2022 EST"); - REQUIRE(fmt::format(loc, "{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103080405"); + 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"); + const auto loc = std::locale("en_US.UTF-8"); - REQUIRE(fmt::format(loc, "{}", tm).empty()); // no format specified - REQUIRE_THAT(fmt::format(loc, "{:%c}", tm), // HowardHinnant/date#704 - Catch::Matchers::StartsWith("Mon 03 Jan 2022 08:04:05 AM")); - REQUIRE(fmt::format(loc, "{:%x %X}", tm) == "01/03/2022 08:04:05 AM"); - REQUIRE(fmt::format(loc, "{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103080405"); - } catch (const std::runtime_error&) { - // locale not found; ignore + 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"); + const auto loc = std::locale("en_GB.UTF-8"); - REQUIRE(fmt::format(loc, "{}", tm).empty()); // no format specified - REQUIRE_THAT(fmt::format(loc, "{:%c}", tm), // HowardHinnant/date#704 - Catch::Matchers::StartsWith("Mon 03 Jan 2022 08:04:05")); - REQUIRE(fmt::format(loc, "{:%x %X}", tm) == "03/01/22 08:04:05"); - REQUIRE(fmt::format(loc, "{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103080405"); - } catch (const std::runtime_error&) { - // locale not found; ignore + 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"); } } } From 93e340a081bcf32ba775baf11a2aaa468af99239 Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Mon, 16 Jan 2023 17:30:06 -0800 Subject: [PATCH 21/29] feat(clock): support chrono Time Zone extensions. Use chrono Calendars and Time Zones (P0355R7, P1466R3) when available instead of the `date` library. Verified with a patched build of a recent GCC 13 snapshot. --- include/factory.hpp | 2 +- include/modules/clock.hpp | 3 +-- include/util/date.hpp | 28 +++++++++++++++++++++++++++- meson.build | 22 ++++++++++++++++------ src/modules/clock.cpp | 1 - 5 files changed, 45 insertions(+), 11 deletions(-) diff --git a/include/factory.hpp b/include/factory.hpp index 21dc647..558a8d4 100644 --- a/include/factory.hpp +++ b/include/factory.hpp @@ -1,7 +1,7 @@ #pragma once #include -#ifdef HAVE_LIBDATE +#if defined(HAVE_CHRONO_TIMEZONES) || defined(HAVE_LIBDATE) #include "modules/clock.hpp" #else #include "modules/simpleclock.hpp" diff --git a/include/modules/clock.hpp b/include/modules/clock.hpp index c97565d..a7290f6 100644 --- a/include/modules/clock.hpp +++ b/include/modules/clock.hpp @@ -1,8 +1,7 @@ #pragma once -#include - #include "ALabel.hpp" +#include "util/date.hpp" #include "util/sleeper_thread.hpp" namespace waybar::modules { diff --git a/include/util/date.hpp b/include/util/date.hpp index ec948bd..380bb6e 100644 --- a/include/util/date.hpp +++ b/include/util/date.hpp @@ -1,8 +1,34 @@ #pragma once -#include #include +#if HAVE_CHRONO_TIMEZONES +#include +#include + +/* Compatibility layer for on top of C++20 */ +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 +#endif + template struct fmt::formatter> { std::string_view specs; diff --git a/meson.build b/meson.build index 7b63181..32bfd2b 100644 --- a/meson.build +++ b/meson.build @@ -123,11 +123,18 @@ gtk_layer_shell = dependency('gtk-layer-shell-0', required: get_option('gtk-layer-shell'), fallback : ['gtk-layer-shell', 'gtk_layer_shell_dep']) systemd = dependency('systemd', required: get_option('systemd')) -tz_dep = dependency('date', - required: false, - default_options : [ 'use_system_tzdb=true' ], - modules : [ 'date::date', 'date::date-tz' ], - fallback: [ 'date', 'tz_dep' ]) + +cpp_lib_chrono = compiler.compute_int('__cpp_lib_chrono', prefix : '#include ') +have_chrono_timezones = cpp_lib_chrono >= 201907 +if have_chrono_timezones + tz_dep = declare_dependency() +else + tz_dep = dependency('date', + required: false, + default_options : [ 'use_system_tzdb=true' ], + modules : [ 'date::date', 'date::date-tz' ], + fallback: [ 'date', 'tz_dep' ]) +endif prefix = get_option('prefix') sysconfdir = get_option('sysconfdir') @@ -312,7 +319,10 @@ if get_option('rfkill').enabled() and is_linux ) endif -if tz_dep.found() +if have_chrono_timezones + add_project_arguments('-DHAVE_CHRONO_TIMEZONES', language: 'cpp') + src_files += 'src/modules/clock.cpp' +elif tz_dep.found() add_project_arguments('-DHAVE_LIBDATE', language: 'cpp') src_files += 'src/modules/clock.cpp' else diff --git a/src/modules/clock.cpp b/src/modules/clock.cpp index 0dbd255..76ec73c 100644 --- a/src/modules/clock.cpp +++ b/src/modules/clock.cpp @@ -9,7 +9,6 @@ #include #include -#include "util/date.hpp" #include "util/ustring_clen.hpp" #ifdef HAVE_LANGINFO_1STDAY #include From 01cee153a44805f6b0a107ab8613a3586c1888dd Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Mon, 16 Jan 2023 23:56:26 -0800 Subject: [PATCH 22/29] ci: try to build with cpp_std=c++20 Add an extra job to build with `-std=c++20` on Fedora. Update actions/checkout to v3. --- .github/workflows/freebsd.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/linux.yml | 10 +++++++--- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/.github/workflows/freebsd.yml b/.github/workflows/freebsd.yml index a6da7ef..550f945 100644 --- a/.github/workflows/freebsd.yml +++ b/.github/workflows/freebsd.yml @@ -9,7 +9,7 @@ jobs: # https://github.com/actions/virtual-environments/issues/4060 - for lack of VirtualBox on MacOS 11 runners runs-on: macos-12 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Test in FreeBSD VM uses: vmactions/freebsd-vm@v0 with: diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index e9f1656..d11d2cc 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -6,7 +6,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: DoozyX/clang-format-lint-action@v0.13 with: source: '.' diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 4c77c3a..c82af85 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -13,16 +13,20 @@ jobs: - fedora - opensuse - gentoo + cpp_std: [c++17] + include: + - distro: fedora + cpp_std: c++20 runs-on: ubuntu-latest container: image: alexays/waybar:${{ matrix.distro }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: configure - run: meson -Dman-pages=enabled build + run: meson -Dman-pages=enabled -Dcpp_std=${{matrix.cpp_std}} build - name: build run: ninja -C build - name: test - run: meson test -C build --no-rebuild --print-errorlogs --suite waybar + run: meson test -C build --no-rebuild --verbose --suite waybar From 51b6c22cab5420f15bc1b59ff81fbbfc0bd0c5c8 Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Sat, 21 Jan 2023 00:14:01 -0800 Subject: [PATCH 23/29] ci: add glibc locales for date formatting tests. Add some missing dependencies for Fedora. --- Dockerfiles/archlinux | 3 ++- Dockerfiles/fedora | 37 +++++++++++++++++++++++++++++-------- 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/Dockerfiles/archlinux b/Dockerfiles/archlinux index e7cbba6..cab4146 100644 --- a/Dockerfiles/archlinux +++ b/Dockerfiles/archlinux @@ -3,4 +3,5 @@ FROM archlinux:base-devel 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 diff --git a/Dockerfiles/fedora b/Dockerfiles/fedora index e1abd44..5892159 100644 --- a/Dockerfiles/fedora +++ b/Dockerfiles/fedora @@ -2,12 +2,33 @@ FROM fedora:latest -RUN dnf install -y @c-development git-core meson scdoc 'pkgconfig(date)' \ - 'pkgconfig(dbusmenu-gtk3-0.4)' 'pkgconfig(fmt)' 'pkgconfig(gdk-pixbuf-2.0)' \ - 'pkgconfig(gio-unix-2.0)' 'pkgconfig(gtk-layer-shell-0)' 'pkgconfig(gtkmm-3.0)' \ - 'pkgconfig(jsoncpp)' 'pkgconfig(libinput)' 'pkgconfig(libmpdclient)' \ - 'pkgconfig(libnl-3.0)' 'pkgconfig(libnl-genl-3.0)' 'pkgconfig(libpulse)' \ - 'pkgconfig(libudev)' 'pkgconfig(pugixml)' 'pkgconfig(sigc++-2.0)' 'pkgconfig(spdlog)' \ - 'pkgconfig(wayland-client)' 'pkgconfig(wayland-cursor)' 'pkgconfig(wayland-protocols)' 'pkgconfig(xkbregistry)' \ - 'pkgconfig(playerctl)' && \ +RUN dnf install -y @c-development \ + git-core glibc-langpack-en meson scdoc \ + 'pkgconfig(catch2)' \ + 'pkgconfig(date)' \ + 'pkgconfig(dbusmenu-gtk3-0.4)' \ + 'pkgconfig(fmt)' \ + 'pkgconfig(gdk-pixbuf-2.0)' \ + 'pkgconfig(gio-unix-2.0)' \ + 'pkgconfig(gtk-layer-shell-0)' \ + 'pkgconfig(gtkmm-3.0)' \ + 'pkgconfig(jack)' \ + 'pkgconfig(jsoncpp)' \ + 'pkgconfig(libevdev)' \ + 'pkgconfig(libinput)' \ + 'pkgconfig(libmpdclient)' \ + 'pkgconfig(libnl-3.0)' \ + 'pkgconfig(libnl-genl-3.0)' \ + 'pkgconfig(libpulse)' \ + 'pkgconfig(libudev)' \ + 'pkgconfig(playerctl)' \ + 'pkgconfig(pugixml)' \ + 'pkgconfig(sigc++-2.0)' \ + 'pkgconfig(spdlog)' \ + 'pkgconfig(upower-glib)' \ + 'pkgconfig(wayland-client)' \ + 'pkgconfig(wayland-cursor)' \ + 'pkgconfig(wayland-protocols)' \ + 'pkgconfig(wireplumber-0.4)' \ + 'pkgconfig(xkbregistry)' && \ dnf clean all -y From de77787b606bd930c22fce2746ed9fd25830e330 Mon Sep 17 00:00:00 2001 From: Kauan Decarli Date: Sat, 21 Jan 2023 21:57:28 -0300 Subject: [PATCH 24/29] Allow any module to implement signal handling --- include/AModule.hpp | 1 + src/bar.cpp | 5 +---- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/include/AModule.hpp b/include/AModule.hpp index 357f70e..c5f0ebc 100644 --- a/include/AModule.hpp +++ b/include/AModule.hpp @@ -15,6 +15,7 @@ class AModule : public IModule { bool enable_scroll = false); virtual ~AModule(); virtual auto update() -> void; + virtual auto refresh(int) -> void {}; virtual operator Gtk::Widget &(); Glib::Dispatcher dp; diff --git a/src/bar.cpp b/src/bar.cpp index f46b7d0..62ff80c 100644 --- a/src/bar.cpp +++ b/src/bar.cpp @@ -725,10 +725,7 @@ void waybar::Bar::setupAltFormatKeyForModuleList(const char* module_list_name) { void waybar::Bar::handleSignal(int signal) { for (auto& module : modules_all_) { - auto* custom = dynamic_cast(module.get()); - if (custom != nullptr) { - custom->refresh(signal); - } + module->refresh(signal); } } From 0ca1c3957a0b02528c03507cd80463c3bfe29902 Mon Sep 17 00:00:00 2001 From: asas1asas200 Date: Sun, 22 Jan 2023 20:16:46 +0800 Subject: [PATCH 25/29] docs(image): add image doc in meson and fix title --- man/waybar-image.5.scd | 4 ++-- man/waybar.5.scd.in | 1 + meson.build | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/man/waybar-image.5.scd b/man/waybar-image.5.scd index feff9f6..df7086f 100644 --- a/man/waybar-image.5.scd +++ b/man/waybar-image.5.scd @@ -1,4 +1,4 @@ -waybar-custom(5) +waybar-image(5) # NAME @@ -69,4 +69,4 @@ Addressed by *custom/* "interval": 5, "on-click": "mpc toggle" } -``` \ No newline at end of file +``` diff --git a/man/waybar.5.scd.in b/man/waybar.5.scd.in index 7566dd0..704d666 100644 --- a/man/waybar.5.scd.in +++ b/man/waybar.5.scd.in @@ -263,6 +263,7 @@ A module group is defined by specifying a module named "group/some-group-name". - *waybar-custom(5)* - *waybar-disk(5)* - *waybar-idle-inhibitor(5)* +- *waybar-image(5)* - *waybar-keyboard-state(5)* - *waybar-memory(5)* - *waybar-mpd(5)* diff --git a/meson.build b/meson.build index ebf68d4..96ef1f3 100644 --- a/meson.build +++ b/meson.build @@ -395,6 +395,7 @@ if scdoc.found() 'waybar-disk.5.scd', 'waybar-gamemode.5.scd', 'waybar-idle-inhibitor.5.scd', + 'waybar-image.5.scd', 'waybar-keyboard-state.5.scd', 'waybar-memory.5.scd', 'waybar-mpd.5.scd', From f4cfafd2380607d1fd448bb93e29d43aa3cb8777 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 23 Jan 2023 09:25:02 +0100 Subject: [PATCH 26/29] fix: lint --- include/AModule.hpp | 2 +- src/modules/custom.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/AModule.hpp b/include/AModule.hpp index c5f0ebc..35625cd 100644 --- a/include/AModule.hpp +++ b/include/AModule.hpp @@ -15,7 +15,7 @@ class AModule : public IModule { bool enable_scroll = false); virtual ~AModule(); virtual auto update() -> void; - virtual auto refresh(int) -> void {}; + virtual auto refresh(int) -> void{}; virtual operator Gtk::Widget &(); Glib::Dispatcher dp; diff --git a/src/modules/custom.cpp b/src/modules/custom.cpp index 9818f5a..b7e1d2d 100644 --- a/src/modules/custom.cpp +++ b/src/modules/custom.cpp @@ -210,7 +210,7 @@ void waybar::modules::Custom::parseOutputJson() { } } if (!parsed["percentage"].asString().empty() && parsed["percentage"].isNumeric()) { - percentage_ = (int) lround(parsed["percentage"].asFloat()); + percentage_ = (int)lround(parsed["percentage"].asFloat()); } else { percentage_ = 0; } From 3c8ca009ff2630bdb2e9778448d73e9b3e37faee Mon Sep 17 00:00:00 2001 From: Enes Hecan Date: Fri, 13 Jan 2023 16:28:34 +0100 Subject: [PATCH 27/29] Sanitize hyprland language string only instead of the whole format. Fixes #1940 --- src/modules/hyprland/language.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/modules/hyprland/language.cpp b/src/modules/hyprland/language.cpp index d398b23..f9ad091 100644 --- a/src/modules/hyprland/language.cpp +++ b/src/modules/hyprland/language.cpp @@ -55,6 +55,8 @@ void Language::onEvent(const std::string& ev) { if (config_.isMember("keyboard-name") && kbName != config_["keyboard-name"].asString()) return; // ignore + layoutName = waybar::util::sanitize_string(layoutName); + const auto briefName = getShortFrom(layoutName); if (config_.isMember("format-" + briefName)) { @@ -64,8 +66,6 @@ void Language::onEvent(const std::string& ev) { layoutName = fmt::format(fmt::runtime(format_), layoutName); } - layoutName = waybar::util::sanitize_string(layoutName); - if (layoutName == layoutName_) return; layoutName_ = layoutName; @@ -87,6 +87,8 @@ void Language::initLanguage() { searcher = searcher.substr(searcher.find("keymap:") + 8); searcher = searcher.substr(0, searcher.find_first_of("\n\t")); + searcher = waybar::util::sanitize_string(searcher); + auto layoutName = std::string{}; const auto briefName = getShortFrom(searcher); @@ -97,8 +99,6 @@ void Language::initLanguage() { layoutName = fmt::format(fmt::runtime(format_), searcher); } - layoutName = waybar::util::sanitize_string(layoutName); - layoutName_ = layoutName; spdlog::debug("hyprland language initLanguage found {}", layoutName_); From ed31b20c26a4ee0dedd24f5ab5d0f8f40918e159 Mon Sep 17 00:00:00 2001 From: Viktar Lukashonak Date: Mon, 23 Jan 2023 18:42:32 +0300 Subject: [PATCH 28/29] Merge branch 'master' into YearCalendar Signed-off-by: Viktar Lukashonak --- .github/workflows/freebsd.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/linux.yml | 10 +- Dockerfiles/archlinux | 3 +- Dockerfiles/fedora | 37 +- include/AModule.hpp | 1 + include/factory.hpp | 2 +- include/modules/clock.hpp | 51 ++- include/modules/sway/ipc/client.hpp | 1 + include/modules/sway/workspaces.hpp | 5 +- include/util/date.hpp | 60 ++++ include/util/format.hpp | 8 +- include/util/waybar_time.hpp | 39 --- man/waybar-image.5.scd | 4 +- man/waybar.5.scd.in | 1 + meson.build | 25 +- src/bar.cpp | 5 +- src/modules/backlight.cpp | 9 +- src/modules/battery.cpp | 11 +- src/modules/bluetooth.cpp | 7 +- src/modules/clock.cpp | 480 +++++++++++++++++--------- src/modules/custom.cpp | 6 +- src/modules/disk.cpp | 20 +- src/modules/gamemode.cpp | 8 +- src/modules/hyprland/language.cpp | 16 +- src/modules/hyprland/submap.cpp | 3 +- src/modules/hyprland/window.cpp | 4 +- src/modules/idle_inhibitor.cpp | 18 +- src/modules/inhibitor.cpp | 2 +- src/modules/jack.cpp | 8 +- src/modules/keyboard_state.cpp | 2 +- src/modules/memory/common.cpp | 7 +- src/modules/mpd/mpd.cpp | 18 +- src/modules/mpris/mpris.cpp | 8 +- src/modules/network.cpp | 6 +- src/modules/pulseaudio.cpp | 6 +- src/modules/river/mode.cpp | 2 +- src/modules/river/window.cpp | 2 +- src/modules/sndio.cpp | 3 +- src/modules/sway/ipc/client.cpp | 2 + src/modules/sway/language.cpp | 4 +- src/modules/sway/mode.cpp | 2 +- src/modules/sway/scratchpad.cpp | 5 +- src/modules/sway/window.cpp | 7 +- src/modules/sway/workspaces.cpp | 10 +- src/modules/temperature.cpp | 8 +- src/modules/upower/upower.cpp | 4 +- src/modules/user.cpp | 20 +- src/modules/wireplumber.cpp | 8 +- src/modules/wlr/taskbar.cpp | 21 +- src/modules/wlr/workspace_manager.cpp | 2 +- test/SafeSignal.cpp | 6 +- test/config.cpp | 6 +- test/date.cpp | 162 +++++++++ test/main.cpp | 15 +- test/meson.build | 2 +- test/waybar_time.cpp | 90 ----- 57 files changed, 799 insertions(+), 477 deletions(-) create mode 100644 include/util/date.hpp delete mode 100644 include/util/waybar_time.hpp create mode 100644 test/date.cpp delete mode 100644 test/waybar_time.cpp diff --git a/.github/workflows/freebsd.yml b/.github/workflows/freebsd.yml index a6da7ef..550f945 100644 --- a/.github/workflows/freebsd.yml +++ b/.github/workflows/freebsd.yml @@ -9,7 +9,7 @@ jobs: # https://github.com/actions/virtual-environments/issues/4060 - for lack of VirtualBox on MacOS 11 runners runs-on: macos-12 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Test in FreeBSD VM uses: vmactions/freebsd-vm@v0 with: diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index e9f1656..d11d2cc 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -6,7 +6,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: DoozyX/clang-format-lint-action@v0.13 with: source: '.' diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 4c77c3a..c82af85 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -13,16 +13,20 @@ jobs: - fedora - opensuse - gentoo + cpp_std: [c++17] + include: + - distro: fedora + cpp_std: c++20 runs-on: ubuntu-latest container: image: alexays/waybar:${{ matrix.distro }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: configure - run: meson -Dman-pages=enabled build + run: meson -Dman-pages=enabled -Dcpp_std=${{matrix.cpp_std}} build - name: build run: ninja -C build - name: test - run: meson test -C build --no-rebuild --print-errorlogs --suite waybar + run: meson test -C build --no-rebuild --verbose --suite waybar diff --git a/Dockerfiles/archlinux b/Dockerfiles/archlinux index e7cbba6..cab4146 100644 --- a/Dockerfiles/archlinux +++ b/Dockerfiles/archlinux @@ -3,4 +3,5 @@ FROM archlinux:base-devel 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 diff --git a/Dockerfiles/fedora b/Dockerfiles/fedora index e1abd44..5892159 100644 --- a/Dockerfiles/fedora +++ b/Dockerfiles/fedora @@ -2,12 +2,33 @@ FROM fedora:latest -RUN dnf install -y @c-development git-core meson scdoc 'pkgconfig(date)' \ - 'pkgconfig(dbusmenu-gtk3-0.4)' 'pkgconfig(fmt)' 'pkgconfig(gdk-pixbuf-2.0)' \ - 'pkgconfig(gio-unix-2.0)' 'pkgconfig(gtk-layer-shell-0)' 'pkgconfig(gtkmm-3.0)' \ - 'pkgconfig(jsoncpp)' 'pkgconfig(libinput)' 'pkgconfig(libmpdclient)' \ - 'pkgconfig(libnl-3.0)' 'pkgconfig(libnl-genl-3.0)' 'pkgconfig(libpulse)' \ - 'pkgconfig(libudev)' 'pkgconfig(pugixml)' 'pkgconfig(sigc++-2.0)' 'pkgconfig(spdlog)' \ - 'pkgconfig(wayland-client)' 'pkgconfig(wayland-cursor)' 'pkgconfig(wayland-protocols)' 'pkgconfig(xkbregistry)' \ - 'pkgconfig(playerctl)' && \ +RUN dnf install -y @c-development \ + git-core glibc-langpack-en meson scdoc \ + 'pkgconfig(catch2)' \ + 'pkgconfig(date)' \ + 'pkgconfig(dbusmenu-gtk3-0.4)' \ + 'pkgconfig(fmt)' \ + 'pkgconfig(gdk-pixbuf-2.0)' \ + 'pkgconfig(gio-unix-2.0)' \ + 'pkgconfig(gtk-layer-shell-0)' \ + 'pkgconfig(gtkmm-3.0)' \ + 'pkgconfig(jack)' \ + 'pkgconfig(jsoncpp)' \ + 'pkgconfig(libevdev)' \ + 'pkgconfig(libinput)' \ + 'pkgconfig(libmpdclient)' \ + 'pkgconfig(libnl-3.0)' \ + 'pkgconfig(libnl-genl-3.0)' \ + 'pkgconfig(libpulse)' \ + 'pkgconfig(libudev)' \ + 'pkgconfig(playerctl)' \ + 'pkgconfig(pugixml)' \ + 'pkgconfig(sigc++-2.0)' \ + 'pkgconfig(spdlog)' \ + 'pkgconfig(upower-glib)' \ + 'pkgconfig(wayland-client)' \ + 'pkgconfig(wayland-cursor)' \ + 'pkgconfig(wayland-protocols)' \ + 'pkgconfig(wireplumber-0.4)' \ + 'pkgconfig(xkbregistry)' && \ dnf clean all -y diff --git a/include/AModule.hpp b/include/AModule.hpp index 357f70e..35625cd 100644 --- a/include/AModule.hpp +++ b/include/AModule.hpp @@ -15,6 +15,7 @@ class AModule : public IModule { bool enable_scroll = false); virtual ~AModule(); virtual auto update() -> void; + virtual auto refresh(int) -> void{}; virtual operator Gtk::Widget &(); Glib::Dispatcher dp; diff --git a/include/factory.hpp b/include/factory.hpp index 21dc647..558a8d4 100644 --- a/include/factory.hpp +++ b/include/factory.hpp @@ -1,7 +1,7 @@ #pragma once #include -#ifdef HAVE_LIBDATE +#if defined(HAVE_CHRONO_TIMEZONES) || defined(HAVE_LIBDATE) #include "modules/clock.hpp" #else #include "modules/simpleclock.hpp" diff --git a/include/modules/clock.hpp b/include/modules/clock.hpp index ef129fb..9d615ae 100644 --- a/include/modules/clock.hpp +++ b/include/modules/clock.hpp @@ -1,19 +1,25 @@ #pragma once -#include - #include "ALabel.hpp" +#include "util/date.hpp" #include "util/sleeper_thread.hpp" -namespace waybar { - -struct waybar_time; - -namespace modules { +namespace waybar::modules { const std::string kCalendarPlaceholder = "calendar"; const std::string KTimezonedTimeListPlaceholder = "timezoned_time_list"; +enum class WeeksSide { + LEFT, + RIGHT, + HIDDEN, +}; + +enum class CldMode { + MONTH, + YEAR +}; + class Clock : public ALabel { public: Clock(const std::string&, const Json::Value&); @@ -22,26 +28,37 @@ class Clock : public ALabel { private: util::SleeperThread thread_; + std::map, void (waybar::modules::Clock::*)()> eventMap_; std::locale locale_; std::vector time_zones_; int current_time_zone_idx_; - date::year_month_day calendar_cached_ymd_{date::January / 1 / 0}; - date::months calendar_shift_{0}, calendar_shift_init_{0}; - std::string calendar_cached_text_; bool is_calendar_in_tooltip_; bool is_timezoned_list_in_tooltip_; bool handleScroll(GdkEventScroll* e); + bool handleToggle(GdkEventButton* const& e); - 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; const date::time_zone* current_timezone(); bool is_timezone_fixed(); auto timezones_text(std::chrono::system_clock::time_point* now) -> std::string; + + /*Calendar properties*/ + WeeksSide cldWPos_{WeeksSide::HIDDEN}; + std::map 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_{2}; // Length of the week number + date::year_month_day cldYearShift_; + date::year_month cldMonShift_; + date::months cldCurrShift_{0}; + date::months cldShift_{0}; + std::string cldYearCached_{}; + std::string cldMonCached_{}; + /*Calendar functions*/ + auto get_calendar(const date::zoned_seconds& now, + const date::zoned_seconds& wtime) -> std::string; + void cldModeSwitch(); }; -} // namespace modules -} // namespace waybar +} // namespace waybar::modules diff --git a/include/modules/sway/ipc/client.hpp b/include/modules/sway/ipc/client.hpp index 77dab08..a6705ea 100644 --- a/include/modules/sway/ipc/client.hpp +++ b/include/modules/sway/ipc/client.hpp @@ -8,6 +8,7 @@ #include #include #include +#include #include "ipc.hpp" #include "util/sleeper_thread.hpp" diff --git a/include/modules/sway/workspaces.hpp b/include/modules/sway/workspaces.hpp index e6df067..f8a55fa 100644 --- a/include/modules/sway/workspaces.hpp +++ b/include/modules/sway/workspaces.hpp @@ -4,6 +4,7 @@ #include #include +#include #include #include "AModule.hpp" @@ -21,7 +22,9 @@ class Workspaces : public AModule, public sigc::trackable { auto update() -> void; private: - static inline const std::string workspace_switch_cmd_ = "workspace {} \"{}\""; + static constexpr std::string_view workspace_switch_cmd_ = "workspace {} \"{}\""; + static constexpr std::string_view persistent_workspace_switch_cmd_ = + R"(workspace {} "{}"; move workspace to output "{}"; workspace {} "{}")"; static int convertWorkspaceNameToNum(std::string name); diff --git a/include/util/date.hpp b/include/util/date.hpp new file mode 100644 index 0000000..380bb6e --- /dev/null +++ b/include/util/date.hpp @@ -0,0 +1,60 @@ +#pragma once + +#include + +#if HAVE_CHRONO_TIMEZONES +#include +#include + +/* Compatibility layer for on top of C++20 */ +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 +#endif + +template +struct fmt::formatter> { + std::string_view specs; + + template + 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 + auto format(const date::zoned_time& ztime, FormatContext& ctx) { + if (ctx.locale()) { + const auto loc = ctx.locale().template get(); + 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)); + } +}; diff --git a/include/util/format.hpp b/include/util/format.hpp index fac0377..00b6a31 100644 --- a/include/util/format.hpp +++ b/include/util/format.hpp @@ -66,9 +66,9 @@ struct formatter { std::string string; switch (spec) { case '>': - return format_to(ctx.out(), "{:>{}}", fmt::format("{}", s), max_width); + return fmt::format_to(ctx.out(), "{:>{}}", fmt::format("{}", s), max_width); case '<': - return format_to(ctx.out(), "{:<{}}", fmt::format("{}", s), max_width); + return fmt::format_to(ctx.out(), "{:<{}}", fmt::format("{}", s), max_width); case '=': format = "{coefficient:<{number_width}.1f}{padding}{prefix}{unit}"; break; @@ -77,8 +77,8 @@ struct formatter { format = "{coefficient:.1f}{prefix}{unit}"; break; } - return format_to( - ctx.out(), format, fmt::arg("coefficient", fraction), + return fmt::format_to( + ctx.out(), fmt::runtime(format), fmt::arg("coefficient", fraction), fmt::arg("number_width", number_width), fmt::arg("prefix", std::string() + units[pow] + ((s.binary_ && pow) ? "i" : "")), fmt::arg("unit", s.unit_), diff --git a/include/util/waybar_time.hpp b/include/util/waybar_time.hpp deleted file mode 100644 index b9f9ea9..0000000 --- a/include/util/waybar_time.hpp +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once - -#include -#include - -namespace waybar { - -struct waybar_time { - std::locale locale; - date::zoned_seconds ztime; -}; - -} // namespace waybar - -template <> -struct fmt::formatter { - std::string_view specs; - - template - 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 - auto format(const waybar::waybar_time& t, FormatContext& ctx) { - return format_to(ctx.out(), "{}", date::format(t.locale, fmt::to_string(specs), t.ztime)); - } -}; diff --git a/man/waybar-image.5.scd b/man/waybar-image.5.scd index feff9f6..df7086f 100644 --- a/man/waybar-image.5.scd +++ b/man/waybar-image.5.scd @@ -1,4 +1,4 @@ -waybar-custom(5) +waybar-image(5) # NAME @@ -69,4 +69,4 @@ Addressed by *custom/* "interval": 5, "on-click": "mpc toggle" } -``` \ No newline at end of file +``` diff --git a/man/waybar.5.scd.in b/man/waybar.5.scd.in index 7566dd0..704d666 100644 --- a/man/waybar.5.scd.in +++ b/man/waybar.5.scd.in @@ -263,6 +263,7 @@ A module group is defined by specifying a module named "group/some-group-name". - *waybar-custom(5)* - *waybar-disk(5)* - *waybar-idle-inhibitor(5)* +- *waybar-image(5)* - *waybar-keyboard-state(5)* - *waybar-memory(5)* - *waybar-mpd(5)* diff --git a/meson.build b/meson.build index ebf68d4..77b292a 100644 --- a/meson.build +++ b/meson.build @@ -123,11 +123,18 @@ gtk_layer_shell = dependency('gtk-layer-shell-0', required: get_option('gtk-layer-shell'), fallback : ['gtk-layer-shell', 'gtk_layer_shell_dep']) systemd = dependency('systemd', required: get_option('systemd')) -tz_dep = dependency('date', - required: false, - default_options : [ 'use_system_tzdb=true' ], - modules : [ 'date::date', 'date::date-tz' ], - fallback: [ 'date', 'tz_dep' ]) + +cpp_lib_chrono = compiler.compute_int('__cpp_lib_chrono', prefix : '#include ') +have_chrono_timezones = cpp_lib_chrono >= 201907 +if have_chrono_timezones + tz_dep = declare_dependency() +else + tz_dep = dependency('date', + required: false, + default_options : [ 'use_system_tzdb=true' ], + modules : [ 'date::date', 'date::date-tz' ], + fallback: [ 'date', 'tz_dep' ]) +endif prefix = get_option('prefix') sysconfdir = get_option('sysconfdir') @@ -312,7 +319,10 @@ if get_option('rfkill').enabled() and is_linux ) endif -if tz_dep.found() +if have_chrono_timezones + add_project_arguments('-DHAVE_CHRONO_TIMEZONES', language: 'cpp') + src_files += 'src/modules/clock.cpp' +elif tz_dep.found() add_project_arguments('-DHAVE_LIBDATE', language: 'cpp') src_files += 'src/modules/clock.cpp' else @@ -395,6 +405,7 @@ if scdoc.found() 'waybar-disk.5.scd', 'waybar-gamemode.5.scd', 'waybar-idle-inhibitor.5.scd', + 'waybar-image.5.scd', 'waybar-keyboard-state.5.scd', 'waybar-memory.5.scd', 'waybar-mpd.5.scd', @@ -447,7 +458,7 @@ endif catch2 = dependency( 'catch2', - version: '>=3.0.0', + version: '>=2.0.0', fallback: ['catch2', 'catch2_dep'], required: get_option('tests'), ) diff --git a/src/bar.cpp b/src/bar.cpp index f46b7d0..62ff80c 100644 --- a/src/bar.cpp +++ b/src/bar.cpp @@ -725,10 +725,7 @@ void waybar::Bar::setupAltFormatKeyForModuleList(const char* module_list_name) { void waybar::Bar::handleSignal(int signal) { for (auto& module : modules_all_) { - auto* custom = dynamic_cast(module.get()); - if (custom != nullptr) { - custom->refresh(signal); - } + module->refresh(signal); } } diff --git a/src/modules/backlight.cpp b/src/modules/backlight.cpp index aa734a4..77c1dc0 100644 --- a/src/modules/backlight.cpp +++ b/src/modules/backlight.cpp @@ -48,13 +48,13 @@ struct UdevMonitorDeleter { void check_eq(int rc, int expected, const char *message = "eq, rc was: ") { if (rc != expected) { - throw std::runtime_error(fmt::format(message, rc)); + throw std::runtime_error(fmt::format(fmt::runtime(message), rc)); } } void check_neq(int rc, int bad_rc, const char *message = "neq, rc was: ") { if (rc == bad_rc) { - throw std::runtime_error(fmt::format(message, rc)); + throw std::runtime_error(fmt::format(fmt::runtime(message), rc)); } } @@ -62,7 +62,7 @@ void check0(int rc, const char *message = "rc wasn't 0") { check_eq(rc, 0, messa void check_gte(int rc, int gte, const char *message = "rc was: ") { if (rc < gte) { - throw std::runtime_error(fmt::format(message, rc)); + throw std::runtime_error(fmt::format(fmt::runtime(message), rc)); } } @@ -181,7 +181,8 @@ auto waybar::modules::Backlight::update() -> void { event_box_.show(); const uint8_t percent = best->get_max() == 0 ? 100 : round(best->get_actual() * 100.0f / best->get_max()); - label_.set_markup(fmt::format(format_, fmt::arg("percent", std::to_string(percent)), + label_.set_markup(fmt::format(fmt::runtime(format_), + fmt::arg("percent", std::to_string(percent)), fmt::arg("icon", getIcon(percent)))); getState(percent); } else { diff --git a/src/modules/battery.cpp b/src/modules/battery.cpp index b3e51a6..abd1240 100644 --- a/src/modules/battery.cpp +++ b/src/modules/battery.cpp @@ -604,7 +604,7 @@ const std::string waybar::modules::Battery::formatTimeRemaining(float hoursRemai format = config_["format-time"].asString(); } std::string zero_pad_minutes = fmt::format("{:02d}", minutes); - return fmt::format(format, fmt::arg("H", full_hours), fmt::arg("M", minutes), + return fmt::format(fmt::runtime(format), fmt::arg("H", full_hours), fmt::arg("M", minutes), fmt::arg("m", zero_pad_minutes)); } @@ -644,7 +644,8 @@ auto waybar::modules::Battery::update() -> void { } else if (config_["tooltip-format"].isString()) { tooltip_format = config_["tooltip-format"].asString(); } - label_.set_tooltip_text(fmt::format(tooltip_format, fmt::arg("timeTo", tooltip_text_default), + label_.set_tooltip_text(fmt::format(fmt::runtime(tooltip_format), + fmt::arg("timeTo", tooltip_text_default), fmt::arg("power", power), fmt::arg("capacity", capacity), fmt::arg("time", time_remaining_formatted))); } @@ -665,9 +666,9 @@ auto waybar::modules::Battery::update() -> void { } else { event_box_.show(); auto icons = std::vector{status + "-" + state, status, state}; - label_.set_markup(fmt::format(format, fmt::arg("capacity", capacity), fmt::arg("power", power), - fmt::arg("icon", getIcon(capacity, icons)), - fmt::arg("time", time_remaining_formatted))); + label_.set_markup(fmt::format( + fmt::runtime(format), fmt::arg("capacity", capacity), fmt::arg("power", power), + fmt::arg("icon", getIcon(capacity, icons)), fmt::arg("time", time_remaining_formatted))); } // Call parent update ALabel::update(); diff --git a/src/modules/bluetooth.cpp b/src/modules/bluetooth.cpp index e6a1fe3..c3a2547 100644 --- a/src/modules/bluetooth.cpp +++ b/src/modules/bluetooth.cpp @@ -206,7 +206,8 @@ auto waybar::modules::Bluetooth::update() -> void { state_ = state; label_.set_markup(fmt::format( - format_, fmt::arg("status", state_), fmt::arg("num_connections", connected_devices_.size()), + fmt::runtime(format_), fmt::arg("status", state_), + fmt::arg("num_connections", connected_devices_.size()), fmt::arg("controller_address", cur_controller_.address), fmt::arg("controller_address_type", cur_controller_.address_type), fmt::arg("controller_alias", cur_controller_.alias), @@ -234,7 +235,7 @@ auto waybar::modules::Bluetooth::update() -> void { enumerate_format = config_["tooltip-format-enumerate-connected"].asString(); } ss << fmt::format( - enumerate_format, fmt::arg("device_address", dev.address), + fmt::runtime(enumerate_format), fmt::arg("device_address", dev.address), fmt::arg("device_address_type", dev.address_type), fmt::arg("device_alias", dev.alias), fmt::arg("icon", enumerate_icon), fmt::arg("device_battery_percentage", dev.battery_percentage.value_or(0))); @@ -247,7 +248,7 @@ auto waybar::modules::Bluetooth::update() -> void { } } label_.set_tooltip_text(fmt::format( - tooltip_format, fmt::arg("status", state_), + fmt::runtime(tooltip_format), fmt::arg("status", state_), fmt::arg("num_connections", connected_devices_.size()), fmt::arg("controller_address", cur_controller_.address), fmt::arg("controller_address_type", cur_controller_.address_type), diff --git a/src/modules/clock.cpp b/src/modules/clock.cpp index 55f2c5b..37d5e30 100644 --- a/src/modules/clock.cpp +++ b/src/modules/clock.cpp @@ -10,14 +10,11 @@ #include #include "util/ustring_clen.hpp" -#include "util/waybar_time.hpp" #ifdef HAVE_LANGINFO_1STDAY #include #include #endif -using waybar::waybar_time; - waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config) : ALabel(config, "clock", id, "{:%H:%M}", 60, false, false, true), current_time_zone_idx_(0), @@ -41,12 +38,6 @@ waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config) time_zones_.push_back(nullptr); } - if (!is_timezone_fixed()) { - spdlog::warn( - "As using a timezone, some format args may be missing as the date library haven't got a " - "release since 2018."); - } - // Check if a particular placeholder is present in the tooltip format, to know what to calculate // on update. if (config_["tooltip-format"].isString()) { @@ -62,39 +53,98 @@ waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config) } } + // Calendar configuration if (is_calendar_in_tooltip_) { - if (config_["on-scroll"][kCalendarPlaceholder].isInt()) { - calendar_shift_init_ = - date::months{config_["on-scroll"].get(kCalendarPlaceholder, 0).asInt()}; + if (config_[kCalendarPlaceholder]["weeks-pos"].isString()) { + if (config_[kCalendarPlaceholder]["weeks-pos"].asString() == "left") { + cldWPos_ = WeeksSide::LEFT; + } else if (config_[kCalendarPlaceholder]["weeks-pos"].asString() == "right") { + cldWPos_ = WeeksSide::RIGHT; + } + } + if (config_[kCalendarPlaceholder]["format"]["months"].isString()) + fmtMap_.insert({0, config_[kCalendarPlaceholder]["format"]["months"].asString()}); + else + fmtMap_.insert({0, "{}"}); + if (config_[kCalendarPlaceholder]["format"]["days"].isString()) + fmtMap_.insert({2, config_[kCalendarPlaceholder]["format"]["days"].asString()}); + else + fmtMap_.insert({2, "{}"}); + if (config_[kCalendarPlaceholder]["format"]["weeks"].isString()) { + fmtMap_.insert( + {4, std::regex_replace(config_[kCalendarPlaceholder]["format"]["weeks"].asString(), + std::regex("\\{\\}"), + (first_day_of_week() == date::Monday) ? "{:%W}" : "{:%U}")}); + + if (cldWPos_ == WeeksSide::HIDDEN) + cldWnLen_ = 0; + else { + // tmp contains full length of the weeks including user characters + Glib::ustring tmp{std::regex_replace(fmtMap_[4], std::regex("]+>|\\{.*\\}"), "")}; + cldWnLen_ += (tmp.size() + 1); + cldMonColLen_ += cldWnLen_; + } + } else { + if (cldWPos_ != WeeksSide::HIDDEN) + fmtMap_.insert({4, (first_day_of_week() == date::Monday) ? "{:%W}" : "{:%U}"}); + } + 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 monthModes{{"month", CldMode::MONTH}, + {"year", CldMode::YEAR}}; + if (monthModes.find(cfgMode) != monthModes.end()) + cldMode_ = monthModes.at(cfgMode); + else + spdlog::warn( + "Clock calendar configuration \"mode\"\"\" \"{0}\" is not recognized. Mode = \"month\" " + "is using instead", + cfgMode); + } + if (config_[kCalendarPlaceholder]["mode-mon-col"].isInt()) { + cldMonCols_ = config_[kCalendarPlaceholder]["mode-mon-col"].asInt(); + if (cldMonCols_ == 0u || 12 % cldMonCols_ != 0u) { + cldMonCols_ = 3u; + spdlog::warn( + "Clock calendar configuration \"mode-mon-col\" = {0} must be one of [1, 2, 3, 4, 6, " + "12]. Value 3 is using instead", + cldMonCols_); + } + } else + cldMonCols_ = 1; + if (config_[kCalendarPlaceholder]["on-scroll"].isInt()) { + cldShift_ = date::months{config_[kCalendarPlaceholder]["on-scroll"].asInt()}; event_box_.add_events(Gdk::LEAVE_NOTIFY_MASK); event_box_.signal_leave_notify_event().connect([this](GdkEventCrossing*) { - calendar_shift_ = date::months{0}; + cldCurrShift_ = date::months{0}; return false; }); } + if (config_[kCalendarPlaceholder]["on-click-left"].isString()) { + if (config_[kCalendarPlaceholder]["on-click-left"].asString() == "mode") + eventMap_.insert({std::make_pair(1, GdkEventType::GDK_BUTTON_PRESS), + &waybar::modules::Clock::cldModeSwitch}); + } + if (config_[kCalendarPlaceholder]["on-click-right"].isString()) { + if (config_[kCalendarPlaceholder]["on-click-right"].asString() == "mode") + eventMap_.insert({std::make_pair(3, GdkEventType::GDK_BUTTON_PRESS), + &waybar::modules::Clock::cldModeSwitch}); + } } - if (config_["locale"].isString()) { + if (config_["locale"].isString()) locale_ = std::locale(config_["locale"].asString()); - } else { + else locale_ = std::locale(""); - } - - 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] { dp.emit(); @@ -116,24 +166,22 @@ bool waybar::modules::Clock::is_timezone_fixed() { } auto waybar::modules::Clock::update() -> void { - auto time_zone = current_timezone(); + const auto* time_zone = current_timezone(); auto now = std::chrono::system_clock::now(); - waybar_time wtime = {locale_, - date::make_zoned(time_zone, date::floor(now))}; + auto ztime = date::zoned_time{time_zone, date::floor(now)}; - auto shifted_date = date::year_month_day{date::floor(now)} + calendar_shift_; + auto shifted_date = date::year_month_day{date::floor(now)} + cldCurrShift_; auto now_shifted = date::sys_days{shifted_date} + (now - date::floor(now)); - waybar_time shifted_wtime = { - locale_, date::make_zoned(time_zone, date::floor(now_shifted))}; + auto shifted_ztime = date::zoned_time{time_zone, date::floor(now_shifted)}; - std::string text = ""; + std::string text{""}; if (!is_timezone_fixed()) { // As date dep is not fully compatible, prefer fmt tzset(); auto localtime = fmt::localtime(std::chrono::system_clock::to_time_t(now)); - text = fmt::format(locale_, format_, localtime); + text = fmt::format(locale_, fmt::runtime(format_), localtime); } else { - text = fmt::format(format_, wtime); + text = fmt::format(locale_, fmt::runtime(format_), ztime); } label_.set_markup(text); @@ -142,13 +190,13 @@ auto waybar::modules::Clock::update() -> void { std::string calendar_lines{""}; std::string timezoned_time_lines{""}; if (is_calendar_in_tooltip_) { - calendar_lines = calendar_text(shifted_wtime); + calendar_lines = get_calendar(ztime, shifted_ztime); } if (is_timezoned_list_in_tooltip_) { timezoned_time_lines = timezones_text(&now); } auto tooltip_format = config_["tooltip-format"].asString(); - text = fmt::format(tooltip_format, shifted_wtime, + text = fmt::format(locale_, fmt::runtime(tooltip_format), shifted_ztime, fmt::arg(kCalendarPlaceholder.c_str(), calendar_lines), fmt::arg(KTimezonedTimeListPlaceholder.c_str(), timezoned_time_lines)); label_.set_tooltip_markup(text); @@ -159,6 +207,21 @@ auto waybar::modules::Clock::update() -> void { ALabel::update(); } +bool waybar::modules::Clock::handleToggle(GdkEventButton* const& e) { + const std::map, 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 AModule::handleToggle(e); + + update(); + return true; +} + bool waybar::modules::Clock::handleScroll(GdkEventScroll* e) { // defer to user commands if set if (config_["on-scroll-up"].isString() || config_["on-scroll-down"].isString()) { @@ -168,11 +231,11 @@ bool waybar::modules::Clock::handleScroll(GdkEventScroll* e) { auto dir = AModule::getScrollDir(e); // Shift calendar date - if (calendar_shift_init_.count() != 0) { + if (cldShift_.count() != 0) { if (dir == SCROLL_DIR::UP) - calendar_shift_ += calendar_shift_init_; + cldCurrShift_ += ((cldMode_ == CldMode::YEAR) ? 12 : 1) * cldShift_; else - calendar_shift_ -= calendar_shift_init_; + cldCurrShift_ -= ((cldMode_ == CldMode::YEAR) ? 12 : 1) * cldShift_; } else { // Change time zone if (dir != SCROLL_DIR::UP && dir != SCROLL_DIR::DOWN) { @@ -196,126 +259,212 @@ bool waybar::modules::Clock::handleScroll(GdkEventScroll* e) { return true; } -auto waybar::modules::Clock::calendar_text(const waybar_time& wtime) -> std::string { - const auto daypoint = date::floor(wtime.ztime.get_local_time()); - const auto ymd{date::year_month_day{daypoint}}; - - if (calendar_cached_ymd_ == ymd) { - return calendar_cached_text_; - } - - const auto curr_day{(calendar_shift_init_.count() != 0 && calendar_shift_.count() != 0) - ? date::day{0} - : ymd.day()}; - const date::year_month ym{ymd.year(), ymd.month()}; - const auto 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 << "" << date::format("%e", d) << ""; - } - } 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; +// The number of weeks in calendar month layout plus 1 more for calendar titles +unsigned cldRowsInMonth(date::year_month const ym, date::weekday const firstdow) { + using namespace date; + return static_cast( + ceil((weekday{ym / 1} - firstdow) + ((ym / last).day() - day{0})).count()) + + 2; } -auto waybar::modules::Clock::weekdays_header(const date::weekday& first_week_day, std::ostream& os) - -> void { - std::stringstream res; - auto wd = first_week_day; - do { - if (wd != first_week_day) { - res << ' '; - } - 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'; +auto cldGetWeekForLine(date::year_month const ym, date::weekday const firstdow, unsigned const line) + -> const date::year_month_weekday { + unsigned index = line - 2; + auto sd = date::sys_days{ym / 1}; + if (date::weekday{sd} == firstdow) ++index; + auto ymdw = ym / firstdow[index]; + return ymdw; +} - if (config_["format-calendar-weekdays"].isString()) { - os << fmt::format(config_["format-calendar-weekdays"].asString(), res.str()); - } else - os << res.str(); +auto getCalendarLine(date::year_month_day const currDate, date::year_month const ym, + unsigned const line, date::weekday const firstdow, int rowLen, + const std::locale* const locale_) -> std::string { + using namespace date::literals; + std::ostringstream res; + + switch (line) { + case 0: { + // Output month and year title + Glib::ustring wd_ustring{Glib::ustring::format( + std::left, std::setw(rowLen), date::format(*locale_, "%B %Y", ym), std::right)}; + res << wd_ustring; + 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((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((firstdow - wd).count()) * 3, ' '); + } else // Otherwise not a valid week, output a blank row + res << std::string(rowLen, ' '); + + 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(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(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(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 && cldWnLen_ > 0) os << std::string(cldWnLen_, ' '); + + if (line > 1 && line < ml[static_cast(ymTmp.month()) - 1u]) + os << fmt::format(fmt::runtime(fmtMap_[4]), + (line == 2) + ? date::sys_days{ymTmp / 1} + : date::sys_days{cldGetWeekForLine(ymTmp, firstdow, line)}) + << ' '; + } + + os << getCalendarLine(currDate, ymTmp, line, firstdow, cldMonColLen_, &locale_); + + // Week numbers on the right + if (cldWPos_ == WeeksSide ::RIGHT && line > 0) { + if (line == 1 && cldWnLen_ > 0) os << std::string(cldWnLen_, ' '); + + if (line > 1 && line < ml[static_cast(ymTmp.month()) - 1u]) + os << ' ' + << fmt::format(fmt::runtime(fmtMap_[4]), + (line == 2) + ? date::sys_days{ymTmp / 1} + : date::sys_days{cldGetWeekForLine(ymTmp, firstdow, line)}); + } + } + } + + // 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) @@ -324,7 +473,6 @@ auto waybar::modules::Clock::timezones_text(std::chrono::system_clock::time_poin return ""; } std::stringstream os; - waybar_time wtime; for (size_t time_zone_idx = 0; time_zone_idx < time_zones_.size(); ++time_zone_idx) { if (static_cast(time_zone_idx) == current_time_zone_idx_) { continue; @@ -333,8 +481,8 @@ auto waybar::modules::Clock::timezones_text(std::chrono::system_clock::time_poin if (!timezone) { timezone = date::current_zone(); } - wtime = {locale_, date::make_zoned(timezone, date::floor(*now))}; - os << fmt::format(format_, wtime) << '\n'; + auto ztime = date::zoned_time{timezone, date::floor(*now)}; + os << fmt::format(locale_, fmt::runtime(format_), ztime) << '\n'; } return os.str(); } diff --git a/src/modules/custom.cpp b/src/modules/custom.cpp index 23dba38..b7e1d2d 100644 --- a/src/modules/custom.cpp +++ b/src/modules/custom.cpp @@ -126,7 +126,7 @@ auto waybar::modules::Custom::update() -> void { } else { 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("percentage", percentage_)); if (str.empty()) { @@ -209,8 +209,8 @@ void waybar::modules::Custom::parseOutputJson() { class_.push_back(c.asString()); } } - if (!parsed["percentage"].asString().empty() && parsed["percentage"].isUInt()) { - percentage_ = parsed["percentage"].asUInt(); + if (!parsed["percentage"].asString().empty() && parsed["percentage"].isNumeric()) { + percentage_ = (int)lround(parsed["percentage"].asFloat()); } else { percentage_ = 0; } diff --git a/src/modules/disk.cpp b/src/modules/disk.cpp index 5578dc2..eb4d902 100644 --- a/src/modules/disk.cpp +++ b/src/modules/disk.cpp @@ -58,11 +58,11 @@ auto waybar::modules::Disk::update() -> void { event_box_.hide(); } else { event_box_.show(); - label_.set_markup( - fmt::format(format, stats.f_bavail * 100 / stats.f_blocks, fmt::arg("free", free), - fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks), - fmt::arg("used", used), fmt::arg("percentage_used", percentage_used), - fmt::arg("total", total), fmt::arg("path", path_))); + label_.set_markup(fmt::format( + fmt::runtime(format), stats.f_bavail * 100 / stats.f_blocks, fmt::arg("free", free), + fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks), fmt::arg("used", used), + fmt::arg("percentage_used", percentage_used), fmt::arg("total", total), + fmt::arg("path", path_))); } if (tooltipEnabled()) { @@ -70,11 +70,11 @@ auto waybar::modules::Disk::update() -> void { if (config_["tooltip-format"].isString()) { tooltip_format = config_["tooltip-format"].asString(); } - label_.set_tooltip_text( - fmt::format(tooltip_format, stats.f_bavail * 100 / stats.f_blocks, fmt::arg("free", free), - fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks), - fmt::arg("used", used), fmt::arg("percentage_used", percentage_used), - fmt::arg("total", total), fmt::arg("path", path_))); + label_.set_tooltip_text(fmt::format( + fmt::runtime(tooltip_format), stats.f_bavail * 100 / stats.f_blocks, fmt::arg("free", free), + fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks), fmt::arg("used", used), + fmt::arg("percentage_used", percentage_used), fmt::arg("total", total), + fmt::arg("path", path_))); } // Call parent update ALabel::update(); diff --git a/src/modules/gamemode.cpp b/src/modules/gamemode.cpp index 7129297..1b8d7fc 100644 --- a/src/modules/gamemode.cpp +++ b/src/modules/gamemode.cpp @@ -213,14 +213,14 @@ auto Gamemode::update() -> void { // Tooltip if (tooltip) { - std::string text = fmt::format(tooltip_format, fmt::arg("count", gameCount)); + std::string text = fmt::format(fmt::runtime(tooltip_format), fmt::arg("count", gameCount)); box_.set_tooltip_text(text); } // Label format - std::string str = - fmt::format(showAltText ? format_alt : format, fmt::arg("glyph", useIcon ? "" : glyph), - fmt::arg("count", gameCount > 0 ? std::to_string(gameCount) : "")); + std::string str = fmt::format(fmt::runtime(showAltText ? format_alt : format), + fmt::arg("glyph", useIcon ? "" : glyph), + fmt::arg("count", gameCount > 0 ? std::to_string(gameCount) : "")); label_.set_markup(str); if (useIcon) { diff --git a/src/modules/hyprland/language.cpp b/src/modules/hyprland/language.cpp index 6cadd62..f9ad091 100644 --- a/src/modules/hyprland/language.cpp +++ b/src/modules/hyprland/language.cpp @@ -55,17 +55,17 @@ void Language::onEvent(const std::string& ev) { if (config_.isMember("keyboard-name") && kbName != config_["keyboard-name"].asString()) return; // ignore + layoutName = waybar::util::sanitize_string(layoutName); + const auto briefName = getShortFrom(layoutName); if (config_.isMember("format-" + briefName)) { const auto propName = "format-" + briefName; - layoutName = fmt::format(format_, config_[propName].asString()); + layoutName = fmt::format(fmt::runtime(format_), config_[propName].asString()); } else { - layoutName = fmt::format(format_, layoutName); + layoutName = fmt::format(fmt::runtime(format_), layoutName); } - layoutName = waybar::util::sanitize_string(layoutName); - if (layoutName == layoutName_) return; layoutName_ = layoutName; @@ -87,18 +87,18 @@ void Language::initLanguage() { searcher = searcher.substr(searcher.find("keymap:") + 8); searcher = searcher.substr(0, searcher.find_first_of("\n\t")); + searcher = waybar::util::sanitize_string(searcher); + auto layoutName = std::string{}; const auto briefName = getShortFrom(searcher); if (config_.isMember("format-" + briefName)) { const auto propName = "format-" + briefName; - layoutName = fmt::format(format_, config_[propName].asString()); + layoutName = fmt::format(fmt::runtime(format_), config_[propName].asString()); } else { - layoutName = fmt::format(format_, searcher); + layoutName = fmt::format(fmt::runtime(format_), searcher); } - layoutName = waybar::util::sanitize_string(layoutName); - layoutName_ = layoutName; spdlog::debug("hyprland language initLanguage found {}", layoutName_); diff --git a/src/modules/hyprland/submap.cpp b/src/modules/hyprland/submap.cpp index 6eb0942..22acbf3 100644 --- a/src/modules/hyprland/submap.cpp +++ b/src/modules/hyprland/submap.cpp @@ -19,6 +19,7 @@ Submap::Submap(const std::string& id, const Bar& bar, const Json::Value& config) // register for hyprland ipc gIPC->registerForIPC("submap", this); + dp.emit(); } Submap::~Submap() { @@ -33,7 +34,7 @@ auto Submap::update() -> void { if (submap_.empty()) { event_box_.hide(); } else { - label_.set_markup(fmt::format(format_, submap_)); + label_.set_markup(fmt::format(fmt::runtime(format_), submap_)); if (tooltipEnabled()) { label_.set_tooltip_text(submap_); } diff --git a/src/modules/hyprland/window.cpp b/src/modules/hyprland/window.cpp index d3d06cc..47daae9 100644 --- a/src/modules/hyprland/window.cpp +++ b/src/modules/hyprland/window.cpp @@ -40,8 +40,8 @@ auto Window::update() -> void { if (!format_.empty()) { label_.show(); - label_.set_markup( - fmt::format(format_, waybar::util::rewriteTitle(lastView, config_["rewrite"]))); + label_.set_markup(fmt::format(fmt::runtime(format_), + waybar::util::rewriteTitle(lastView, config_["rewrite"]))); } else { label_.hide(); } diff --git a/src/modules/idle_inhibitor.cpp b/src/modules/idle_inhibitor.cpp index c4109b0..a5fc9ac 100644 --- a/src/modules/idle_inhibitor.cpp +++ b/src/modules/idle_inhibitor.cpp @@ -63,21 +63,15 @@ auto waybar::modules::IdleInhibitor::update() -> void { } std::string status_text = status ? "activated" : "deactivated"; - label_.set_markup(fmt::format(format_, fmt::arg("status", status_text), + label_.set_markup(fmt::format(fmt::runtime(format_), fmt::arg("status", status_text), fmt::arg("icon", getIcon(0, status_text)))); label_.get_style_context()->add_class(status_text); if (tooltipEnabled()) { - label_.set_tooltip_markup( - status ? fmt::format(config_["tooltip-format-activated"].isString() - ? config_["tooltip-format-activated"].asString() - : "{status}", - fmt::arg("status", status_text), - fmt::arg("icon", getIcon(0, status_text))) - : fmt::format(config_["tooltip-format-deactivated"].isString() - ? config_["tooltip-format-deactivated"].asString() - : "{status}", - fmt::arg("status", status_text), - fmt::arg("icon", getIcon(0, status_text)))); + auto config = config_[status ? "tooltip-format-activated" : "tooltip-format-deactivated"]; + auto tooltip_format = config.isString() ? config.asString() : "{status}"; + label_.set_tooltip_markup(fmt::format(fmt::runtime(tooltip_format), + fmt::arg("status", status_text), + fmt::arg("icon", getIcon(0, status_text)))); } // Call parent update ALabel::update(); diff --git a/src/modules/inhibitor.cpp b/src/modules/inhibitor.cpp index e4340b1..fe2a4be 100644 --- a/src/modules/inhibitor.cpp +++ b/src/modules/inhibitor.cpp @@ -118,7 +118,7 @@ auto Inhibitor::update() -> void { std::string status_text = activated() ? "activated" : "deactivated"; label_.get_style_context()->remove_class(activated() ? "deactivated" : "activated"); - label_.set_markup(fmt::format(format_, fmt::arg("status", status_text), + label_.set_markup(fmt::format(fmt::runtime(format_), fmt::arg("status", status_text), fmt::arg("icon", getIcon(0, status_text)))); label_.get_style_context()->add_class(status_text); diff --git a/src/modules/jack.cpp b/src/modules/jack.cpp index 3a92110..9bd6fcd 100644 --- a/src/modules/jack.cpp +++ b/src/modules/jack.cpp @@ -72,7 +72,7 @@ auto JACK::update() -> void { } else format = "{load}%"; - label_.set_markup(fmt::format(format, fmt::arg("load", std::round(load_)), + label_.set_markup(fmt::format(fmt::runtime(format), fmt::arg("load", std::round(load_)), fmt::arg("bufsize", bufsize_), fmt::arg("samplerate", samplerate_), fmt::arg("latency", fmt::format("{:.2f}", latency)), fmt::arg("xruns", xruns_))); @@ -81,9 +81,9 @@ auto JACK::update() -> void { std::string tooltip_format = "{bufsize}/{samplerate} {latency}ms"; if (config_["tooltip-format"].isString()) tooltip_format = config_["tooltip-format"].asString(); label_.set_tooltip_text(fmt::format( - tooltip_format, fmt::arg("load", std::round(load_)), fmt::arg("bufsize", bufsize_), - fmt::arg("samplerate", samplerate_), fmt::arg("latency", fmt::format("{:.2f}", latency)), - fmt::arg("xruns", xruns_))); + fmt::runtime(tooltip_format), fmt::arg("load", std::round(load_)), + fmt::arg("bufsize", bufsize_), fmt::arg("samplerate", samplerate_), + fmt::arg("latency", fmt::format("{:.2f}", latency)), fmt::arg("xruns", xruns_))); } // Call parent update diff --git a/src/modules/keyboard_state.cpp b/src/modules/keyboard_state.cpp index b2750b6..4c081d6 100644 --- a/src/modules/keyboard_state.cpp +++ b/src/modules/keyboard_state.cpp @@ -278,7 +278,7 @@ auto waybar::modules::KeyboardState::update() -> void { }; for (auto& label_state : label_states) { std::string text; - text = fmt::format(label_state.format, + text = fmt::format(fmt::runtime(label_state.format), fmt::arg("icon", label_state.state ? icon_locked_ : icon_unlocked_), fmt::arg("name", label_state.name)); label_state.label.set_markup(text); diff --git a/src/modules/memory/common.cpp b/src/modules/memory/common.cpp index 4a0e634..544d781 100644 --- a/src/modules/memory/common.cpp +++ b/src/modules/memory/common.cpp @@ -56,7 +56,8 @@ auto waybar::modules::Memory::update() -> void { event_box_.show(); auto icons = std::vector{state}; label_.set_markup(fmt::format( - format, used_ram_percentage, fmt::arg("icon", getIcon(used_ram_percentage, icons)), + fmt::runtime(format), used_ram_percentage, + fmt::arg("icon", getIcon(used_ram_percentage, icons)), fmt::arg("total", total_ram_gigabytes), fmt::arg("swapTotal", total_swap_gigabytes), fmt::arg("percentage", used_ram_percentage), fmt::arg("swapPercentage", used_swap_percentage), fmt::arg("used", used_ram_gigabytes), @@ -68,8 +69,8 @@ auto waybar::modules::Memory::update() -> void { if (config_["tooltip-format"].isString()) { auto tooltip_format = config_["tooltip-format"].asString(); label_.set_tooltip_text(fmt::format( - tooltip_format, used_ram_percentage, fmt::arg("total", total_ram_gigabytes), - fmt::arg("swapTotal", total_swap_gigabytes), + fmt::runtime(tooltip_format), used_ram_percentage, + fmt::arg("total", total_ram_gigabytes), fmt::arg("swapTotal", total_swap_gigabytes), fmt::arg("percentage", used_ram_percentage), fmt::arg("swapPercentage", used_swap_percentage), fmt::arg("used", used_ram_gigabytes), fmt::arg("swapUsed", used_swap_gigabytes), fmt::arg("avail", available_ram_gigabytes), diff --git a/src/modules/mpd/mpd.cpp b/src/modules/mpd/mpd.cpp index 401b759..e728897 100644 --- a/src/modules/mpd/mpd.cpp +++ b/src/modules/mpd/mpd.cpp @@ -174,14 +174,14 @@ void waybar::modules::MPD::setLabel() { try { auto text = fmt::format( - format, fmt::arg("artist", artist.raw()), fmt::arg("albumArtist", album_artist.raw()), - fmt::arg("album", album.raw()), fmt::arg("title", title.raw()), fmt::arg("date", date), - fmt::arg("volume", volume), fmt::arg("elapsedTime", elapsedTime), - fmt::arg("totalTime", totalTime), fmt::arg("songPosition", song_pos), - fmt::arg("queueLength", queue_length), fmt::arg("stateIcon", stateIcon), - fmt::arg("consumeIcon", consumeIcon), fmt::arg("randomIcon", randomIcon), - fmt::arg("repeatIcon", repeatIcon), fmt::arg("singleIcon", singleIcon), - fmt::arg("filename", filename)); + fmt::runtime(format), fmt::arg("artist", artist.raw()), + fmt::arg("albumArtist", album_artist.raw()), fmt::arg("album", album.raw()), + fmt::arg("title", title.raw()), fmt::arg("date", date), fmt::arg("volume", volume), + fmt::arg("elapsedTime", elapsedTime), fmt::arg("totalTime", totalTime), + fmt::arg("songPosition", song_pos), fmt::arg("queueLength", queue_length), + fmt::arg("stateIcon", stateIcon), fmt::arg("consumeIcon", consumeIcon), + fmt::arg("randomIcon", randomIcon), fmt::arg("repeatIcon", repeatIcon), + fmt::arg("singleIcon", singleIcon), fmt::arg("filename", filename)); if (text.empty()) { label_.hide(); } else { @@ -198,7 +198,7 @@ void waybar::modules::MPD::setLabel() { : "MPD (connected)"; try { auto tooltip_text = - fmt::format(tooltip_format, fmt::arg("artist", artist.raw()), + fmt::format(fmt::runtime(tooltip_format), fmt::arg("artist", artist.raw()), fmt::arg("albumArtist", album_artist.raw()), fmt::arg("album", album.raw()), fmt::arg("title", title.raw()), fmt::arg("date", date), fmt::arg("volume", volume), fmt::arg("elapsedTime", elapsedTime), diff --git a/src/modules/mpris/mpris.cpp b/src/modules/mpris/mpris.cpp index 651dfd5..f11821f 100644 --- a/src/modules/mpris/mpris.cpp +++ b/src/modules/mpris/mpris.cpp @@ -378,10 +378,10 @@ auto Mpris::update() -> void { break; } auto label_format = - fmt::format(formatstr, fmt::arg("player", info.name), fmt::arg("status", info.status_string), - fmt::arg("artist", *info.artist), fmt::arg("title", *info.title), - fmt::arg("album", *info.album), fmt::arg("length", *info.length), - fmt::arg("dynamic", dynamic.str()), + fmt::format(fmt::runtime(formatstr), fmt::arg("player", info.name), + fmt::arg("status", info.status_string), fmt::arg("artist", *info.artist), + fmt::arg("title", *info.title), fmt::arg("album", *info.album), + fmt::arg("length", *info.length), fmt::arg("dynamic", dynamic.str()), fmt::arg("player_icon", getIcon(config_["player-icons"], info.name)), fmt::arg("status_icon", getIcon(config_["status-icons"], info.status_string))); label_.set_markup(label_format); diff --git a/src/modules/network.cpp b/src/modules/network.cpp index a4797ee..8409311 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -331,7 +331,7 @@ auto waybar::modules::Network::update() -> void { getState(signal_strength_); auto text = fmt::format( - format_, fmt::arg("essid", essid_), fmt::arg("signaldBm", signal_strength_dbm_), + fmt::runtime(format_), fmt::arg("essid", essid_), fmt::arg("signaldBm", signal_strength_dbm_), fmt::arg("signalStrength", signal_strength_), fmt::arg("signalStrengthApp", signal_strength_app_), fmt::arg("ifname", ifname_), fmt::arg("netmask", netmask_), fmt::arg("ipaddr", ipaddr_), fmt::arg("gwaddr", gwaddr_), @@ -363,8 +363,8 @@ auto waybar::modules::Network::update() -> void { } if (!tooltip_format.empty()) { auto tooltip_text = fmt::format( - tooltip_format, fmt::arg("essid", essid_), fmt::arg("signaldBm", signal_strength_dbm_), - fmt::arg("signalStrength", signal_strength_), + fmt::runtime(tooltip_format), fmt::arg("essid", essid_), + fmt::arg("signaldBm", signal_strength_dbm_), fmt::arg("signalStrength", signal_strength_), fmt::arg("signalStrengthApp", signal_strength_app_), fmt::arg("ifname", ifname_), fmt::arg("netmask", netmask_), fmt::arg("ipaddr", ipaddr_), fmt::arg("gwaddr", gwaddr_), fmt::arg("cidr", cidr_), fmt::arg("frequency", fmt::format("{:.1f}", frequency_)), diff --git a/src/modules/pulseaudio.cpp b/src/modules/pulseaudio.cpp index c797997..0630710 100644 --- a/src/modules/pulseaudio.cpp +++ b/src/modules/pulseaudio.cpp @@ -294,9 +294,9 @@ auto waybar::modules::Pulseaudio::update() -> void { format_source = config_["format-source"].asString(); } } - format_source = fmt::format(format_source, fmt::arg("volume", source_volume_)); + format_source = fmt::format(fmt::runtime(format_source), fmt::arg("volume", source_volume_)); auto text = fmt::format( - format, fmt::arg("desc", desc_), fmt::arg("volume", volume_), + fmt::runtime(format), fmt::arg("desc", desc_), fmt::arg("volume", volume_), fmt::arg("format_source", format_source), fmt::arg("source_volume", source_volume_), fmt::arg("source_desc", source_desc_), fmt::arg("icon", getIcon(volume_, getPulseIcon()))); if (text.empty()) { @@ -313,7 +313,7 @@ auto waybar::modules::Pulseaudio::update() -> void { } if (!tooltip_format.empty()) { label_.set_tooltip_text(fmt::format( - tooltip_format, fmt::arg("desc", desc_), fmt::arg("volume", volume_), + fmt::runtime(tooltip_format), fmt::arg("desc", desc_), fmt::arg("volume", volume_), fmt::arg("format_source", format_source), fmt::arg("source_volume", source_volume_), fmt::arg("source_desc", source_desc_), fmt::arg("icon", getIcon(volume_, getPulseIcon())))); diff --git a/src/modules/river/mode.cpp b/src/modules/river/mode.cpp index 4a51c83..1f788e0 100644 --- a/src/modules/river/mode.cpp +++ b/src/modules/river/mode.cpp @@ -103,7 +103,7 @@ void Mode::handle_mode(const char *mode) { } label_.get_style_context()->add_class(mode); - label_.set_markup(fmt::format(format_, Glib::Markup::escape_text(mode).raw())); + label_.set_markup(fmt::format(fmt::runtime(format_), Glib::Markup::escape_text(mode).raw())); label_.show(); } diff --git a/src/modules/river/window.cpp b/src/modules/river/window.cpp index d0f492f..d93938c 100644 --- a/src/modules/river/window.cpp +++ b/src/modules/river/window.cpp @@ -106,7 +106,7 @@ void Window::handle_focused_view(const char *title) { label_.hide(); // hide empty labels or labels with empty format } else { label_.show(); - label_.set_markup(fmt::format(format_, Glib::Markup::escape_text(title).raw())); + label_.set_markup(fmt::format(fmt::runtime(format_), Glib::Markup::escape_text(title).raw())); } ALabel::update(); diff --git a/src/modules/sndio.cpp b/src/modules/sndio.cpp index e6f1bd0..72e7207 100644 --- a/src/modules/sndio.cpp +++ b/src/modules/sndio.cpp @@ -110,7 +110,8 @@ auto Sndio::update() -> void { label_.get_style_context()->remove_class("muted"); } - auto text = fmt::format(format, fmt::arg("volume", vol), fmt::arg("raw_value", volume_)); + auto text = + fmt::format(fmt::runtime(format), fmt::arg("volume", vol), fmt::arg("raw_value", volume_)); if (text.empty()) { label_.hide(); } else { diff --git a/src/modules/sway/ipc/client.cpp b/src/modules/sway/ipc/client.cpp index 4d6495c..5c3df7b 100644 --- a/src/modules/sway/ipc/client.cpp +++ b/src/modules/sway/ipc/client.cpp @@ -2,6 +2,8 @@ #include +#include + namespace waybar::modules::sway { Ipc::Ipc() { diff --git a/src/modules/sway/language.cpp b/src/modules/sway/language.cpp index d3730a1..a5860bd 100644 --- a/src/modules/sway/language.cpp +++ b/src/modules/sway/language.cpp @@ -96,14 +96,14 @@ void Language::onEvent(const struct Ipc::ipc_response& res) { auto Language::update() -> void { std::lock_guard lock(mutex_); auto display_layout = trim(fmt::format( - format_, fmt::arg("short", layout_.short_name), + fmt::runtime(format_), fmt::arg("short", layout_.short_name), fmt::arg("shortDescription", layout_.short_description), fmt::arg("long", layout_.full_name), fmt::arg("variant", layout_.variant), fmt::arg("flag", layout_.country_flag()))); label_.set_markup(display_layout); if (tooltipEnabled()) { if (tooltip_format_ != "") { auto tooltip_display_layout = trim( - fmt::format(tooltip_format_, fmt::arg("short", layout_.short_name), + fmt::format(fmt::runtime(tooltip_format_), fmt::arg("short", layout_.short_name), fmt::arg("shortDescription", layout_.short_description), fmt::arg("long", layout_.full_name), fmt::arg("variant", layout_.variant), fmt::arg("flag", layout_.country_flag()))); diff --git a/src/modules/sway/mode.cpp b/src/modules/sway/mode.cpp index 7eaa523..b81735e 100644 --- a/src/modules/sway/mode.cpp +++ b/src/modules/sway/mode.cpp @@ -42,7 +42,7 @@ auto Mode::update() -> void { if (mode_.empty()) { event_box_.hide(); } else { - label_.set_markup(fmt::format(format_, mode_)); + label_.set_markup(fmt::format(fmt::runtime(format_), mode_)); if (tooltipEnabled()) { label_.set_tooltip_text(mode_); } diff --git a/src/modules/sway/scratchpad.cpp b/src/modules/sway/scratchpad.cpp index 59e3053..17dc270 100644 --- a/src/modules/sway/scratchpad.cpp +++ b/src/modules/sway/scratchpad.cpp @@ -32,7 +32,8 @@ auto Scratchpad::update() -> void { if (count_ || show_empty_) { event_box_.show(); label_.set_markup( - fmt::format(format_, fmt::arg("icon", getIcon(count_, "", config_["format-icons"].size())), + fmt::format(fmt::runtime(format_), + fmt::arg("icon", getIcon(count_, "", config_["format-icons"].size())), fmt::arg("count", count_))); if (tooltip_enabled_) { label_.set_tooltip_markup(tooltip_text_); @@ -64,7 +65,7 @@ auto Scratchpad::onCmd(const struct Ipc::ipc_response& res) -> void { if (tooltip_enabled_) { tooltip_text_.clear(); for (const auto& window : tree["nodes"][0]["nodes"][0]["floating_nodes"]) { - tooltip_text_.append(fmt::format(tooltip_format_ + '\n', + tooltip_text_.append(fmt::format(fmt::runtime(tooltip_format_ + '\n'), fmt::arg("app", window["app_id"].asString()), fmt::arg("title", window["name"].asString()))); } diff --git a/src/modules/sway/window.cpp b/src/modules/sway/window.cpp index 0e74b76..7d60d29 100644 --- a/src/modules/sway/window.cpp +++ b/src/modules/sway/window.cpp @@ -204,9 +204,10 @@ auto Window::update() -> void { old_app_id_ = app_id_; } - label_.set_markup(fmt::format( - format_, fmt::arg("title", waybar::util::rewriteTitle(window_, config_["rewrite"])), - fmt::arg("app_id", app_id_), fmt::arg("shell", shell_))); + label_.set_markup( + fmt::format(fmt::runtime(format_), + fmt::arg("title", waybar::util::rewriteTitle(window_, config_["rewrite"])), + fmt::arg("app_id", app_id_), fmt::arg("shell", shell_))); if (tooltipEnabled()) { label_.set_tooltip_text(window_); } diff --git a/src/modules/sway/workspaces.cpp b/src/modules/sway/workspaces.cpp index b621b83..08742ae 100644 --- a/src/modules/sway/workspaces.cpp +++ b/src/modules/sway/workspaces.cpp @@ -233,7 +233,7 @@ auto Workspaces::update() -> void { std::string output = (*it)["name"].asString(); if (config_["format"].isString()) { auto format = config_["format"].asString(); - output = fmt::format(format, fmt::arg("icon", getIcon(output, *it)), + output = fmt::format(fmt::runtime(format), fmt::arg("icon", getIcon(output, *it)), fmt::arg("value", output), fmt::arg("name", trimWorkspaceName(output)), fmt::arg("index", (*it)["num"].asString())); } @@ -259,11 +259,9 @@ Gtk::Button &Workspaces::addButton(const Json::Value &node) { try { if (node["target_output"].isString()) { ipc_.sendCmd(IPC_COMMAND, - fmt::format(workspace_switch_cmd_ + "; move workspace to output \"{}\"; " + - workspace_switch_cmd_, - "--no-auto-back-and-forth", node["name"].asString(), - node["target_output"].asString(), "--no-auto-back-and-forth", - node["name"].asString())); + fmt::format(persistent_workspace_switch_cmd_, "--no-auto-back-and-forth", + node["name"].asString(), node["target_output"].asString(), + "--no-auto-back-and-forth", node["name"].asString())); } else { ipc_.sendCmd(IPC_COMMAND, fmt::format("workspace {} \"{}\"", config_["disable-auto-back-and-forth"].asBool() diff --git a/src/modules/temperature.cpp b/src/modules/temperature.cpp index eca05a7..ff722d7 100644 --- a/src/modules/temperature.cpp +++ b/src/modules/temperature.cpp @@ -55,7 +55,7 @@ auto waybar::modules::Temperature::update() -> void { } auto max_temp = config_["critical-threshold"].isInt() ? config_["critical-threshold"].asInt() : 0; - label_.set_markup(fmt::format(format, fmt::arg("temperatureC", temperature_c), + label_.set_markup(fmt::format(fmt::runtime(format), fmt::arg("temperatureC", temperature_c), fmt::arg("temperatureF", temperature_f), fmt::arg("temperatureK", temperature_k), fmt::arg("icon", getIcon(temperature_c, "", max_temp)))); @@ -64,9 +64,9 @@ auto waybar::modules::Temperature::update() -> void { if (config_["tooltip-format"].isString()) { tooltip_format = config_["tooltip-format"].asString(); } - label_.set_tooltip_text(fmt::format(tooltip_format, fmt::arg("temperatureC", temperature_c), - fmt::arg("temperatureF", temperature_f), - fmt::arg("temperatureK", temperature_k))); + label_.set_tooltip_text(fmt::format( + fmt::runtime(tooltip_format), fmt::arg("temperatureC", temperature_c), + fmt::arg("temperatureF", temperature_f), fmt::arg("temperatureK", temperature_k))); } // Call parent update ALabel::update(); diff --git a/src/modules/upower/upower.cpp b/src/modules/upower/upower.cpp index eb29913..38c1f7f 100644 --- a/src/modules/upower/upower.cpp +++ b/src/modules/upower/upower.cpp @@ -336,8 +336,8 @@ auto UPower::update() -> void { break; } std::string label_format = - fmt::format(showAltText ? format_alt : format, fmt::arg("percentage", percentString), - fmt::arg("time", time_format)); + fmt::format(fmt::runtime(showAltText ? format_alt : format), + fmt::arg("percentage", percentString), fmt::arg("time", time_format)); // Only set the label text if it doesn't only contain spaces bool onlySpaces = true; for (auto& character : label_format) { diff --git a/src/modules/user.cpp b/src/modules/user.cpp index 2f7c6e9..418fc58 100644 --- a/src/modules/user.cpp +++ b/src/modules/user.cpp @@ -127,16 +127,16 @@ auto User::update() -> void { auto startSystemTime = currentSystemTime - workSystemTimeSeconds; long workSystemDays = uptimeSeconds / 86400; - auto label = fmt::format(ALabel::format_, fmt::arg("up_H", fmt::format("{:%H}", startSystemTime)), - fmt::arg("up_M", fmt::format("{:%M}", startSystemTime)), - fmt::arg("up_d", fmt::format("{:%d}", startSystemTime)), - fmt::arg("up_m", fmt::format("{:%m}", startSystemTime)), - fmt::arg("up_Y", fmt::format("{:%Y}", startSystemTime)), - fmt::arg("work_d", workSystemDays), - fmt::arg("work_H", fmt::format("{:%H}", workSystemTimeSeconds)), - fmt::arg("work_M", fmt::format("{:%M}", workSystemTimeSeconds)), - fmt::arg("work_S", fmt::format("{:%S}", workSystemTimeSeconds)), - fmt::arg("user", systemUser)); + auto label = fmt::format( + fmt::runtime(ALabel::format_), fmt::arg("up_H", fmt::format("{:%H}", startSystemTime)), + fmt::arg("up_M", fmt::format("{:%M}", startSystemTime)), + fmt::arg("up_d", fmt::format("{:%d}", startSystemTime)), + fmt::arg("up_m", fmt::format("{:%m}", startSystemTime)), + fmt::arg("up_Y", fmt::format("{:%Y}", startSystemTime)), fmt::arg("work_d", workSystemDays), + fmt::arg("work_H", fmt::format("{:%H}", workSystemTimeSeconds)), + fmt::arg("work_M", fmt::format("{:%M}", workSystemTimeSeconds)), + fmt::arg("work_S", fmt::format("{:%S}", workSystemTimeSeconds)), + fmt::arg("user", systemUser)); ALabel::label_.set_markup(label); AIconLabel::update(); } diff --git a/src/modules/wireplumber.cpp b/src/modules/wireplumber.cpp index 9652e1e..fd1a0d3 100644 --- a/src/modules/wireplumber.cpp +++ b/src/modules/wireplumber.cpp @@ -279,7 +279,7 @@ auto waybar::modules::Wireplumber::update() -> void { label_.get_style_context()->remove_class("muted"); } - std::string markup = fmt::format(format, fmt::arg("node_name", node_name_), + std::string markup = fmt::format(fmt::runtime(format), fmt::arg("node_name", node_name_), fmt::arg("volume", volume_), fmt::arg("icon", getIcon(volume_))); label_.set_markup(markup); @@ -291,9 +291,9 @@ auto waybar::modules::Wireplumber::update() -> void { } if (!tooltip_format.empty()) { - label_.set_tooltip_text(fmt::format(tooltip_format, fmt::arg("node_name", node_name_), - fmt::arg("volume", volume_), - fmt::arg("icon", getIcon(volume_)))); + label_.set_tooltip_text( + fmt::format(fmt::runtime(tooltip_format), fmt::arg("node_name", node_name_), + fmt::arg("volume", volume_), fmt::arg("icon", getIcon(volume_)))); } else { label_.set_tooltip_text(node_name_); } diff --git a/src/modules/wlr/taskbar.cpp b/src/modules/wlr/taskbar.cpp index 5460244..427083b 100644 --- a/src/modules/wlr/taskbar.cpp +++ b/src/modules/wlr/taskbar.cpp @@ -618,9 +618,10 @@ void Task::update() { app_id = Glib::Markup::escape_text(app_id); } if (!format_before_.empty()) { - auto txt = fmt::format(format_before_, fmt::arg("title", title), fmt::arg("name", name), - fmt::arg("app_id", app_id), fmt::arg("state", state_string()), - fmt::arg("short_state", state_string(true))); + auto txt = + fmt::format(fmt::runtime(format_before_), fmt::arg("title", title), fmt::arg("name", name), + fmt::arg("app_id", app_id), fmt::arg("state", state_string()), + fmt::arg("short_state", state_string(true))); if (markup) text_before_.set_markup(txt); else @@ -628,9 +629,10 @@ void Task::update() { text_before_.show(); } if (!format_after_.empty()) { - auto txt = fmt::format(format_after_, fmt::arg("title", title), fmt::arg("name", name), - fmt::arg("app_id", app_id), fmt::arg("state", state_string()), - fmt::arg("short_state", state_string(true))); + auto txt = + fmt::format(fmt::runtime(format_after_), fmt::arg("title", title), fmt::arg("name", name), + fmt::arg("app_id", app_id), fmt::arg("state", state_string()), + fmt::arg("short_state", state_string(true))); if (markup) text_after_.set_markup(txt); else @@ -639,9 +641,10 @@ void Task::update() { } if (!format_tooltip_.empty()) { - auto txt = fmt::format(format_tooltip_, fmt::arg("title", title), fmt::arg("name", name), - fmt::arg("app_id", app_id), fmt::arg("state", state_string()), - fmt::arg("short_state", state_string(true))); + auto txt = + fmt::format(fmt::runtime(format_tooltip_), fmt::arg("title", title), fmt::arg("name", name), + fmt::arg("app_id", app_id), fmt::arg("state", state_string()), + fmt::arg("short_state", state_string(true))); if (markup) button_.set_tooltip_markup(txt); else diff --git a/src/modules/wlr/workspace_manager.cpp b/src/modules/wlr/workspace_manager.cpp index ade0269..c1b68c8 100644 --- a/src/modules/wlr/workspace_manager.cpp +++ b/src/modules/wlr/workspace_manager.cpp @@ -379,7 +379,7 @@ Workspace::~Workspace() { } auto Workspace::update() -> void { - label_.set_markup(fmt::format(format_, fmt::arg("name", name_), + label_.set_markup(fmt::format(fmt::runtime(format_), fmt::arg("name", name_), fmt::arg("icon", with_icon_ ? get_icon() : ""))); } diff --git a/test/SafeSignal.cpp b/test/SafeSignal.cpp index 7ff6f2a..f496d7a 100644 --- a/test/SafeSignal.cpp +++ b/test/SafeSignal.cpp @@ -2,7 +2,11 @@ #include -#include +#if __has_include() +#include +#else +#include +#endif #include #include diff --git a/test/config.cpp b/test/config.cpp index cdc96b0..3d0f007 100644 --- a/test/config.cpp +++ b/test/config.cpp @@ -1,6 +1,10 @@ #include "config.hpp" -#include +#if __has_include() +#include +#else +#include +#endif TEST_CASE("Load simple config", "[config]") { waybar::Config conf; diff --git a/test/date.cpp b/test/date.cpp new file mode 100644 index 0000000..aa6d79b --- /dev/null +++ b/test/date.cpp @@ -0,0 +1,162 @@ +#include "util/date.hpp" + +#include +#include +#include +#include +#include + +#if __has_include() +#include +#include +#else +#include +#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 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"); + } + } +} diff --git a/test/main.cpp b/test/main.cpp index 7970c26..daeee69 100644 --- a/test/main.cpp +++ b/test/main.cpp @@ -3,8 +3,13 @@ #include #include +#if __has_include() #include #include +#else +#include +#include +#endif #include int main(int argc, char* argv[]) { @@ -13,10 +18,16 @@ int main(int argc, char* argv[]) { session.applyCommandLine(argc, argv); const auto logger = spdlog::default_logger(); +#if CATCH_VERSION_MAJOR >= 3 for (const auto& spec : session.config().getReporterSpecs()) { - if (spec.name() == "tap") { + const auto& reporter_name = spec.name(); +#else + { + const auto& reporter_name = session.config().getReporterName(); +#endif + if (reporter_name == "tap") { spdlog::set_pattern("# [%l] %v"); - } else if (spec.name() == "compact") { + } else if (reporter_name == "compact") { logger->sinks().clear(); } else { logger->sinks().assign({std::make_shared()}); diff --git a/test/meson.build b/test/meson.build index b1e1123..02cbb2a 100644 --- a/test/meson.build +++ b/test/meson.build @@ -15,7 +15,7 @@ test_src = files( if tz_dep.found() test_dep += tz_dep - test_src += files('waybar_time.cpp') + test_src += files('date.cpp') endif waybar_test = executable( diff --git a/test/waybar_time.cpp b/test/waybar_time.cpp deleted file mode 100644 index 79469d4..0000000 --- a/test/waybar_time.cpp +++ /dev/null @@ -1,90 +0,0 @@ -#include "util/waybar_time.hpp" - -#include -#include - -#include -#include -#include - -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 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 - } - } -} From a9613892bb628525ddcc38d38104bb08ba8b5da5 Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 24 Jan 2023 09:33:38 +0100 Subject: [PATCH 29/29] Rename .envrc to .envrc.sample --- .envrc => .envrc.sample | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .envrc => .envrc.sample (100%) diff --git a/.envrc b/.envrc.sample similarity index 100% rename from .envrc rename to .envrc.sample