Merge branch 'Alexays:master' into hyprland/windowcount

This commit is contained in:
Khiet Tam Nguyen
2025-05-09 20:46:00 +10:00
committed by GitHub
62 changed files with 1138 additions and 595 deletions

View File

@ -7,14 +7,16 @@ concurrency:
cancel-in-progress: true cancel-in-progress: true
jobs: jobs:
build: lint:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: DoozyX/clang-format-lint-action@v0.16.2 # TODO: bump to clang 19 release
# - uses: DoozyX/clang-format-lint-action@v0.18.2
- uses: DoozyX/clang-format-lint-action@558090054b3f39e3d6af24f0cd73b319535da809
name: clang-format name: clang-format
with: with:
source: "." source: "."
extensions: "hpp,h,cpp,c" extensions: "hpp,h,cpp,c"
style: "file:.clang-format" style: "file:.clang-format"
clangFormatVersion: 18 clangFormatVersion: 19

View File

@ -7,7 +7,7 @@ concurrency:
cancel-in-progress: true cancel-in-progress: true
jobs: jobs:
build: lint:
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: container:
image: alexays/waybar:debian image: alexays/waybar:debian

View File

@ -7,7 +7,7 @@ concurrency:
cancel-in-progress: true cancel-in-progress: true
jobs: jobs:
clang: build:
# Run actions in a FreeBSD VM on the ubuntu runner # Run actions in a FreeBSD VM on the ubuntu runner
# https://github.com/actions/runner/issues/385 - for FreeBSD runner support # https://github.com/actions/runner/issues/385 - for FreeBSD runner support
runs-on: ubuntu-latest runs-on: ubuntu-latest

View File

@ -9,7 +9,7 @@
- River (Mapping mode, Tags, Focused window name) - River (Mapping mode, Tags, Focused window name)
- Hyprland (Window Icons, Workspaces, Focused window name) - Hyprland (Window Icons, Workspaces, Focused window name)
- Niri (Workspaces, Focused window name, Language) - Niri (Workspaces, Focused window name, Language)
- DWL (Tags, Focused window name) [requires dwl ipc patch](https://github.com/djpohly/dwl/wiki/ipc) - DWL (Tags, Focused window name) [requires dwl ipc patch](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/ipc)
- Tray [#21](https://github.com/Alexays/Waybar/issues/21) - Tray [#21](https://github.com/Alexays/Waybar/issues/21)
- Local time - Local time
- Battery - Battery

View File

@ -1,10 +1,9 @@
(import (import (
( let
let lock = builtins.fromJSON (builtins.readFile ./flake.lock); in lock = builtins.fromJSON (builtins.readFile ./flake.lock);
fetchTarball { in
url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz"; fetchTarball {
sha256 = lock.nodes.flake-compat.locked.narHash; url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz";
} sha256 = lock.nodes.flake-compat.locked.narHash;
) }
{ src = ./.; } ) { src = ./.; }).defaultNix
).defaultNix

6
flake.lock generated
View File

@ -18,11 +18,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1735471104, "lastModified": 1745391562,
"narHash": "sha256-0q9NGQySwDQc7RhAV2ukfnu7Gxa5/ybJ2ANT8DQrQrs=", "narHash": "sha256-sPwcCYuiEopaafePqlG826tBhctuJsLx/mhKKM5Fmjo=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "88195a94f390381c6afcdaa933c2f6ff93959cb4", "rev": "8a2f738d9d1f1d986b5a4cd2fd2061a7127237d7",
"type": "github" "type": "github"
}, },
"original": { "original": {

129
flake.nix
View File

@ -9,47 +9,96 @@
}; };
}; };
outputs = { self, nixpkgs, ... }: outputs =
{ self, nixpkgs, ... }:
let let
inherit (nixpkgs) lib; inherit (nixpkgs) lib;
genSystems = func: lib.genAttrs [ genSystems =
"x86_64-linux" func:
"aarch64-linux" lib.genAttrs
] [
(system: func (import nixpkgs { "x86_64-linux"
inherit system; "aarch64-linux"
overlays = with self.overlays; [ ]
waybar (
]; system:
})); func (
import nixpkgs {
inherit system;
overlays = with self.overlays; [
waybar
];
}
)
);
mkDate = longDate: (lib.concatStringsSep "-" [ mkDate =
(builtins.substring 0 4 longDate) longDate:
(builtins.substring 4 2 longDate) (lib.concatStringsSep "-" [
(builtins.substring 6 2 longDate) (builtins.substring 0 4 longDate)
]); (builtins.substring 4 2 longDate)
(builtins.substring 6 2 longDate)
]);
in in
{ {
devShells = genSystems devShells = genSystems (pkgs: {
(pkgs: default = pkgs.mkShell {
{ name = "waybar-shell";
default =
pkgs.mkShell
{
name = "waybar-shell";
# inherit attributes from upstream nixpkgs derivation # inherit attributes from upstream nixpkgs derivation
inherit (pkgs.waybar) buildInputs depsBuildBuild depsBuildBuildPropagated depsBuildTarget inherit (pkgs.waybar)
depsBuildTargetPropagated depsHostHost depsHostHostPropagated depsTargetTarget buildInputs
depsTargetTargetPropagated propagatedBuildInputs propagatedNativeBuildInputs strictDeps; depsBuildBuild
depsBuildBuildPropagated
depsBuildTarget
depsBuildTargetPropagated
depsHostHost
depsHostHostPropagated
depsTargetTarget
depsTargetTargetPropagated
propagatedBuildInputs
propagatedNativeBuildInputs
strictDeps
;
# overrides for local development # overrides for local development
nativeBuildInputs = pkgs.waybar.nativeBuildInputs ++ (with pkgs; [ nativeBuildInputs =
clang-tools pkgs.waybar.nativeBuildInputs
gdb ++ (with pkgs; [
]); nixfmt-rfc-style
clang-tools
gdb
]);
};
});
formatter = genSystems (
pkgs:
pkgs.treefmt.withConfig {
settings = [
{
formatter = {
clang-format = {
options = [ "-i" ];
command = lib.getExe' pkgs.clang-tools "clang-format";
excludes = [ ];
includes = [
"*.c"
"*.cpp"
"*.h"
"*.hpp"
];
}; };
}); nixfmt = {
command = lib.getExe pkgs.nixfmt-rfc-style;
includes = [ "*.nix" ];
};
};
tree-root-file = ".git/index";
}
];
}
);
overlays = { overlays = {
default = self.overlays.waybar; default = self.overlays.waybar;
@ -58,11 +107,15 @@
waybar = prev.waybar; waybar = prev.waybar;
# take the first "version: '...'" from meson.build # take the first "version: '...'" from meson.build
version = version =
(builtins.head (builtins.split "'" (builtins.head (
(builtins.elemAt builtins.split "'" (
(builtins.split " version: '" (builtins.readFile ./meson.build)) builtins.elemAt (builtins.split " version: '" (builtins.readFile ./meson.build)) 2
2))) )
+ "+date=" + (mkDate (self.lastModifiedDate or "19700101")) + "_" + (self.shortRev or "dirty"); ))
+ "+date="
+ (mkDate (self.lastModifiedDate or "19700101"))
+ "_"
+ (self.shortRev or "dirty");
}; };
}; };
}; };

View File

@ -5,7 +5,16 @@
namespace cava { namespace cava {
extern "C" { extern "C" {
// Need sdl_glsl output feature to be enabled on libcava
#ifndef SDL_GLSL
#define SDL_GLSL
#endif
#include <cava/common.h> #include <cava/common.h>
#ifdef SDL_GLSL
#undef SDL_GLSL
#endif
} }
} // namespace cava } // namespace cava
@ -23,11 +32,11 @@ class Cava final : public ALabel {
util::SleeperThread thread_; util::SleeperThread thread_;
util::SleeperThread thread_fetch_input_; util::SleeperThread thread_fetch_input_;
struct cava::error_s error_ {}; // cava errors struct cava::error_s error_{}; // cava errors
struct cava::config_params prm_ {}; // cava parameters struct cava::config_params prm_{}; // cava parameters
struct cava::audio_raw audio_raw_ {}; // cava handled raw audio data(is based on audio_data) struct cava::audio_raw audio_raw_{}; // cava handled raw audio data(is based on audio_data)
struct cava::audio_data audio_data_ {}; // cava audio data struct cava::audio_data audio_data_{}; // cava audio data
struct cava::cava_plan* plan_; //{new cava_plan{}}; struct cava::cava_plan* plan_; //{new cava_plan{}};
// Cava API to read audio source // Cava API to read audio source
cava::ptr input_source_; cava::ptr input_source_;
// Delay to handle audio source // Delay to handle audio source
@ -44,7 +53,7 @@ class Cava final : public ALabel {
// Cava method // Cava method
void pause_resume(); void pause_resume();
// ModuleActionMap // ModuleActionMap
static inline std::map<const std::string, void (waybar::modules::Cava::*const)()> actionMap_{ static inline std::map<const std::string, void (waybar::modules::Cava::* const)()> actionMap_{
{"mode", &waybar::modules::Cava::pause_resume}}; {"mode", &waybar::modules::Cava::pause_resume}};
}; };
} // namespace waybar::modules } // namespace waybar::modules

View File

@ -51,8 +51,8 @@ class Clock final : public ALabel {
day cldBaseDay_{0}; // calendar Cached day. Is used when today is changing(midnight) day cldBaseDay_{0}; // calendar Cached day. Is used when today is changing(midnight)
std::string cldText_{""}; // calendar text to print std::string cldText_{""}; // calendar text to print
CldMode cldMode_{CldMode::MONTH}; CldMode cldMode_{CldMode::MONTH};
auto get_calendar(const year_month_day& today, const year_month_day& ymd, auto get_calendar(const year_month_day& today, const year_month_day& ymd, const time_zone* tz)
const time_zone* tz) -> const std::string; -> const std::string;
// get local time zone // get local time zone
auto local_zone() -> const time_zone*; auto local_zone() -> const time_zone*;
@ -79,7 +79,7 @@ class Clock final : public ALabel {
void tz_up(); void tz_up();
void tz_down(); void tz_down();
// Module Action Map // Module Action Map
static inline std::map<const std::string, void (waybar::modules::Clock::*const)()> actionMap_{ static inline std::map<const std::string, void (waybar::modules::Clock::* const)()> actionMap_{
{"mode", &waybar::modules::Clock::cldModeSwitch}, {"mode", &waybar::modules::Clock::cldModeSwitch},
{"shift_up", &waybar::modules::Clock::cldShift_up}, {"shift_up", &waybar::modules::Clock::cldShift_up},
{"shift_down", &waybar::modules::Clock::cldShift_down}, {"shift_down", &waybar::modules::Clock::cldShift_down},

View File

@ -2,9 +2,9 @@
#include <filesystem> #include <filesystem>
#include <list> #include <list>
#include <memory>
#include <mutex> #include <mutex>
#include <string> #include <string>
#include <thread>
#include <utility> #include <utility>
#include "util/json.hpp" #include "util/json.hpp"
@ -19,7 +19,9 @@ class EventHandler {
class IPC { class IPC {
public: public:
IPC() { startIPC(); } IPC();
~IPC();
static IPC& inst();
void registerForIPC(const std::string& ev, EventHandler* ev_handler); void registerForIPC(const std::string& ev, EventHandler* ev_handler);
void unregisterForIPC(EventHandler* handler); void unregisterForIPC(EventHandler* handler);
@ -32,14 +34,16 @@ class IPC {
static std::filesystem::path socketFolder_; static std::filesystem::path socketFolder_;
private: private:
void startIPC(); void socketListener();
void parseIPC(const std::string&); void parseIPC(const std::string&);
std::thread ipcThread_;
std::mutex callbackMutex_; std::mutex callbackMutex_;
util::JsonParser parser_; util::JsonParser parser_;
std::list<std::pair<std::string, EventHandler*>> callbacks_; std::list<std::pair<std::string, EventHandler*>> callbacks_;
int socketfd_; // the hyprland socket file descriptor
bool running_ = true;
}; };
inline std::unique_ptr<IPC> gIPC;
inline bool modulesReady = false; inline bool modulesReady = false;
}; // namespace waybar::modules::hyprland }; // namespace waybar::modules::hyprland

View File

@ -37,6 +37,8 @@ class Language : public waybar::ALabel, public EventHandler {
util::JsonParser parser_; util::JsonParser parser_;
Layout layout_; Layout layout_;
IPC& m_ipc;
}; };
} // namespace waybar::modules::hyprland } // namespace waybar::modules::hyprland

View File

@ -28,6 +28,8 @@ class Submap : public waybar::ALabel, public EventHandler {
std::string submap_; std::string submap_;
bool always_on_ = false; bool always_on_ = false;
std::string default_submap_ = "Default"; std::string default_submap_ = "Default";
IPC& m_ipc;
}; };
} // namespace waybar::modules::hyprland } // namespace waybar::modules::hyprland

View File

@ -60,6 +60,8 @@ class Window : public waybar::AAppIconLabel, public EventHandler {
bool swallowing_; bool swallowing_;
bool fullscreen_; bool fullscreen_;
bool focused_; bool focused_;
IPC& m_ipc;
}; };
} // namespace waybar::modules::hyprland } // namespace waybar::modules::hyprland

View File

@ -83,6 +83,7 @@ class Workspace {
Gtk::Button m_button; Gtk::Button m_button;
Gtk::Box m_content; Gtk::Box m_content;
Gtk::Label m_label; Gtk::Label m_label;
IPC& m_ipc;
}; };
} // namespace waybar::modules::hyprland } // namespace waybar::modules::hyprland

View File

@ -7,6 +7,7 @@
#include <cstdint> #include <cstdint>
#include <map> #include <map>
#include <memory> #include <memory>
#include <optional>
#include <regex> #include <regex>
#include <string> #include <string>
#include <vector> #include <vector>
@ -55,14 +56,14 @@ class Workspaces : public AModule, public EventHandler {
static Json::Value createMonitorWorkspaceData(std::string const& name, static Json::Value createMonitorWorkspaceData(std::string const& name,
std::string const& monitor); std::string const& monitor);
void removeWorkspace(std::string const& name); void removeWorkspace(std::string const& workspaceString);
void setUrgentWorkspace(std::string const& windowaddress); void setUrgentWorkspace(std::string const& windowaddress);
// Config // Config
void parseConfig(const Json::Value& config); void parseConfig(const Json::Value& config);
auto populateIconsMap(const Json::Value& formatIcons) -> void; auto populateIconsMap(const Json::Value& formatIcons) -> void;
static auto populateBoolConfig(const Json::Value& config, const std::string& key, static auto populateBoolConfig(const Json::Value& config, const std::string& key, bool& member)
bool& member) -> void; -> void;
auto populateSortByConfig(const Json::Value& config) -> void; auto populateSortByConfig(const Json::Value& config) -> void;
auto populateIgnoreWorkspacesConfig(const Json::Value& config) -> void; auto populateIgnoreWorkspacesConfig(const Json::Value& config) -> void;
auto populateFormatWindowSeparatorConfig(const Json::Value& config) -> void; auto populateFormatWindowSeparatorConfig(const Json::Value& config) -> void;
@ -74,10 +75,11 @@ class Workspaces : public AModule, public EventHandler {
void onWorkspaceActivated(std::string const& payload); void onWorkspaceActivated(std::string const& payload);
void onSpecialWorkspaceActivated(std::string const& payload); void onSpecialWorkspaceActivated(std::string const& payload);
void onWorkspaceDestroyed(std::string const& payload); void onWorkspaceDestroyed(std::string const& payload);
void onWorkspaceCreated(std::string const& workspaceName, void onWorkspaceCreated(std::string const& payload,
Json::Value const& clientsData = Json::Value::nullRef); Json::Value const& clientsData = Json::Value::nullRef);
void onWorkspaceMoved(std::string const& payload); void onWorkspaceMoved(std::string const& payload);
void onWorkspaceRenamed(std::string const& payload); void onWorkspaceRenamed(std::string const& payload);
static std::optional<int> parseWorkspaceId(std::string const& workspaceIdStr);
// monitor events // monitor events
void onMonitorFocused(std::string const& payload); void onMonitorFocused(std::string const& payload);
@ -93,11 +95,18 @@ class Workspaces : public AModule, public EventHandler {
int windowRewritePriorityFunction(std::string const& window_rule); int windowRewritePriorityFunction(std::string const& window_rule);
// event payload management
template <typename... Args>
static std::string makePayload(Args const&... args);
static std::pair<std::string, std::string> splitDoublePayload(std::string const& payload);
static std::tuple<std::string, std::string, std::string> splitTriplePayload(
std::string const& payload);
// Update methods // Update methods
void doUpdate(); void doUpdate();
void removeWorkspacesToRemove(); void removeWorkspacesToRemove();
void createWorkspacesToCreate(); void createWorkspacesToCreate();
static std::vector<std::string> getVisibleWorkspaces(); static std::vector<int> getVisibleWorkspaces();
void updateWorkspaceStates(); void updateWorkspaceStates();
bool updateWindowsToCreate(); bool updateWindowsToCreate();
@ -138,7 +147,7 @@ class Workspaces : public AModule, public EventHandler {
bool m_withIcon; bool m_withIcon;
uint64_t m_monitorId; uint64_t m_monitorId;
std::string m_activeWorkspaceName; int m_activeWorkspaceId;
std::string m_activeSpecialWorkspaceName; std::string m_activeSpecialWorkspaceName;
std::vector<std::unique_ptr<Workspace>> m_workspaces; std::vector<std::unique_ptr<Workspace>> m_workspaces;
std::vector<std::pair<Json::Value, Json::Value>> m_workspacesToCreate; std::vector<std::pair<Json::Value, Json::Value>> m_workspacesToCreate;
@ -150,6 +159,7 @@ class Workspaces : public AModule, public EventHandler {
std::mutex m_mutex; std::mutex m_mutex;
const Bar& m_bar; const Bar& m_bar;
Gtk::Box m_box; Gtk::Box m_box;
IPC& m_ipc;
}; };
} // namespace waybar::modules::hyprland } // namespace waybar::modules::hyprland

View File

@ -16,6 +16,8 @@
#include "util/rfkill.hpp" #include "util/rfkill.hpp"
#endif #endif
enum ip_addr_pref : uint8_t { IPV4, IPV6, IPV4_6 };
namespace waybar::modules { namespace waybar::modules {
class Network : public ALabel { class Network : public ALabel {
@ -50,6 +52,7 @@ class Network : public ALabel {
std::optional<std::pair<unsigned long long, unsigned long long>> readBandwidthUsage(); std::optional<std::pair<unsigned long long, unsigned long long>> readBandwidthUsage();
int ifid_; int ifid_;
ip_addr_pref addr_pref_;
struct sockaddr_nl nladdr_ = {0}; struct sockaddr_nl nladdr_ = {0};
struct nl_sock* sock_ = nullptr; struct nl_sock* sock_ = nullptr;
struct nl_sock* ev_sock_ = nullptr; struct nl_sock* ev_sock_ = nullptr;
@ -73,9 +76,12 @@ class Network : public ALabel {
bool carrier_; bool carrier_;
std::string ifname_; std::string ifname_;
std::string ipaddr_; std::string ipaddr_;
std::string ipaddr6_;
std::string gwaddr_; std::string gwaddr_;
std::string netmask_; std::string netmask_;
std::string netmask6_;
int cidr_; int cidr_;
int cidr6_;
int32_t signal_strength_dbm_; int32_t signal_strength_dbm_;
uint8_t signal_strength_; uint8_t signal_strength_;
std::string signal_strength_app_; std::string signal_strength_app_;

View File

@ -13,7 +13,7 @@ namespace waybar::modules::privacy {
class Privacy : public AModule { class Privacy : public AModule {
public: public:
Privacy(const std::string &, const Json::Value &, const std::string &pos); Privacy(const std::string &, const Json::Value &, Gtk::Orientation, const std::string &pos);
auto update() -> void override; auto update() -> void override;
void onPrivacyNodesChanged(); void onPrivacyNodesChanged();

View File

@ -17,8 +17,8 @@ namespace waybar::modules::privacy {
class PrivacyItem : public Gtk::Revealer { class PrivacyItem : public Gtk::Revealer {
public: public:
PrivacyItem(const Json::Value &config_, enum PrivacyNodeType privacy_type_, PrivacyItem(const Json::Value &config_, enum PrivacyNodeType privacy_type_,
std::list<PrivacyNodeInfo *> *nodes, const std::string &pos, const uint icon_size, std::list<PrivacyNodeInfo *> *nodes, Gtk::Orientation orientation,
const uint transition_duration); const std::string &pos, const uint icon_size, const uint transition_duration);
enum PrivacyNodeType privacy_type; enum PrivacyNodeType privacy_type;

View File

@ -0,0 +1,43 @@
#pragma once
#include <json/json.h>
#include <spdlog/spdlog.h>
#include <string>
#include <unordered_map>
class IconManager {
public:
static IconManager& instance() {
static IconManager instance;
return instance;
}
void setIconsConfig(const Json::Value& icons_config) {
if (icons_config.isObject()) {
for (const auto& key : icons_config.getMemberNames()) {
std::string app_name = key;
const Json::Value& icon_value = icons_config[key];
if (icon_value.isString()) {
std::string icon_path = icon_value.asString();
icons_map_[app_name] = icon_path;
}
}
} else {
spdlog::warn("Invalid icon config format.");
}
}
std::string getIconForApp(const std::string& app_name) const {
auto it = icons_map_.find(app_name);
if (it != icons_map_.end()) {
return it->second;
}
return "";
}
private:
IconManager() = default;
std::unordered_map<std::string, std::string> icons_map_;
};

View File

@ -62,6 +62,7 @@ class Item : public sigc::trackable {
void proxyReady(Glib::RefPtr<Gio::AsyncResult>& result); void proxyReady(Glib::RefPtr<Gio::AsyncResult>& result);
void setProperty(const Glib::ustring& name, Glib::VariantBase& value); void setProperty(const Glib::ustring& name, Glib::VariantBase& value);
void setStatus(const Glib::ustring& value); void setStatus(const Glib::ustring& value);
void setCustomIcon(const std::string& id);
void getUpdatedProperties(); void getUpdatedProperties();
void processUpdatedProperties(Glib::RefPtr<Gio::AsyncResult>& result); void processUpdatedProperties(Glib::RefPtr<Gio::AsyncResult>& result);
void onSignal(const Glib::ustring& sender_name, const Glib::ustring& signal_name, void onSignal(const Glib::ustring& sender_name, const Glib::ustring& signal_name,

View File

@ -18,7 +18,7 @@ class Wireplumber : public ALabel {
private: private:
void asyncLoadRequiredApiModules(); void asyncLoadRequiredApiModules();
void prepare(); void prepare(waybar::modules::Wireplumber* self);
void activatePlugins(); void activatePlugins();
static void updateVolume(waybar::modules::Wireplumber* self, uint32_t id); static void updateVolume(waybar::modules::Wireplumber* self, uint32_t id);
static void updateNodeName(waybar::modules::Wireplumber* self, uint32_t id); static void updateNodeName(waybar::modules::Wireplumber* self, uint32_t id);
@ -32,6 +32,8 @@ class Wireplumber : public ALabel {
bool handleScroll(GdkEventScroll* e) override; bool handleScroll(GdkEventScroll* e) override;
static std::list<waybar::modules::Wireplumber*> modules;
WpCore* wp_core_; WpCore* wp_core_;
GPtrArray* apis_; GPtrArray* apis_;
WpObjectManager* om_; WpObjectManager* om_;
@ -44,6 +46,7 @@ class Wireplumber : public ALabel {
double min_step_; double min_step_;
uint32_t node_id_{0}; uint32_t node_id_{0};
std::string node_name_; std::string node_name_;
gchar* type_;
}; };
} // namespace waybar::modules } // namespace waybar::modules

View File

@ -622,8 +622,8 @@ inline auto convertInto(std::string const &source, bool &target) -> ParserResult
} }
#ifdef CLARA_CONFIG_OPTIONAL_TYPE #ifdef CLARA_CONFIG_OPTIONAL_TYPE
template <typename T> template <typename T>
inline auto convertInto(std::string const &source, inline auto convertInto(std::string const &source, CLARA_CONFIG_OPTIONAL_TYPE<T> &target)
CLARA_CONFIG_OPTIONAL_TYPE<T> &target) -> ParserResult { -> ParserResult {
T temp; T temp;
auto result = convertInto(source, temp); auto result = convertInto(source, temp);
if (result) target = std::move(temp); if (result) target = std::move(temp);
@ -751,8 +751,8 @@ class ParserBase {
public: public:
virtual ~ParserBase() = default; virtual ~ParserBase() = default;
virtual auto validate() const -> Result { return Result::ok(); } virtual auto validate() const -> Result { return Result::ok(); }
virtual auto parse(std::string const &exeName, virtual auto parse(std::string const &exeName, TokenStream const &tokens) const
TokenStream const &tokens) const -> InternalParseResult = 0; -> InternalParseResult = 0;
virtual auto cardinality() const -> size_t { return 1; } virtual auto cardinality() const -> size_t { return 1; }
auto parse(Args const &args) const -> InternalParseResult { auto parse(Args const &args) const -> InternalParseResult {
@ -1098,8 +1098,8 @@ struct Parser : ParserBase {
using ParserBase::parse; using ParserBase::parse;
auto parse(std::string const &exeName, auto parse(std::string const &exeName, TokenStream const &tokens) const
TokenStream const &tokens) const -> InternalParseResult override { -> InternalParseResult override {
struct ParserInfo { struct ParserInfo {
ParserBase const *parser = nullptr; ParserBase const *parser = nullptr;
size_t count = 0; size_t count = 0;

View File

@ -40,7 +40,7 @@ The brightness can be controlled by dragging the slider across the bar or clicki
``` ```
"modules-right": [ "modules-right": [
"backlight-slider", "backlight/slider",
], ],
"backlight/slider": { "backlight/slider": {
"min": 0, "min": 0,

View File

@ -117,7 +117,7 @@ View all valid format options in *strftime(3)* or have a look https://en.cpprefe
:[ 3 :[ 3
:[ Relevant for *mode=year*. Count of months per row :[ Relevant for *mode=year*. Count of months per row
|[ *weeks-pos* |[ *weeks-pos*
:[ integer :[ string
:[ :[
:[ The position where week numbers should be displayed. Disabled when is empty. :[ The position where week numbers should be displayed. Disabled when is empty.
Possible values: left|right Possible values: left|right

View File

@ -59,9 +59,9 @@ Addressed by *hyprland/language*
``` ```
"hyprland/language": { "hyprland/language": {
"format": "Lang: {long}" "format": "Lang: {long}",
"format-en": "AMERICA, HELL YEAH!" "format-en": "AMERICA, HELL YEAH!",
"format-tr": "As bayrakları" "format-tr": "As bayrakları",
"keyboard-name": "at-translated-set-2-keyboard" "keyboard-name": "at-translated-set-2-keyboard"
} }
``` ```

View File

@ -24,7 +24,7 @@ Addressed by *network*
*family*: ++ *family*: ++
typeof: string ++ typeof: string ++
default: *ipv4* ++ default: *ipv4* ++
The address family that is used for the format replacement {ipaddr} and to determine if a network connection is present. The address family that is used for the format replacement {ipaddr} and to determine if a network connection is present. Set it to ipv4_6 to display both.
*format*: ++ *format*: ++
typeof: string ++ typeof: string ++
@ -155,9 +155,13 @@ Addressed by *network*
*{gwaddr}*: The default gateway for the interface *{gwaddr}*: The default gateway for the interface
*{netmask}*: The subnetmask corresponding to the IP. *{netmask}*: The subnetmask corresponding to the IP(V4).
*{cidr}*: The subnetmask corresponding to the IP in CIDR notation. *{netmask6}*: The subnetmask corresponding to the IP(V6).
*{cidr}*: The subnetmask corresponding to the IP(V4) in CIDR notation.
*{cidr6}*: The subnetmask corresponding to the IP(V6) in CIDR notation.
*{essid}*: Name (SSID) of the wireless network. *{essid}*: Name (SSID) of the wireless network.

View File

@ -47,7 +47,11 @@ Addressed by *tray*
``` ```
"tray": { "tray": {
"icon-size": 21, "icon-size": 21,
"spacing": 10 "spacing": 10,
"icons": {
"blueman": "bluetooth",
"TelegramDesktop": "$HOME/.local/share/icons/hicolor/16x16/apps/telegram.png"
}
} }
``` ```

View File

@ -19,6 +19,11 @@ The *wireplumber* module displays the current volume reported by WirePlumber.
typeof: string ++ typeof: string ++
This format is used when the sound is muted. This format is used when the sound is muted.
*node-type*: ++
typeof: string ++
default: *Audio/Sink* ++
The WirePlumber node type to attach to. Use *Audio/Source* to manage microphones etc.
*tooltip*: ++ *tooltip*: ++
typeof: bool ++ typeof: bool ++
default: *true* ++ default: *true* ++
@ -108,6 +113,8 @@ The *wireplumber* module displays the current volume reported by WirePlumber.
# EXAMPLES # EXAMPLES
## Basic:
``` ```
"wireplumber": { "wireplumber": {
"format": "{volume}%", "format": "{volume}%",
@ -116,6 +123,26 @@ The *wireplumber* module displays the current volume reported by WirePlumber.
} }
``` ```
## Separate Sink and Source Widgets
```
"wireplumber#sink": {
"format": "{volume}% {icon}",
"format-muted": "",
"format-icons": ["", "", ""],
"on-click": "helvum",
"on-click-right": "wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle",
"scroll-step": 5
},
"wireplumber#source": {
"node-type": "Audio/Source",
"format": "{volume}% ",
"format-muted": "",
"on-click-right": "wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle",
"scroll-step": 5
}
```
# STYLE # STYLE
- *#wireplumber* - *#wireplumber*

View File

@ -1,6 +1,6 @@
project( project(
'waybar', 'cpp', 'c', 'waybar', 'cpp', 'c',
version: '0.11.0', version: '0.12.0',
license: 'MIT', license: 'MIT',
meson_version: '>= 0.59.0', meson_version: '>= 0.59.0',
default_options : [ default_options : [
@ -334,6 +334,10 @@ if get_option('niri')
) )
endif endif
if get_option('login-proxy')
add_project_arguments('-DHAVE_LOGIN_PROXY', language: 'cpp')
endif
if libnl.found() and libnlgen.found() if libnl.found() and libnlgen.found()
add_project_arguments('-DHAVE_LIBNL', language: 'cpp') add_project_arguments('-DHAVE_LIBNL', language: 'cpp')
src_files += files('src/modules/network.cpp') src_files += files('src/modules/network.cpp')
@ -483,7 +487,7 @@ if get_option('experimental')
endif endif
cava = dependency('cava', cava = dependency('cava',
version : '>=0.10.3', version : '>=0.10.4',
required: get_option('cava'), required: get_option('cava'),
fallback : ['cava', 'cava_dep'], fallback : ['cava', 'cava_dep'],
not_found_message: 'cava is not found. Building waybar without cava') not_found_message: 'cava is not found. Building waybar without cava')

View File

@ -20,3 +20,4 @@ option('jack', type: 'feature', value: 'auto', description: 'Enable support for
option('wireplumber', type: 'feature', value: 'auto', description: 'Enable support for WirePlumber') option('wireplumber', type: 'feature', value: 'auto', description: 'Enable support for WirePlumber')
option('cava', type: 'feature', value: 'auto', description: 'Enable support for Cava') option('cava', type: 'feature', value: 'auto', description: 'Enable support for Cava')
option('niri', type: 'boolean', description: 'Enable support for niri') option('niri', type: 'boolean', description: 'Enable support for niri')
option('login-proxy', type: 'boolean', description: 'Enable interfacing with dbus login interface')

View File

@ -1,42 +1,43 @@
{ lib {
, pkgs lib,
, waybar pkgs,
, version waybar,
version,
}: }:
let let
libcava = rec { libcava = rec {
version = "0.10.3"; version = "0.10.4";
src = pkgs.fetchFromGitHub { src = pkgs.fetchFromGitHub {
owner = "LukashonakV"; owner = "LukashonakV";
repo = "cava"; repo = "cava";
rev = version; tag = version;
hash = "sha256-ZDFbI69ECsUTjbhlw2kHRufZbQMu+FQSMmncCJ5pagg="; hash = "sha256-9eTDqM+O1tA/3bEfd1apm8LbEcR9CVgELTIspSVPMKM=";
}; };
}; };
in in
(waybar.overrideAttrs ( (waybar.overrideAttrs (oldAttrs: {
oldAttrs: { inherit version;
inherit version;
src = lib.cleanSourceWith { src = lib.cleanSourceWith {
filter = name: type: type != "regular" || !lib.hasSuffix ".nix" name; filter = name: type: type != "regular" || !lib.hasSuffix ".nix" name;
src = lib.cleanSource ../.; src = lib.cleanSource ../.;
}; };
mesonFlags = lib.remove "-Dgtk-layer-shell=enabled" oldAttrs.mesonFlags; mesonFlags = lib.remove "-Dgtk-layer-shell=enabled" oldAttrs.mesonFlags;
# downstream patch should not affect upstream # downstream patch should not affect upstream
patches = []; patches = [ ];
# nixpkgs checks version, no need when building locally
nativeInstallCheckInputs = [ ];
buildInputs = (builtins.filter (p: p.pname != "wireplumber") oldAttrs.buildInputs) ++ [ buildInputs = (builtins.filter (p: p.pname != "wireplumber") oldAttrs.buildInputs) ++ [
pkgs.wireplumber pkgs.wireplumber
]; ];
postUnpack = '' postUnpack = ''
pushd "$sourceRoot" pushd "$sourceRoot"
cp -R --no-preserve=mode,ownership ${libcava.src} subprojects/cava-${libcava.version} cp -R --no-preserve=mode,ownership ${libcava.src} subprojects/cava-${libcava.version}
patchShebangs . patchShebangs .
popd popd
''; '';
} }))
))

View File

@ -104,7 +104,11 @@
}, },
"tray": { "tray": {
// "icon-size": 21, // "icon-size": 21,
"spacing": 10 "spacing": 10,
// "icons": {
// "blueman": "bluetooth",
// "TelegramDesktop": "$HOME/.local/share/icons/hicolor/16x16/apps/telegram.png"
// }
}, },
"clock": { "clock": {
// "timezone": "America/New_York", // "timezone": "America/New_York",

View File

@ -17,7 +17,7 @@ void onclicked(GtkButton* button) {
} }
// You must // You must
const size_t wbcffi_version = 1; const size_t wbcffi_version = 2;
void* wbcffi_init(const wbcffi_init_info* init_info, const wbcffi_config_entry* config_entries, void* wbcffi_init(const wbcffi_init_info* init_info, const wbcffi_config_entry* config_entries,
size_t config_entries_len) { size_t config_entries_len) {
@ -67,4 +67,4 @@ void wbcffi_refresh(void* instance, int signal) {
void wbcffi_doaction(void* instance, const char* name) { void wbcffi_doaction(void* instance, const char* name) {
printf("cffi_example inst=%p: doAction(%s)\n", instance, name); printf("cffi_example inst=%p: doAction(%s)\n", instance, name);
} }

View File

@ -7,7 +7,7 @@
extern "C" { extern "C" {
#endif #endif
/// Waybar ABI version. 1 is the latest version /// Waybar ABI version. 2 is the latest version
extern const size_t wbcffi_version; extern const size_t wbcffi_version;
/// Private Waybar CFFI module /// Private Waybar CFFI module
@ -35,7 +35,13 @@ typedef struct {
typedef struct { typedef struct {
/// Entry key /// Entry key
const char* key; const char* key;
/// Entry value as string. JSON object and arrays are serialized. /// Entry value
///
/// In ABI version 1, this may be either a bare string if the value is a
/// string, or the JSON representation of any other JSON object as a string.
///
/// From ABI version 2 onwards, this is always the JSON representation of the
/// value as a string.
const char* value; const char* value;
} wbcffi_config_entry; } wbcffi_config_entry;

View File

@ -141,7 +141,7 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name,
#endif #endif
#ifdef HAVE_PIPEWIRE #ifdef HAVE_PIPEWIRE
if (ref == "privacy") { if (ref == "privacy") {
return new waybar::modules::privacy::Privacy(id, config_[name], pos); return new waybar::modules::privacy::Privacy(id, config_[name], bar_.orientation, pos);
} }
#endif #endif
#ifdef HAVE_MPRIS #ifdef HAVE_MPRIS

View File

@ -273,14 +273,18 @@ waybar::modules::Battery::getInfos() {
// Scale these by the voltage to get μW/μWh. // Scale these by the voltage to get μW/μWh.
uint32_t current_now = 0; uint32_t current_now = 0;
int32_t _current_now_int = 0;
bool current_now_exists = false; bool current_now_exists = false;
if (fs::exists(bat / "current_now")) { if (fs::exists(bat / "current_now")) {
current_now_exists = true; current_now_exists = true;
std::ifstream(bat / "current_now") >> current_now; std::ifstream(bat / "current_now") >> _current_now_int;
} else if (fs::exists(bat / "current_avg")) { } else if (fs::exists(bat / "current_avg")) {
current_now_exists = true; current_now_exists = true;
std::ifstream(bat / "current_avg") >> current_now; std::ifstream(bat / "current_avg") >> _current_now_int;
} }
// Documentation ABI allows a negative value when discharging, positive
// value when charging.
current_now = std::abs(_current_now_int);
if (fs::exists(bat / "time_to_empty_now")) { if (fs::exists(bat / "time_to_empty_now")) {
time_to_empty_now_exists = true; time_to_empty_now_exists = true;
@ -324,11 +328,15 @@ waybar::modules::Battery::getInfos() {
} }
uint32_t power_now = 0; uint32_t power_now = 0;
int32_t _power_now_int = 0;
bool power_now_exists = false; bool power_now_exists = false;
if (fs::exists(bat / "power_now")) { if (fs::exists(bat / "power_now")) {
power_now_exists = true; power_now_exists = true;
std::ifstream(bat / "power_now") >> power_now; std::ifstream(bat / "power_now") >> _power_now_int;
} }
// Some drivers (example: Qualcomm) exposes use a negative value when
// discharging, positive value when charging.
power_now = std::abs(_power_now_int);
uint32_t energy_now = 0; uint32_t energy_now = 0;
bool energy_now_exists = false; bool energy_now_exists = false;

View File

@ -49,8 +49,8 @@ auto getBoolProperty(GDBusProxy* proxy, const char* property_name) -> bool {
return false; return false;
} }
auto getOptionalStringProperty(GDBusProxy* proxy, auto getOptionalStringProperty(GDBusProxy* proxy, const char* property_name)
const char* property_name) -> std::optional<std::string> { -> std::optional<std::string> {
auto gvar = g_dbus_proxy_get_cached_property(proxy, property_name); auto gvar = g_dbus_proxy_get_cached_property(proxy, property_name);
if (gvar) { if (gvar) {
std::string property_value = g_variant_get_string(gvar, NULL); std::string property_value = g_variant_get_string(gvar, NULL);
@ -345,8 +345,8 @@ auto waybar::modules::Bluetooth::onInterfaceAddedOrRemoved(GDBusObjectManager* m
auto waybar::modules::Bluetooth::onInterfaceProxyPropertiesChanged( auto waybar::modules::Bluetooth::onInterfaceProxyPropertiesChanged(
GDBusObjectManagerClient* manager, GDBusObjectProxy* object_proxy, GDBusProxy* interface_proxy, GDBusObjectManagerClient* manager, GDBusObjectProxy* object_proxy, GDBusProxy* interface_proxy,
GVariant* changed_properties, const gchar* const* invalidated_properties, GVariant* changed_properties, const gchar* const* invalidated_properties, gpointer user_data)
gpointer user_data) -> void { -> void {
std::string interface_name = g_dbus_proxy_get_interface_name(interface_proxy); std::string interface_name = g_dbus_proxy_get_interface_name(interface_proxy);
std::string object_path = g_dbus_object_get_object_path(G_DBUS_OBJECT(object_proxy)); std::string object_path = g_dbus_object_get_object_path(G_DBUS_OBJECT(object_proxy));
@ -395,8 +395,8 @@ auto waybar::modules::Bluetooth::getDeviceBatteryPercentage(GDBusObject* object)
return std::nullopt; return std::nullopt;
} }
auto waybar::modules::Bluetooth::getDeviceProperties(GDBusObject* object, auto waybar::modules::Bluetooth::getDeviceProperties(GDBusObject* object, DeviceInfo& device_info)
DeviceInfo& device_info) -> bool { -> bool {
GDBusProxy* proxy_device = G_DBUS_PROXY(g_dbus_object_get_interface(object, "org.bluez.Device1")); GDBusProxy* proxy_device = G_DBUS_PROXY(g_dbus_object_get_interface(object, "org.bluez.Device1"));
if (proxy_device != NULL) { if (proxy_device != NULL) {
@ -462,8 +462,9 @@ auto waybar::modules::Bluetooth::findCurController() -> std::optional<Controller
return controller_info; return controller_info;
} }
auto waybar::modules::Bluetooth::findConnectedDevices( auto waybar::modules::Bluetooth::findConnectedDevices(const std::string& cur_controller_path,
const std::string& cur_controller_path, std::vector<DeviceInfo>& connected_devices) -> void { std::vector<DeviceInfo>& connected_devices)
-> void {
GList* objects = g_dbus_object_manager_get_objects(manager_.get()); GList* objects = g_dbus_object_manager_get_objects(manager_.get());
for (GList* l = objects; l != NULL; l = l->next) { for (GList* l = objects; l != NULL; l = l->next) {
GDBusObject* object = G_DBUS_OBJECT(l->data); GDBusObject* object = G_DBUS_OBJECT(l->data);

View File

@ -28,7 +28,7 @@ CFFI::CFFI(const std::string& name, const std::string& id, const Json::Value& co
} }
// Fetch functions // Fetch functions
if (*wbcffi_version == 1) { if (*wbcffi_version == 1 || *wbcffi_version == 2) {
// Mandatory functions // Mandatory functions
hooks_.init = reinterpret_cast<InitFn*>(dlsym(handle, "wbcffi_init")); hooks_.init = reinterpret_cast<InitFn*>(dlsym(handle, "wbcffi_init"));
if (!hooks_.init) { if (!hooks_.init) {
@ -58,10 +58,14 @@ CFFI::CFFI(const std::string& name, const std::string& id, const Json::Value& co
const auto& keys = config.getMemberNames(); const auto& keys = config.getMemberNames();
for (size_t i = 0; i < keys.size(); i++) { for (size_t i = 0; i < keys.size(); i++) {
const auto& value = config[keys[i]]; const auto& value = config[keys[i]];
if (value.isConvertibleTo(Json::ValueType::stringValue)) { if (*wbcffi_version == 1) {
config_entries_stringstor.push_back(config[keys[i]].asString()); if (value.isConvertibleTo(Json::ValueType::stringValue)) {
config_entries_stringstor.push_back(value.asString());
} else {
config_entries_stringstor.push_back(value.toStyledString());
}
} else { } else {
config_entries_stringstor.push_back(config[keys[i]].toStyledString()); config_entries_stringstor.push_back(value.toStyledString());
} }
} }

View File

@ -199,8 +199,8 @@ const unsigned cldRowsInMonth(const year_month& ym, const weekday& firstdow) {
return 2u + ceil<weeks>((weekday{ym / 1} - firstdow) + ((ym / last).day() - day{0})).count(); return 2u + ceil<weeks>((weekday{ym / 1} - firstdow) + ((ym / last).day() - day{0})).count();
} }
auto cldGetWeekForLine(const year_month& ym, const weekday& firstdow, auto cldGetWeekForLine(const year_month& ym, const weekday& firstdow, const unsigned line)
const unsigned line) -> const year_month_weekday { -> const year_month_weekday {
unsigned index{line - 2}; unsigned index{line - 2};
if (weekday{ym / 1} == firstdow) ++index; if (weekday{ym / 1} == firstdow) ++index;
return ym / firstdow[index]; return ym / firstdow[index];

View File

@ -98,13 +98,9 @@ Window::~Window() {
} }
} }
void Window::handle_title(const char *title) { void Window::handle_title(const char *title) { title_ = Glib::Markup::escape_text(title); }
title_ = Glib::Markup::escape_text(title);
}
void Window::handle_appid(const char *appid) { void Window::handle_appid(const char *appid) { appid_ = Glib::Markup::escape_text(appid); }
appid_ = Glib::Markup::escape_text(appid);
}
void Window::handle_layout_symbol(const char *layout_symbol) { void Window::handle_layout_symbol(const char *layout_symbol) {
layout_symbol_ = Glib::Markup::escape_text(layout_symbol); layout_symbol_ = Glib::Markup::escape_text(layout_symbol);

View File

@ -11,7 +11,6 @@
#include <filesystem> #include <filesystem>
#include <string> #include <string>
#include <thread>
namespace waybar::modules::hyprland { namespace waybar::modules::hyprland {
@ -44,71 +43,96 @@ std::filesystem::path IPC::getSocketFolder(const char* instanceSig) {
return socketFolder_; return socketFolder_;
} }
void IPC::startIPC() { IPC::IPC() {
// will start IPC and relay events to parseIPC // will start IPC and relay events to parseIPC
ipcThread_ = std::thread([this]() { socketListener(); });
}
std::thread([&]() { IPC::~IPC() {
// check for hyprland running_ = false;
const char* his = getenv("HYPRLAND_INSTANCE_SIGNATURE"); spdlog::info("Hyprland IPC stopping...");
if (socketfd_ != -1) {
if (his == nullptr) { spdlog::trace("Shutting down socket");
spdlog::warn("Hyprland is not running, Hyprland IPC will not be available."); if (shutdown(socketfd_, SHUT_RDWR) == -1) {
return; spdlog::error("Hyprland IPC: Couldn't shutdown socket");
} }
spdlog::trace("Closing socket");
if (!modulesReady) return; if (close(socketfd_) == -1) {
spdlog::error("Hyprland IPC: Couldn't close socket");
spdlog::info("Hyprland IPC starting");
struct sockaddr_un addr;
int socketfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (socketfd == -1) {
spdlog::error("Hyprland IPC: socketfd failed");
return;
} }
}
ipcThread_.join();
}
addr.sun_family = AF_UNIX; IPC& IPC::inst() {
static IPC ipc;
return ipc;
}
auto socketPath = IPC::getSocketFolder(his) / ".socket2.sock"; void IPC::socketListener() {
strncpy(addr.sun_path, socketPath.c_str(), sizeof(addr.sun_path) - 1); // check for hyprland
const char* his = getenv("HYPRLAND_INSTANCE_SIGNATURE");
addr.sun_path[sizeof(addr.sun_path) - 1] = 0; if (his == nullptr) {
spdlog::warn("Hyprland is not running, Hyprland IPC will not be available.");
return;
}
int l = sizeof(struct sockaddr_un); if (!modulesReady) return;
if (connect(socketfd, (struct sockaddr*)&addr, l) == -1) { spdlog::info("Hyprland IPC starting");
spdlog::error("Hyprland IPC: Unable to connect?");
return;
}
auto* file = fdopen(socketfd, "r"); struct sockaddr_un addr;
socketfd_ = socket(AF_UNIX, SOCK_STREAM, 0);
while (true) { if (socketfd_ == -1) {
std::array<char, 1024> buffer; // Hyprland socket2 events are max 1024 bytes spdlog::error("Hyprland IPC: socketfd failed");
return;
}
auto* receivedCharPtr = fgets(buffer.data(), buffer.size(), file); addr.sun_family = AF_UNIX;
if (receivedCharPtr == nullptr) { auto socketPath = IPC::getSocketFolder(his) / ".socket2.sock";
std::this_thread::sleep_for(std::chrono::milliseconds(1)); strncpy(addr.sun_path, socketPath.c_str(), sizeof(addr.sun_path) - 1);
continue;
}
std::string messageReceived(buffer.data()); addr.sun_path[sizeof(addr.sun_path) - 1] = 0;
messageReceived = messageReceived.substr(0, messageReceived.find_first_of('\n'));
spdlog::debug("hyprland IPC received {}", messageReceived);
try { int l = sizeof(struct sockaddr_un);
parseIPC(messageReceived);
} catch (std::exception& e) {
spdlog::warn("Failed to parse IPC message: {}, reason: {}", messageReceived, e.what());
} catch (...) {
throw;
}
if (connect(socketfd_, (struct sockaddr*)&addr, l) == -1) {
spdlog::error("Hyprland IPC: Unable to connect?");
return;
}
auto* file = fdopen(socketfd_, "r");
if (file == nullptr) {
spdlog::error("Hyprland IPC: Couldn't open file descriptor");
return;
}
while (running_) {
std::array<char, 1024> buffer; // Hyprland socket2 events are max 1024 bytes
auto* receivedCharPtr = fgets(buffer.data(), buffer.size(), file);
if (receivedCharPtr == nullptr) {
std::this_thread::sleep_for(std::chrono::milliseconds(1)); std::this_thread::sleep_for(std::chrono::milliseconds(1));
continue;
} }
}).detach();
std::string messageReceived(buffer.data());
messageReceived = messageReceived.substr(0, messageReceived.find_first_of('\n'));
spdlog::debug("hyprland IPC received {}", messageReceived);
try {
parseIPC(messageReceived);
} catch (std::exception& e) {
spdlog::warn("Failed to parse IPC message: {}, reason: {}", messageReceived, e.what());
} catch (...) {
throw;
}
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
spdlog::debug("Hyprland IPC stopped");
} }
void IPC::parseIPC(const std::string& ev) { void IPC::parseIPC(const std::string& ev) {

View File

@ -10,13 +10,9 @@
namespace waybar::modules::hyprland { namespace waybar::modules::hyprland {
Language::Language(const std::string& id, const Bar& bar, const Json::Value& config) Language::Language(const std::string& id, const Bar& bar, const Json::Value& config)
: ALabel(config, "language", id, "{}", 0, true), bar_(bar) { : ALabel(config, "language", id, "{}", 0, true), bar_(bar), m_ipc(IPC::inst()) {
modulesReady = true; modulesReady = true;
if (!gIPC) {
gIPC = std::make_unique<IPC>();
}
// get the active layout when open // get the active layout when open
initLanguage(); initLanguage();
@ -24,11 +20,11 @@ Language::Language(const std::string& id, const Bar& bar, const Json::Value& con
update(); update();
// register for hyprland ipc // register for hyprland ipc
gIPC->registerForIPC("activelayout", this); m_ipc.registerForIPC("activelayout", this);
} }
Language::~Language() { Language::~Language() {
gIPC->unregisterForIPC(this); m_ipc.unregisterForIPC(this);
// wait for possible event handler to finish // wait for possible event handler to finish
std::lock_guard<std::mutex> lg(mutex_); std::lock_guard<std::mutex> lg(mutex_);
} }
@ -85,7 +81,7 @@ void Language::onEvent(const std::string& ev) {
} }
void Language::initLanguage() { void Language::initLanguage() {
const auto inputDevices = gIPC->getSocket1Reply("devices"); const auto inputDevices = m_ipc.getSocket1Reply("devices");
const auto kbName = config_["keyboard-name"].asString(); const auto kbName = config_["keyboard-name"].asString();

View File

@ -7,15 +7,11 @@
namespace waybar::modules::hyprland { namespace waybar::modules::hyprland {
Submap::Submap(const std::string& id, const Bar& bar, const Json::Value& config) Submap::Submap(const std::string& id, const Bar& bar, const Json::Value& config)
: ALabel(config, "submap", id, "{}", 0, true), bar_(bar) { : ALabel(config, "submap", id, "{}", 0, true), bar_(bar), m_ipc(IPC::inst()) {
modulesReady = true; modulesReady = true;
parseConfig(config); parseConfig(config);
if (!gIPC) {
gIPC = std::make_unique<IPC>();
}
label_.hide(); label_.hide();
ALabel::update(); ALabel::update();
@ -27,12 +23,12 @@ Submap::Submap(const std::string& id, const Bar& bar, const Json::Value& config)
} }
// register for hyprland ipc // register for hyprland ipc
gIPC->registerForIPC("submap", this); m_ipc.registerForIPC("submap", this);
dp.emit(); dp.emit();
} }
Submap::~Submap() { Submap::~Submap() {
gIPC->unregisterForIPC(this); m_ipc.unregisterForIPC(this);
// wait for possible event handler to finish // wait for possible event handler to finish
std::lock_guard<std::mutex> lg(mutex_); std::lock_guard<std::mutex> lg(mutex_);
} }

View File

@ -6,37 +6,30 @@
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
#include <algorithm> #include <algorithm>
#include <shared_mutex>
#include <vector> #include <vector>
#include "modules/hyprland/backend.hpp" #include "modules/hyprland/backend.hpp"
#include "util/rewrite_string.hpp" #include "util/rewrite_string.hpp"
#include "util/sanitize_str.hpp" #include "util/sanitize_str.hpp"
#include <shared_mutex>
#include <thread>
namespace waybar::modules::hyprland { namespace waybar::modules::hyprland {
std::shared_mutex windowIpcSmtx; std::shared_mutex windowIpcSmtx;
Window::Window(const std::string& id, const Bar& bar, const Json::Value& config) Window::Window(const std::string& id, const Bar& bar, const Json::Value& config)
: AAppIconLabel(config, "window", id, "{title}", 0, true), bar_(bar) { : AAppIconLabel(config, "window", id, "{title}", 0, true), bar_(bar), m_ipc(IPC::inst()) {
std::unique_lock<std::shared_mutex> windowIpcUniqueLock(windowIpcSmtx); std::unique_lock<std::shared_mutex> windowIpcUniqueLock(windowIpcSmtx);
modulesReady = true; modulesReady = true;
separateOutputs_ = config["separate-outputs"].asBool(); separateOutputs_ = config["separate-outputs"].asBool();
if (!gIPC) {
gIPC = std::make_unique<IPC>();
}
// register for hyprland ipc // register for hyprland ipc
gIPC->registerForIPC("activewindow", this); m_ipc.registerForIPC("activewindow", this);
gIPC->registerForIPC("closewindow", this); m_ipc.registerForIPC("closewindow", this);
gIPC->registerForIPC("movewindow", this); m_ipc.registerForIPC("movewindow", this);
gIPC->registerForIPC("changefloatingmode", this); m_ipc.registerForIPC("changefloatingmode", this);
gIPC->registerForIPC("fullscreen", this); m_ipc.registerForIPC("fullscreen", this);
windowIpcUniqueLock.unlock(); windowIpcUniqueLock.unlock();
@ -47,11 +40,10 @@ Window::Window(const std::string& id, const Bar& bar, const Json::Value& config)
Window::~Window() { Window::~Window() {
std::unique_lock<std::shared_mutex> windowIpcUniqueLock(windowIpcSmtx); std::unique_lock<std::shared_mutex> windowIpcUniqueLock(windowIpcSmtx);
gIPC->unregisterForIPC(this); m_ipc.unregisterForIPC(this);
} }
auto Window::update() -> void { auto Window::update() -> void {
std::shared_lock<std::shared_mutex> windowIpcShareLock(windowIpcSmtx); std::shared_lock<std::shared_mutex> windowIpcShareLock(windowIpcSmtx);
std::string windowName = waybar::util::sanitize_string(workspace_.last_window_title); std::string windowName = waybar::util::sanitize_string(workspace_.last_window_title);
@ -59,18 +51,36 @@ auto Window::update() -> void {
windowData_.title = windowName; windowData_.title = windowName;
std::string label_text;
if (!format_.empty()) { if (!format_.empty()) {
label_.show(); label_.show();
label_.set_markup(waybar::util::rewriteString( label_text = waybar::util::rewriteString(
fmt::format(fmt::runtime(format_), fmt::arg("title", windowName), fmt::format(fmt::runtime(format_), fmt::arg("title", windowName),
fmt::arg("initialTitle", windowData_.initial_title), fmt::arg("initialTitle", windowData_.initial_title),
fmt::arg("class", windowData_.class_name), fmt::arg("class", windowData_.class_name),
fmt::arg("initialClass", windowData_.initial_class_name)), fmt::arg("initialClass", windowData_.initial_class_name)),
config_["rewrite"])); config_["rewrite"]);
label_.set_markup(label_text);
} else { } else {
label_.hide(); label_.hide();
} }
if (tooltipEnabled()) {
std::string tooltip_format;
if (config_["tooltip-format"].isString()) {
tooltip_format = config_["tooltip-format"].asString();
}
if (!tooltip_format.empty()) {
label_.set_tooltip_text(
fmt::format(fmt::runtime(tooltip_format), fmt::arg("title", windowName),
fmt::arg("initialTitle", windowData_.initial_title),
fmt::arg("class", windowData_.class_name),
fmt::arg("initialClass", windowData_.initial_class_name)));
} else if (!label_text.empty()) {
label_.set_tooltip_text(label_text);
}
}
if (focused_) { if (focused_) {
image_.show(); image_.show();
} else { } else {
@ -100,7 +110,7 @@ auto Window::update() -> void {
} }
auto Window::getActiveWorkspace() -> Workspace { auto Window::getActiveWorkspace() -> Workspace {
const auto workspace = gIPC->getSocket1JsonReply("activeworkspace"); const auto workspace = IPC::inst().getSocket1JsonReply("activeworkspace");
if (workspace.isObject()) { if (workspace.isObject()) {
return Workspace::parse(workspace); return Workspace::parse(workspace);
@ -110,24 +120,33 @@ auto Window::getActiveWorkspace() -> Workspace {
} }
auto Window::getActiveWorkspace(const std::string& monitorName) -> Workspace { auto Window::getActiveWorkspace(const std::string& monitorName) -> Workspace {
const auto monitors = gIPC->getSocket1JsonReply("monitors"); const auto monitors = IPC::inst().getSocket1JsonReply("monitors");
if (monitors.isArray()) { if (monitors.isArray()) {
auto monitor = std::find_if(monitors.begin(), monitors.end(), [&](Json::Value monitor) { auto monitor = std::ranges::find_if(
return monitor["name"] == monitorName; monitors, [&](Json::Value monitor) { return monitor["name"] == monitorName; });
});
if (monitor == std::end(monitors)) { if (monitor == std::end(monitors)) {
spdlog::warn("Monitor not found: {}", monitorName); spdlog::warn("Monitor not found: {}", monitorName);
return Workspace{-1, 0, "", ""}; return Workspace{
.id = -1,
.windows = 0,
.last_window = "",
.last_window_title = "",
};
} }
const int id = (*monitor)["activeWorkspace"]["id"].asInt(); const int id = (*monitor)["activeWorkspace"]["id"].asInt();
const auto workspaces = gIPC->getSocket1JsonReply("workspaces"); const auto workspaces = IPC::inst().getSocket1JsonReply("workspaces");
if (workspaces.isArray()) { if (workspaces.isArray()) {
auto workspace = std::find_if(workspaces.begin(), workspaces.end(), auto workspace = std::ranges::find_if(
[&](Json::Value workspace) { return workspace["id"] == id; }); workspaces, [&](Json::Value workspace) { return workspace["id"] == id; });
if (workspace == std::end(workspaces)) { if (workspace == std::end(workspaces)) {
spdlog::warn("No workspace with id {}", id); spdlog::warn("No workspace with id {}", id);
return Workspace{-1, 0, "", ""}; return Workspace{
.id = -1,
.windows = 0,
.last_window = "",
.last_window_title = "",
};
} }
return Workspace::parse(*workspace); return Workspace::parse(*workspace);
}; };
@ -138,22 +157,25 @@ auto Window::getActiveWorkspace(const std::string& monitorName) -> Workspace {
auto Window::Workspace::parse(const Json::Value& value) -> Window::Workspace { auto Window::Workspace::parse(const Json::Value& value) -> Window::Workspace {
return Workspace{ return Workspace{
value["id"].asInt(), .id = value["id"].asInt(),
value["windows"].asInt(), .windows = value["windows"].asInt(),
value["lastwindow"].asString(), .last_window = value["lastwindow"].asString(),
value["lastwindowtitle"].asString(), .last_window_title = value["lastwindowtitle"].asString(),
}; };
} }
auto Window::WindowData::parse(const Json::Value& value) -> Window::WindowData { auto Window::WindowData::parse(const Json::Value& value) -> Window::WindowData {
return WindowData{value["floating"].asBool(), value["monitor"].asInt(), return WindowData{.floating = value["floating"].asBool(),
value["class"].asString(), value["initialClass"].asString(), .monitor = value["monitor"].asInt(),
value["title"].asString(), value["initialTitle"].asString(), .class_name = value["class"].asString(),
value["fullscreen"].asBool(), !value["grouped"].empty()}; .initial_class_name = value["initialClass"].asString(),
.title = value["title"].asString(),
.initial_title = value["initialTitle"].asString(),
.fullscreen = value["fullscreen"].asBool(),
.grouped = !value["grouped"].empty()};
} }
void Window::queryActiveWorkspace() { void Window::queryActiveWorkspace() {
std::shared_lock<std::shared_mutex> windowIpcShareLock(windowIpcSmtx); std::shared_lock<std::shared_mutex> windowIpcShareLock(windowIpcSmtx);
if (separateOutputs_) { if (separateOutputs_) {
@ -164,11 +186,10 @@ void Window::queryActiveWorkspace() {
focused_ = true; focused_ = true;
if (workspace_.windows > 0) { if (workspace_.windows > 0) {
const auto clients = gIPC->getSocket1JsonReply("clients"); const auto clients = m_ipc.getSocket1JsonReply("clients");
if (clients.isArray()) { if (clients.isArray()) {
auto activeWindow = std::find_if(clients.begin(), clients.end(), [&](Json::Value window) { auto activeWindow = std::ranges::find_if(
return window["address"] == workspace_.last_window; clients, [&](Json::Value window) { return window["address"] == workspace_.last_window; });
});
if (activeWindow == std::end(clients)) { if (activeWindow == std::end(clients)) {
focused_ = false; focused_ = false;
@ -178,22 +199,19 @@ void Window::queryActiveWorkspace() {
windowData_ = WindowData::parse(*activeWindow); windowData_ = WindowData::parse(*activeWindow);
updateAppIconName(windowData_.class_name, windowData_.initial_class_name); updateAppIconName(windowData_.class_name, windowData_.initial_class_name);
std::vector<Json::Value> workspaceWindows; std::vector<Json::Value> workspaceWindows;
std::copy_if(clients.begin(), clients.end(), std::back_inserter(workspaceWindows), std::ranges::copy_if(clients, std::back_inserter(workspaceWindows), [&](Json::Value window) {
[&](Json::Value window) { return window["workspace"]["id"] == workspace_.id && window["mapped"].asBool();
return window["workspace"]["id"] == workspace_.id && window["mapped"].asBool(); });
}); swallowing_ = std::ranges::any_of(workspaceWindows, [&](Json::Value window) {
swallowing_ = return !window["swallowing"].isNull() && window["swallowing"].asString() != "0x0";
std::any_of(workspaceWindows.begin(), workspaceWindows.end(), [&](Json::Value window) { });
return !window["swallowing"].isNull() && window["swallowing"].asString() != "0x0";
});
std::vector<Json::Value> visibleWindows; std::vector<Json::Value> visibleWindows;
std::copy_if(workspaceWindows.begin(), workspaceWindows.end(), std::ranges::copy_if(workspaceWindows, std::back_inserter(visibleWindows),
std::back_inserter(visibleWindows), [&](Json::Value window) { return !window["hidden"].asBool(); });
[&](Json::Value window) { return !window["hidden"].asBool(); });
solo_ = 1 == std::count_if(visibleWindows.begin(), visibleWindows.end(), solo_ = 1 == std::count_if(visibleWindows.begin(), visibleWindows.end(),
[&](Json::Value window) { return !window["floating"].asBool(); }); [&](Json::Value window) { return !window["floating"].asBool(); });
allFloating_ = std::all_of(visibleWindows.begin(), visibleWindows.end(), allFloating_ = std::ranges::all_of(
[&](Json::Value window) { return window["floating"].asBool(); }); visibleWindows, [&](Json::Value window) { return window["floating"].asBool(); });
fullscreen_ = windowData_.fullscreen; fullscreen_ = windowData_.fullscreen;
// Fullscreen windows look like they are solo // Fullscreen windows look like they are solo
@ -206,7 +224,7 @@ void Window::queryActiveWorkspace() {
} else { } else {
soloClass_ = ""; soloClass_ = "";
} }
}; }
} else { } else {
focused_ = false; focused_ = false;
windowData_ = WindowData{}; windowData_ = WindowData{};

View File

@ -18,7 +18,8 @@ Workspace::Workspace(const Json::Value &workspace_data, Workspaces &workspace_ma
m_windows(workspace_data["windows"].asInt()), m_windows(workspace_data["windows"].asInt()),
m_isActive(true), m_isActive(true),
m_isPersistentRule(workspace_data["persistent-rule"].asBool()), m_isPersistentRule(workspace_data["persistent-rule"].asBool()),
m_isPersistentConfig(workspace_data["persistent-config"].asBool()) { m_isPersistentConfig(workspace_data["persistent-config"].asBool()),
m_ipc(IPC::inst()) {
if (m_name.starts_with("name:")) { if (m_name.starts_with("name:")) {
m_name = m_name.substr(5); m_name = m_name.substr(5);
} else if (m_name.starts_with("special")) { } else if (m_name.starts_with("special")) {
@ -58,20 +59,20 @@ bool Workspace::handleClicked(GdkEventButton *bt) const {
try { try {
if (id() > 0) { // normal if (id() > 0) { // normal
if (m_workspaceManager.moveToMonitor()) { if (m_workspaceManager.moveToMonitor()) {
gIPC->getSocket1Reply("dispatch focusworkspaceoncurrentmonitor " + std::to_string(id())); m_ipc.getSocket1Reply("dispatch focusworkspaceoncurrentmonitor " + std::to_string(id()));
} else { } else {
gIPC->getSocket1Reply("dispatch workspace " + std::to_string(id())); m_ipc.getSocket1Reply("dispatch workspace " + std::to_string(id()));
} }
} else if (!isSpecial()) { // named (this includes persistent) } else if (!isSpecial()) { // named (this includes persistent)
if (m_workspaceManager.moveToMonitor()) { if (m_workspaceManager.moveToMonitor()) {
gIPC->getSocket1Reply("dispatch focusworkspaceoncurrentmonitor name:" + name()); m_ipc.getSocket1Reply("dispatch focusworkspaceoncurrentmonitor name:" + name());
} else { } else {
gIPC->getSocket1Reply("dispatch workspace name:" + name()); m_ipc.getSocket1Reply("dispatch workspace name:" + name());
} }
} else if (id() != -99) { // named special } else if (id() != -99) { // named special
gIPC->getSocket1Reply("dispatch togglespecialworkspace " + name()); m_ipc.getSocket1Reply("dispatch togglespecialworkspace " + name());
} else { // special } else { // special
gIPC->getSocket1Reply("dispatch togglespecialworkspace"); m_ipc.getSocket1Reply("dispatch togglespecialworkspace");
} }
return true; return true;
} catch (const std::exception &e) { } catch (const std::exception &e) {

View File

@ -5,6 +5,7 @@
#include <algorithm> #include <algorithm>
#include <memory> #include <memory>
#include <sstream>
#include <string> #include <string>
#include <utility> #include <utility>
@ -13,7 +14,10 @@
namespace waybar::modules::hyprland { namespace waybar::modules::hyprland {
Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value &config) Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value &config)
: AModule(config, "workspaces", id, false, false), m_bar(bar), m_box(bar.orientation, 0) { : AModule(config, "workspaces", id, false, false),
m_bar(bar),
m_box(bar.orientation, 0),
m_ipc(IPC::inst()) {
modulesReady = true; modulesReady = true;
parseConfig(config); parseConfig(config);
@ -24,23 +28,19 @@ Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value
m_box.get_style_context()->add_class(MODULE_CLASS); m_box.get_style_context()->add_class(MODULE_CLASS);
event_box_.add(m_box); event_box_.add(m_box);
if (!gIPC) {
gIPC = std::make_unique<IPC>();
}
setCurrentMonitorId(); setCurrentMonitorId();
init(); init();
registerIpc(); registerIpc();
} }
Workspaces::~Workspaces() { Workspaces::~Workspaces() {
gIPC->unregisterForIPC(this); m_ipc.unregisterForIPC(this);
// wait for possible event handler to finish // wait for possible event handler to finish
std::lock_guard<std::mutex> lg(m_mutex); std::lock_guard<std::mutex> lg(m_mutex);
} }
void Workspaces::init() { void Workspaces::init() {
m_activeWorkspaceName = (gIPC->getSocket1JsonReply("activeworkspace"))["name"].asString(); m_activeWorkspaceId = m_ipc.getSocket1JsonReply("activeworkspace")["id"].asInt();
initializeWorkspaces(); initializeWorkspaces();
dp.emit(); dp.emit();
@ -50,13 +50,12 @@ Json::Value Workspaces::createMonitorWorkspaceData(std::string const &name,
std::string const &monitor) { std::string const &monitor) {
spdlog::trace("Creating persistent workspace: {} on monitor {}", name, monitor); spdlog::trace("Creating persistent workspace: {} on monitor {}", name, monitor);
Json::Value workspaceData; Json::Value workspaceData;
try {
// numbered persistent workspaces get the name as ID auto workspaceId = parseWorkspaceId(name);
workspaceData["id"] = name == "special" ? -99 : std::stoi(name); if (!workspaceId.has_value()) {
} catch (const std::exception &e) { workspaceId = 0;
// named persistent workspaces start with ID=0
workspaceData["id"] = 0;
} }
workspaceData["id"] = *workspaceId;
workspaceData["name"] = name; workspaceData["name"] = name;
workspaceData["monitor"] = monitor; workspaceData["monitor"] = monitor;
workspaceData["windows"] = 0; workspaceData["windows"] = 0;
@ -69,9 +68,8 @@ void Workspaces::createWorkspace(Json::Value const &workspace_data,
spdlog::debug("Creating workspace {}", workspaceName); spdlog::debug("Creating workspace {}", workspaceName);
// avoid recreating existing workspaces // avoid recreating existing workspaces
auto workspace = std::find_if( auto workspace =
m_workspaces.begin(), m_workspaces.end(), std::ranges::find_if(m_workspaces, [workspaceName](std::unique_ptr<Workspace> const &w) {
[workspaceName](std::unique_ptr<Workspace> const &w) {
return (workspaceName.starts_with("special:") && workspaceName.substr(8) == w->name()) || return (workspaceName.starts_with("special:") && workspaceName.substr(8) == w->name()) ||
workspaceName == w->name(); workspaceName == w->name();
}); });
@ -81,14 +79,14 @@ void Workspaces::createWorkspace(Json::Value const &workspace_data,
const auto keys = workspace_data.getMemberNames(); const auto keys = workspace_data.getMemberNames();
const auto *k = "persistent-rule"; const auto *k = "persistent-rule";
if (std::find(keys.begin(), keys.end(), k) != keys.end()) { if (std::ranges::find(keys, k) != keys.end()) {
spdlog::debug("Set dynamic persistency of workspace {} to: {}", workspaceName, spdlog::debug("Set dynamic persistency of workspace {} to: {}", workspaceName,
workspace_data[k].asBool() ? "true" : "false"); workspace_data[k].asBool() ? "true" : "false");
(*workspace)->setPersistentRule(workspace_data[k].asBool()); (*workspace)->setPersistentRule(workspace_data[k].asBool());
} }
k = "persistent-config"; k = "persistent-config";
if (std::find(keys.begin(), keys.end(), k) != keys.end()) { if (std::ranges::find(keys, k) != keys.end()) {
spdlog::debug("Set config persistency of workspace {} to: {}", workspaceName, spdlog::debug("Set config persistency of workspace {} to: {}", workspaceName,
workspace_data[k].asBool() ? "true" : "false"); workspace_data[k].asBool() ? "true" : "false");
(*workspace)->setPersistentConfig(workspace_data[k].asBool()); (*workspace)->setPersistentConfig(workspace_data[k].asBool());
@ -159,18 +157,18 @@ std::string Workspaces::getRewrite(std::string window_class, std::string window_
fmt::arg("title", window_title)); fmt::arg("title", window_title));
} }
std::vector<std::string> Workspaces::getVisibleWorkspaces() { std::vector<int> Workspaces::getVisibleWorkspaces() {
std::vector<std::string> visibleWorkspaces; std::vector<int> visibleWorkspaces;
auto monitors = gIPC->getSocket1JsonReply("monitors"); auto monitors = IPC::inst().getSocket1JsonReply("monitors");
for (const auto &monitor : monitors) { for (const auto &monitor : monitors) {
auto ws = monitor["activeWorkspace"]; auto ws = monitor["activeWorkspace"];
if (ws.isObject() && ws["name"].isString()) { if (ws.isObject() && ws["id"].isInt()) {
visibleWorkspaces.push_back(ws["name"].asString()); visibleWorkspaces.push_back(ws["id"].asInt());
} }
auto sws = monitor["specialWorkspace"]; auto sws = monitor["specialWorkspace"];
auto name = sws["name"].asString(); auto name = sws["name"].asString();
if (sws.isObject() && sws["name"].isString() && !name.empty()) { if (sws.isObject() && sws["id"].isInt() && !name.empty()) {
visibleWorkspaces.push_back(!name.starts_with("special:") ? name : name.substr(8)); visibleWorkspaces.push_back(sws["id"].asInt());
} }
} }
return visibleWorkspaces; return visibleWorkspaces;
@ -181,12 +179,12 @@ void Workspaces::initializeWorkspaces() {
// if the workspace rules changed since last initialization, make sure we reset everything: // if the workspace rules changed since last initialization, make sure we reset everything:
for (auto &workspace : m_workspaces) { for (auto &workspace : m_workspaces) {
m_workspacesToRemove.push_back(workspace->name()); m_workspacesToRemove.push_back(std::to_string(workspace->id()));
} }
// get all current workspaces // get all current workspaces
auto const workspacesJson = gIPC->getSocket1JsonReply("workspaces"); auto const workspacesJson = m_ipc.getSocket1JsonReply("workspaces");
auto const clientsJson = gIPC->getSocket1JsonReply("clients"); auto const clientsJson = m_ipc.getSocket1JsonReply("clients");
for (Json::Value workspaceJson : workspacesJson) { for (Json::Value workspaceJson : workspacesJson) {
std::string workspaceName = workspaceJson["name"].asString(); std::string workspaceName = workspaceJson["name"].asString();
@ -233,7 +231,7 @@ void Workspaces::loadPersistentWorkspacesFromConfig(Json::Value const &clientsJs
std::vector<std::string> persistentWorkspacesToCreate; std::vector<std::string> persistentWorkspacesToCreate;
const std::string currentMonitor = m_bar.output->name; const std::string currentMonitor = m_bar.output->name;
const bool monitorInConfig = std::find(keys.begin(), keys.end(), currentMonitor) != keys.end(); const bool monitorInConfig = std::ranges::find(keys, currentMonitor) != keys.end();
for (const std::string &key : keys) { for (const std::string &key : keys) {
// only add if either: // only add if either:
// 1. key is the current monitor name // 1. key is the current monitor name
@ -248,7 +246,7 @@ void Workspaces::loadPersistentWorkspacesFromConfig(Json::Value const &clientsJs
int amount = value.asInt(); int amount = value.asInt();
spdlog::debug("Creating {} persistent workspaces for monitor {}", amount, currentMonitor); spdlog::debug("Creating {} persistent workspaces for monitor {}", amount, currentMonitor);
for (int i = 0; i < amount; i++) { for (int i = 0; i < amount; i++) {
persistentWorkspacesToCreate.emplace_back(std::to_string(m_monitorId * amount + i + 1)); persistentWorkspacesToCreate.emplace_back(std::to_string((m_monitorId * amount) + i + 1));
} }
} }
} else if (value.isArray() && !value.empty()) { } else if (value.isArray() && !value.empty()) {
@ -285,7 +283,7 @@ void Workspaces::loadPersistentWorkspacesFromConfig(Json::Value const &clientsJs
void Workspaces::loadPersistentWorkspacesFromWorkspaceRules(const Json::Value &clientsJson) { void Workspaces::loadPersistentWorkspacesFromWorkspaceRules(const Json::Value &clientsJson) {
spdlog::info("Loading persistent workspaces from Hyprland workspace rules"); spdlog::info("Loading persistent workspaces from Hyprland workspace rules");
auto const workspaceRules = gIPC->getSocket1JsonReply("workspacerules"); auto const workspaceRules = m_ipc.getSocket1JsonReply("workspacerules");
for (Json::Value const &rule : workspaceRules) { for (Json::Value const &rule : workspaceRules) {
if (!rule["workspaceString"].isString()) { if (!rule["workspaceString"].isString()) {
spdlog::warn("Workspace rules: invalid workspaceString, skipping: {}", rule); spdlog::warn("Workspace rules: invalid workspaceString, skipping: {}", rule);
@ -294,7 +292,8 @@ void Workspaces::loadPersistentWorkspacesFromWorkspaceRules(const Json::Value &c
if (!rule["persistent"].asBool()) { if (!rule["persistent"].asBool()) {
continue; continue;
} }
auto const &workspace = rule["workspaceString"].asString(); auto const &workspace = rule.isMember("defaultName") ? rule["defaultName"].asString()
: rule["workspaceString"].asString();
auto const &monitor = rule["monitor"].asString(); auto const &monitor = rule["monitor"].asString();
// create this workspace persistently if: // create this workspace persistently if:
// 1. the allOutputs config option is enabled // 1. the allOutputs config option is enabled
@ -306,6 +305,7 @@ void Workspaces::loadPersistentWorkspacesFromWorkspaceRules(const Json::Value &c
workspaceData["persistent-rule"] = true; workspaceData["persistent-rule"] = true;
m_workspacesToCreate.emplace_back(workspaceData, clientsJson); m_workspacesToCreate.emplace_back(workspaceData, clientsJson);
} else { } else {
// This can be any workspace selector.
m_workspacesToRemove.emplace_back(workspace); m_workspacesToRemove.emplace_back(workspace);
} }
} }
@ -316,29 +316,29 @@ void Workspaces::onEvent(const std::string &ev) {
std::string eventName(begin(ev), begin(ev) + ev.find_first_of('>')); std::string eventName(begin(ev), begin(ev) + ev.find_first_of('>'));
std::string payload = ev.substr(eventName.size() + 2); std::string payload = ev.substr(eventName.size() + 2);
if (eventName == "workspace") { if (eventName == "workspacev2") {
onWorkspaceActivated(payload); onWorkspaceActivated(payload);
} else if (eventName == "activespecial") { } else if (eventName == "activespecial") {
onSpecialWorkspaceActivated(payload); onSpecialWorkspaceActivated(payload);
} else if (eventName == "destroyworkspace") { } else if (eventName == "destroyworkspacev2") {
onWorkspaceDestroyed(payload); onWorkspaceDestroyed(payload);
} else if (eventName == "createworkspace") { } else if (eventName == "createworkspacev2") {
onWorkspaceCreated(payload); onWorkspaceCreated(payload);
} else if (eventName == "focusedmon") { } else if (eventName == "focusedmonv2") {
onMonitorFocused(payload); onMonitorFocused(payload);
} else if (eventName == "moveworkspace") { } else if (eventName == "moveworkspacev2") {
onWorkspaceMoved(payload); onWorkspaceMoved(payload);
} else if (eventName == "openwindow") { } else if (eventName == "openwindow") {
onWindowOpened(payload); onWindowOpened(payload);
} else if (eventName == "closewindow") { } else if (eventName == "closewindow") {
onWindowClosed(payload); onWindowClosed(payload);
} else if (eventName == "movewindow") { } else if (eventName == "movewindowv2") {
onWindowMoved(payload); onWindowMoved(payload);
} else if (eventName == "urgent") { } else if (eventName == "urgent") {
setUrgentWorkspace(payload); setUrgentWorkspace(payload);
} else if (eventName == "renameworkspace") { } else if (eventName == "renameworkspace") {
onWorkspaceRenamed(payload); onWorkspaceRenamed(payload);
} else if (eventName == "windowtitle") { } else if (eventName == "windowtitlev2") {
onWindowTitleEvent(payload); onWindowTitleEvent(payload);
} else if (eventName == "configreloaded") { } else if (eventName == "configreloaded") {
onConfigReloaded(); onConfigReloaded();
@ -348,7 +348,11 @@ void Workspaces::onEvent(const std::string &ev) {
} }
void Workspaces::onWorkspaceActivated(std::string const &payload) { void Workspaces::onWorkspaceActivated(std::string const &payload) {
m_activeWorkspaceName = payload; const auto [workspaceIdStr, workspaceName] = splitDoublePayload(payload);
const auto workspaceId = parseWorkspaceId(workspaceIdStr);
if (workspaceId.has_value()) {
m_activeWorkspaceId = *workspaceId;
}
} }
void Workspaces::onSpecialWorkspaceActivated(std::string const &payload) { void Workspaces::onSpecialWorkspaceActivated(std::string const &payload) {
@ -357,39 +361,55 @@ void Workspaces::onSpecialWorkspaceActivated(std::string const &payload) {
} }
void Workspaces::onWorkspaceDestroyed(std::string const &payload) { void Workspaces::onWorkspaceDestroyed(std::string const &payload) {
if (!isDoubleSpecial(payload)) { const auto [workspaceId, workspaceName] = splitDoublePayload(payload);
m_workspacesToRemove.push_back(payload); if (!isDoubleSpecial(workspaceName)) {
m_workspacesToRemove.push_back(workspaceId);
} }
} }
void Workspaces::onWorkspaceCreated(std::string const &workspaceName, void Workspaces::onWorkspaceCreated(std::string const &payload, Json::Value const &clientsData) {
Json::Value const &clientsData) { spdlog::debug("Workspace created: {}", payload);
spdlog::debug("Workspace created: {}", workspaceName);
auto const workspacesJson = gIPC->getSocket1JsonReply("workspaces");
if (!isWorkspaceIgnored(workspaceName)) { const auto [workspaceIdStr, _] = splitDoublePayload(payload);
auto const workspaceRules = gIPC->getSocket1JsonReply("workspacerules");
for (Json::Value workspaceJson : workspacesJson) {
std::string name = workspaceJson["name"].asString();
if (name == workspaceName) {
if ((allOutputs() || m_bar.output->name == workspaceJson["monitor"].asString()) &&
(showSpecial() || !name.starts_with("special")) && !isDoubleSpecial(workspaceName)) {
for (Json::Value const &rule : workspaceRules) {
if (rule["workspaceString"].asString() == workspaceName) {
workspaceJson["persistent-rule"] = rule["persistent"].asBool();
break;
}
}
m_workspacesToCreate.emplace_back(workspaceJson, clientsData); const auto workspaceId = parseWorkspaceId(workspaceIdStr);
break; if (!workspaceId.has_value()) {
} return;
} else { }
extendOrphans(workspaceJson["id"].asInt(), clientsData);
auto const workspaceRules = m_ipc.getSocket1JsonReply("workspacerules");
auto const workspacesJson = m_ipc.getSocket1JsonReply("workspaces");
for (Json::Value workspaceJson : workspacesJson) {
const auto currentId = workspaceJson["id"].asInt();
if (currentId == *workspaceId) {
std::string workspaceName = workspaceJson["name"].asString();
// This workspace name is more up-to-date than the one in the event payload.
if (isWorkspaceIgnored(workspaceName)) {
spdlog::trace("Not creating workspace because it is ignored: id={} name={}", *workspaceId,
workspaceName);
break;
} }
if ((allOutputs() || m_bar.output->name == workspaceJson["monitor"].asString()) &&
(showSpecial() || !workspaceName.starts_with("special")) &&
!isDoubleSpecial(workspaceName)) {
for (Json::Value const &rule : workspaceRules) {
auto ruleWorkspaceName = rule.isMember("defaultName")
? rule["defaultName"].asString()
: rule["workspaceString"].asString();
if (ruleWorkspaceName == workspaceName) {
workspaceJson["persistent-rule"] = rule["persistent"].asBool();
break;
}
}
m_workspacesToCreate.emplace_back(workspaceJson, clientsData);
break;
}
} else {
extendOrphans(*workspaceId, clientsData);
} }
} else {
spdlog::trace("Not creating workspace because it is ignored: {}", workspaceName);
} }
} }
@ -397,32 +417,34 @@ void Workspaces::onWorkspaceMoved(std::string const &payload) {
spdlog::debug("Workspace moved: {}", payload); spdlog::debug("Workspace moved: {}", payload);
// Update active workspace // Update active workspace
m_activeWorkspaceName = (gIPC->getSocket1JsonReply("activeworkspace"))["name"].asString(); m_activeWorkspaceId = (m_ipc.getSocket1JsonReply("activeworkspace"))["id"].asInt();
if (allOutputs()) return; if (allOutputs()) return;
std::string workspaceName = payload.substr(0, payload.find(',')); const auto [workspaceIdStr, workspaceName, monitorName] = splitTriplePayload(payload);
std::string monitorName = payload.substr(payload.find(',') + 1);
const auto subPayload = makePayload(workspaceIdStr, workspaceName);
if (m_bar.output->name == monitorName) { if (m_bar.output->name == monitorName) {
Json::Value clientsData = gIPC->getSocket1JsonReply("clients"); Json::Value clientsData = m_ipc.getSocket1JsonReply("clients");
onWorkspaceCreated(workspaceName, clientsData); onWorkspaceCreated(subPayload, clientsData);
} else { } else {
spdlog::debug("Removing workspace because it was moved to another monitor: {}"); spdlog::debug("Removing workspace because it was moved to another monitor: {}", subPayload);
onWorkspaceDestroyed(workspaceName); onWorkspaceDestroyed(subPayload);
} }
} }
void Workspaces::onWorkspaceRenamed(std::string const &payload) { void Workspaces::onWorkspaceRenamed(std::string const &payload) {
spdlog::debug("Workspace renamed: {}", payload); spdlog::debug("Workspace renamed: {}", payload);
std::string workspaceIdStr = payload.substr(0, payload.find(',')); const auto [workspaceIdStr, newName] = splitDoublePayload(payload);
int workspaceId = workspaceIdStr == "special" ? -99 : std::stoi(workspaceIdStr);
std::string newName = payload.substr(payload.find(',') + 1); const auto workspaceId = parseWorkspaceId(workspaceIdStr);
if (!workspaceId.has_value()) {
return;
}
for (auto &workspace : m_workspaces) { for (auto &workspace : m_workspaces) {
if (workspace->id() == workspaceId) { if (workspace->id() == *workspaceId) {
if (workspace->name() == m_activeWorkspaceName) {
m_activeWorkspaceName = newName;
}
workspace->setName(newName); workspace->setName(newName);
break; break;
} }
@ -432,11 +454,19 @@ void Workspaces::onWorkspaceRenamed(std::string const &payload) {
void Workspaces::onMonitorFocused(std::string const &payload) { void Workspaces::onMonitorFocused(std::string const &payload) {
spdlog::trace("Monitor focused: {}", payload); spdlog::trace("Monitor focused: {}", payload);
m_activeWorkspaceName = payload.substr(payload.find(',') + 1);
for (Json::Value &monitor : gIPC->getSocket1JsonReply("monitors")) { const auto [monitorName, workspaceIdStr] = splitDoublePayload(payload);
if (monitor["name"].asString() == payload.substr(0, payload.find(','))) {
auto name = monitor["specialWorkspace"]["name"].asString(); const auto workspaceId = parseWorkspaceId(workspaceIdStr);
if (!workspaceId.has_value()) {
return;
}
m_activeWorkspaceId = *workspaceId;
for (Json::Value &monitor : m_ipc.getSocket1JsonReply("monitors")) {
if (monitor["name"].asString() == monitorName) {
const auto name = monitor["specialWorkspace"]["name"].asString();
m_activeSpecialWorkspaceName = !name.starts_with("special:") ? name : name.substr(8); m_activeSpecialWorkspaceName = !name.starts_with("special:") ? name : name.substr(8);
} }
} }
@ -475,11 +505,7 @@ void Workspaces::onWindowClosed(std::string const &addr) {
void Workspaces::onWindowMoved(std::string const &payload) { void Workspaces::onWindowMoved(std::string const &payload) {
spdlog::trace("Window moved: {}", payload); spdlog::trace("Window moved: {}", payload);
updateWindowCount(); updateWindowCount();
size_t lastCommaIdx = 0; auto [windowAddress, _, workspaceName] = splitTriplePayload(payload);
size_t nextCommaIdx = payload.find(',');
std::string windowAddress = payload.substr(lastCommaIdx, nextCommaIdx - lastCommaIdx);
std::string workspaceName = payload.substr(nextCommaIdx + 1, payload.length() - nextCommaIdx);
std::string windowRepr; std::string windowRepr;
@ -515,13 +541,15 @@ void Workspaces::onWindowTitleEvent(std::string const &payload) {
spdlog::trace("Window title changed: {}", payload); spdlog::trace("Window title changed: {}", payload);
std::optional<std::function<void(WindowCreationPayload)>> inserter; std::optional<std::function<void(WindowCreationPayload)>> inserter;
const auto [windowAddress, _] = splitDoublePayload(payload);
// If the window was an orphan, rename it at the orphan's vector // If the window was an orphan, rename it at the orphan's vector
if (m_orphanWindowMap.contains(payload)) { if (m_orphanWindowMap.contains(windowAddress)) {
inserter = [this](WindowCreationPayload wcp) { this->registerOrphanWindow(std::move(wcp)); }; inserter = [this](WindowCreationPayload wcp) { this->registerOrphanWindow(std::move(wcp)); };
} else { } else {
auto windowWorkspace = auto windowWorkspace = std::ranges::find_if(m_workspaces, [windowAddress](auto &workspace) {
std::find_if(m_workspaces.begin(), m_workspaces.end(), return workspace->containsWindow(windowAddress);
[payload](auto &workspace) { return workspace->containsWindow(payload); }); });
// If the window exists on a workspace, rename it at the workspace's window // If the window exists on a workspace, rename it at the workspace's window
// map // map
@ -530,9 +558,9 @@ void Workspaces::onWindowTitleEvent(std::string const &payload) {
(*windowWorkspace)->insertWindow(std::move(wcp)); (*windowWorkspace)->insertWindow(std::move(wcp));
}; };
} else { } else {
auto queuedWindow = std::find_if( auto queuedWindow = std::ranges::find_if(m_windowsToCreate, [payload](auto &windowPayload) {
m_windowsToCreate.begin(), m_windowsToCreate.end(), return windowPayload.getAddress() == payload;
[payload](auto &windowPayload) { return windowPayload.getAddress() == payload; }); });
// If the window was queued, rename it in the queue // If the window was queued, rename it in the queue
if (queuedWindow != m_windowsToCreate.end()) { if (queuedWindow != m_windowsToCreate.end()) {
@ -542,15 +570,14 @@ void Workspaces::onWindowTitleEvent(std::string const &payload) {
} }
if (inserter.has_value()) { if (inserter.has_value()) {
Json::Value clientsData = gIPC->getSocket1JsonReply("clients"); Json::Value clientsData = m_ipc.getSocket1JsonReply("clients");
std::string jsonWindowAddress = fmt::format("0x{}", payload); std::string jsonWindowAddress = fmt::format("0x{}", payload);
auto client = auto client = std::ranges::find_if(clientsData, [jsonWindowAddress](auto &client) {
std::find_if(clientsData.begin(), clientsData.end(), [jsonWindowAddress](auto &client) { return client["address"].asString() == jsonWindowAddress;
return client["address"].asString() == jsonWindowAddress; });
});
if (!client->empty()) { if (client != clientsData.end() && !client->empty()) {
(*inserter)({*client}); (*inserter)({*client});
} }
} }
@ -590,8 +617,8 @@ auto Workspaces::populateIconsMap(const Json::Value &formatIcons) -> void {
m_iconsMap.emplace("", ""); m_iconsMap.emplace("", "");
} }
auto Workspaces::populateBoolConfig(const Json::Value &config, const std::string &key, auto Workspaces::populateBoolConfig(const Json::Value &config, const std::string &key, bool &member)
bool &member) -> void { -> void {
const auto &configValue = config[key]; const auto &configValue = config[key];
if (configValue.isBool()) { if (configValue.isBool()) {
member = configValue.asBool(); member = configValue.asBool();
@ -660,40 +687,58 @@ void Workspaces::registerOrphanWindow(WindowCreationPayload create_window_payloa
} }
auto Workspaces::registerIpc() -> void { auto Workspaces::registerIpc() -> void {
gIPC->registerForIPC("workspace", this); m_ipc.registerForIPC("workspacev2", this);
gIPC->registerForIPC("activespecial", this); m_ipc.registerForIPC("activespecial", this);
gIPC->registerForIPC("createworkspace", this); m_ipc.registerForIPC("createworkspacev2", this);
gIPC->registerForIPC("destroyworkspace", this); m_ipc.registerForIPC("destroyworkspacev2", this);
gIPC->registerForIPC("focusedmon", this); m_ipc.registerForIPC("focusedmonv2", this);
gIPC->registerForIPC("moveworkspace", this); m_ipc.registerForIPC("moveworkspacev2", this);
gIPC->registerForIPC("renameworkspace", this); m_ipc.registerForIPC("renameworkspace", this);
gIPC->registerForIPC("openwindow", this); m_ipc.registerForIPC("openwindow", this);
gIPC->registerForIPC("closewindow", this); m_ipc.registerForIPC("closewindow", this);
gIPC->registerForIPC("movewindow", this); m_ipc.registerForIPC("movewindowv2", this);
gIPC->registerForIPC("urgent", this); m_ipc.registerForIPC("urgent", this);
gIPC->registerForIPC("configreloaded", this); m_ipc.registerForIPC("configreloaded", this);
if (windowRewriteConfigUsesTitle()) { if (windowRewriteConfigUsesTitle()) {
spdlog::info( spdlog::info(
"Registering for Hyprland's 'windowtitle' events because a user-defined window " "Registering for Hyprland's 'windowtitlev2' events because a user-defined window "
"rewrite rule uses the 'title' field."); "rewrite rule uses the 'title' field.");
gIPC->registerForIPC("windowtitle", this); m_ipc.registerForIPC("windowtitlev2", this);
} }
} }
void Workspaces::removeWorkspacesToRemove() { void Workspaces::removeWorkspacesToRemove() {
for (const auto &workspaceName : m_workspacesToRemove) { for (const auto &workspaceString : m_workspacesToRemove) {
removeWorkspace(workspaceName); removeWorkspace(workspaceString);
} }
m_workspacesToRemove.clear(); m_workspacesToRemove.clear();
} }
void Workspaces::removeWorkspace(std::string const &name) { void Workspaces::removeWorkspace(std::string const &workspaceString) {
spdlog::debug("Removing workspace {}", name); spdlog::debug("Removing workspace {}", workspaceString);
auto workspace =
std::find_if(m_workspaces.begin(), m_workspaces.end(), [&](std::unique_ptr<Workspace> &x) { // If this succeeds, we have a workspace ID.
return (name.starts_with("special:") && name.substr(8) == x->name()) || name == x->name(); const auto workspaceId = parseWorkspaceId(workspaceString);
});
std::string name;
// TODO: At some point we want to support all workspace selectors
// This is just a subset.
// https://wiki.hyprland.org/Configuring/Workspace-Rules/#workspace-selectors
if (workspaceString.starts_with("special:")) {
name = workspaceString.substr(8);
} else if (workspaceString.starts_with("name:")) {
name = workspaceString.substr(5);
} else {
name = workspaceString;
}
const auto workspace = std::ranges::find_if(m_workspaces, [&](std::unique_ptr<Workspace> &x) {
if (workspaceId.has_value()) {
return *workspaceId == x->id();
}
return name == x->name();
});
if (workspace == m_workspaces.end()) { if (workspace == m_workspaces.end()) {
// happens when a workspace on another monitor is destroyed // happens when a workspace on another monitor is destroyed
@ -701,7 +746,8 @@ void Workspaces::removeWorkspace(std::string const &name) {
} }
if ((*workspace)->isPersistentConfig()) { if ((*workspace)->isPersistentConfig()) {
spdlog::trace("Not removing config persistent workspace {}", name); spdlog::trace("Not removing config persistent workspace id={} name={}", (*workspace)->id(),
(*workspace)->name());
return; return;
} }
@ -712,10 +758,10 @@ void Workspaces::removeWorkspace(std::string const &name) {
void Workspaces::setCurrentMonitorId() { void Workspaces::setCurrentMonitorId() {
// get monitor ID from name (used by persistent workspaces) // get monitor ID from name (used by persistent workspaces)
m_monitorId = 0; m_monitorId = 0;
auto monitors = gIPC->getSocket1JsonReply("monitors"); auto monitors = m_ipc.getSocket1JsonReply("monitors");
auto currentMonitor = std::find_if( auto currentMonitor = std::ranges::find_if(monitors, [this](const Json::Value &m) {
monitors.begin(), monitors.end(), return m["name"].asString() == m_bar.output->name;
[this](const Json::Value &m) { return m["name"].asString() == m_bar.output->name; }); });
if (currentMonitor == monitors.end()) { if (currentMonitor == monitors.end()) {
spdlog::error("Monitor '{}' does not have an ID? Using 0", m_bar.output->name); spdlog::error("Monitor '{}' does not have an ID? Using 0", m_bar.output->name);
} else { } else {
@ -725,62 +771,63 @@ void Workspaces::setCurrentMonitorId() {
} }
void Workspaces::sortWorkspaces() { void Workspaces::sortWorkspaces() {
std::sort(m_workspaces.begin(), m_workspaces.end(), std::ranges::sort( //
[&](std::unique_ptr<Workspace> &a, std::unique_ptr<Workspace> &b) { m_workspaces, [&](std::unique_ptr<Workspace> &a, std::unique_ptr<Workspace> &b) {
// Helper comparisons // Helper comparisons
auto isIdLess = a->id() < b->id(); auto isIdLess = a->id() < b->id();
auto isNameLess = a->name() < b->name(); auto isNameLess = a->name() < b->name();
switch (m_sortBy) { switch (m_sortBy) {
case SortMethod::ID: case SortMethod::ID:
return isIdLess; return isIdLess;
case SortMethod::NAME: case SortMethod::NAME:
return isNameLess; return isNameLess;
case SortMethod::NUMBER: case SortMethod::NUMBER:
try { try {
return std::stoi(a->name()) < std::stoi(b->name()); return std::stoi(a->name()) < std::stoi(b->name());
} catch (const std::invalid_argument &) { } catch (const std::invalid_argument &) {
// Handle the exception if necessary. // Handle the exception if necessary.
break; break;
} }
case SortMethod::DEFAULT: case SortMethod::DEFAULT:
default: default:
// Handle the default case here. // Handle the default case here.
// normal -> named persistent -> named -> special -> named special // normal -> named persistent -> named -> special -> named special
// both normal (includes numbered persistent) => sort by ID // both normal (includes numbered persistent) => sort by ID
if (a->id() > 0 && b->id() > 0) { if (a->id() > 0 && b->id() > 0) {
return isIdLess; return isIdLess;
} }
// one normal, one special => normal first // one normal, one special => normal first
if ((a->isSpecial()) ^ (b->isSpecial())) { if ((a->isSpecial()) ^ (b->isSpecial())) {
return b->isSpecial(); return b->isSpecial();
} }
// only one normal, one named // only one normal, one named
if ((a->id() > 0) ^ (b->id() > 0)) { if ((a->id() > 0) ^ (b->id() > 0)) {
return a->id() > 0; return a->id() > 0;
} }
// both special // both special
if (a->isSpecial() && b->isSpecial()) { if (a->isSpecial() && b->isSpecial()) {
// if one is -99 => put it last // if one is -99 => put it last
if (a->id() == -99 || b->id() == -99) { if (a->id() == -99 || b->id() == -99) {
return b->id() == -99; return b->id() == -99;
}
// both are 0 (not yet named persistents) / named specials (-98 <= ID <= -1)
return isNameLess;
}
// sort non-special named workspaces by name (ID <= -1377)
return isNameLess;
break;
} }
// both are 0 (not yet named persistents) / named specials
// (-98 <= ID <= -1)
return isNameLess;
}
// Return a default value if none of the cases match. // sort non-special named workspaces by name (ID <= -1377)
return isNameLess; // You can adjust this to your specific needs. return isNameLess;
}); break;
}
// Return a default value if none of the cases match.
return isNameLess; // You can adjust this to your specific needs.
});
for (size_t i = 0; i < m_workspaces.size(); ++i) { for (size_t i = 0; i < m_workspaces.size(); ++i) {
m_box.reorder_child(m_workspaces[i]->button(), i); m_box.reorder_child(m_workspaces[i]->button(), i);
@ -788,7 +835,7 @@ void Workspaces::sortWorkspaces() {
} }
void Workspaces::setUrgentWorkspace(std::string const &windowaddress) { void Workspaces::setUrgentWorkspace(std::string const &windowaddress) {
const Json::Value clientsJson = gIPC->getSocket1JsonReply("clients"); const Json::Value clientsJson = m_ipc.getSocket1JsonReply("clients");
int workspaceId = -1; int workspaceId = -1;
for (Json::Value clientJson : clientsJson) { for (Json::Value clientJson : clientsJson) {
@ -798,9 +845,9 @@ void Workspaces::setUrgentWorkspace(std::string const &windowaddress) {
} }
} }
auto workspace = auto workspace = std::ranges::find_if(m_workspaces, [workspaceId](std::unique_ptr<Workspace> &x) {
std::find_if(m_workspaces.begin(), m_workspaces.end(), return x->id() == workspaceId;
[workspaceId](std::unique_ptr<Workspace> &x) { return x->id() == workspaceId; }); });
if (workspace != m_workspaces.end()) { if (workspace != m_workspaces.end()) {
workspace->get()->setUrgent(); workspace->get()->setUrgent();
} }
@ -812,13 +859,12 @@ auto Workspaces::update() -> void {
} }
void Workspaces::updateWindowCount() { void Workspaces::updateWindowCount() {
const Json::Value workspacesJson = gIPC->getSocket1JsonReply("workspaces"); const Json::Value workspacesJson = m_ipc.getSocket1JsonReply("workspaces");
for (auto &workspace : m_workspaces) { for (auto &workspace : m_workspaces) {
auto workspaceJson = auto workspaceJson = std::ranges::find_if(workspacesJson, [&](Json::Value const &x) {
std::find_if(workspacesJson.begin(), workspacesJson.end(), [&](Json::Value const &x) { return x["name"].asString() == workspace->name() ||
return x["name"].asString() == workspace->name() || (workspace->isSpecial() && x["name"].asString() == "special:" + workspace->name());
(workspace->isSpecial() && x["name"].asString() == "special:" + workspace->name()); });
});
uint32_t count = 0; uint32_t count = 0;
if (workspaceJson != workspacesJson.end()) { if (workspaceJson != workspacesJson.end()) {
try { try {
@ -858,26 +904,26 @@ bool Workspaces::updateWindowsToCreate() {
} }
void Workspaces::updateWorkspaceStates() { void Workspaces::updateWorkspaceStates() {
const std::vector<std::string> visibleWorkspaces = getVisibleWorkspaces(); const std::vector<int> visibleWorkspaces = getVisibleWorkspaces();
auto updatedWorkspaces = gIPC->getSocket1JsonReply("workspaces"); auto updatedWorkspaces = m_ipc.getSocket1JsonReply("workspaces");
for (auto &workspace : m_workspaces) { for (auto &workspace : m_workspaces) {
workspace->setActive(workspace->name() == m_activeWorkspaceName || workspace->setActive(
workspace->name() == m_activeSpecialWorkspaceName); workspace->id() == m_activeWorkspaceId ||
if (workspace->name() == m_activeWorkspaceName && workspace->isUrgent()) { (workspace->isSpecial() && workspace->name() == m_activeSpecialWorkspaceName));
if (workspace->isActive() && workspace->isUrgent()) {
workspace->setUrgent(false); workspace->setUrgent(false);
} }
workspace->setVisible(std::find(visibleWorkspaces.begin(), visibleWorkspaces.end(), workspace->setVisible(std::ranges::find(visibleWorkspaces, workspace->id()) !=
workspace->name()) != visibleWorkspaces.end()); visibleWorkspaces.end());
std::string &workspaceIcon = m_iconsMap[""]; std::string &workspaceIcon = m_iconsMap[""];
if (m_withIcon) { if (m_withIcon) {
workspaceIcon = workspace->selectIcon(m_iconsMap); workspaceIcon = workspace->selectIcon(m_iconsMap);
} }
auto updatedWorkspace = std::find_if( auto updatedWorkspace = std::ranges::find_if(updatedWorkspaces, [&workspace](const auto &w) {
updatedWorkspaces.begin(), updatedWorkspaces.end(), [&workspace](const auto &w) { auto wNameRaw = w["name"].asString();
auto wNameRaw = w["name"].asString(); auto wName = wNameRaw.starts_with("special:") ? wNameRaw.substr(8) : wNameRaw;
auto wName = wNameRaw.starts_with("special:") ? wNameRaw.substr(8) : wNameRaw; return wName == workspace->name();
return wName == workspace->name(); });
});
if (updatedWorkspace != updatedWorkspaces.end()) { if (updatedWorkspace != updatedWorkspaces.end()) {
workspace->setOutput((*updatedWorkspace)["monitor"].asString()); workspace->setOutput((*updatedWorkspace)["monitor"].asString());
} }
@ -905,4 +951,39 @@ int Workspaces::windowRewritePriorityFunction(std::string const &window_rule) {
return 0; return 0;
} }
template <typename... Args>
std::string Workspaces::makePayload(Args const &...args) {
std::ostringstream result;
bool first = true;
((result << (first ? "" : ",") << args, first = false), ...);
return result.str();
}
std::pair<std::string, std::string> Workspaces::splitDoublePayload(std::string const &payload) {
const std::string part1 = payload.substr(0, payload.find(','));
const std::string part2 = payload.substr(part1.size() + 1);
return {part1, part2};
}
std::tuple<std::string, std::string, std::string> Workspaces::splitTriplePayload(
std::string const &payload) {
const size_t firstComma = payload.find(',');
const size_t secondComma = payload.find(',', firstComma + 1);
const std::string part1 = payload.substr(0, firstComma);
const std::string part2 = payload.substr(firstComma + 1, secondComma - (firstComma + 1));
const std::string part3 = payload.substr(secondComma + 1);
return {part1, part2, part3};
}
std::optional<int> Workspaces::parseWorkspaceId(std::string const &workspaceIdStr) {
try {
return workspaceIdStr == "special" ? -99 : std::stoi(workspaceIdStr);
} catch (std::exception const &e) {
spdlog::error("Failed to parse workspace ID: {}", e.what());
return std::nullopt;
}
}
} // namespace waybar::modules::hyprland } // namespace waybar::modules::hyprland

View File

@ -80,6 +80,7 @@ waybar::modules::Network::readBandwidthUsage() {
waybar::modules::Network::Network(const std::string &id, const Json::Value &config) waybar::modules::Network::Network(const std::string &id, const Json::Value &config)
: ALabel(config, "network", id, DEFAULT_FORMAT, 60), : ALabel(config, "network", id, DEFAULT_FORMAT, 60),
ifid_(-1), ifid_(-1),
addr_pref_(IPV4),
efd_(-1), efd_(-1),
ev_fd_(-1), ev_fd_(-1),
want_route_dump_(false), want_route_dump_(false),
@ -88,6 +89,7 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf
dump_in_progress_(false), dump_in_progress_(false),
is_p2p_(false), is_p2p_(false),
cidr_(0), cidr_(0),
cidr6_(0),
signal_strength_dbm_(0), signal_strength_dbm_(0),
signal_strength_(0), signal_strength_(0),
#ifdef WANT_RFKILL #ifdef WANT_RFKILL
@ -101,6 +103,12 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf
// the module start with no text, but the event_box_ is shown. // the module start with no text, but the event_box_ is shown.
label_.set_markup("<s></s>"); label_.set_markup("<s></s>");
if (config_["family"] == "ipv6") {
addr_pref_ = IPV6;
} else if (config["family"] == "ipv4_6") {
addr_pref_ = IPV4_6;
}
auto bandwidth = readBandwidthUsage(); auto bandwidth = readBandwidthUsage();
if (bandwidth.has_value()) { if (bandwidth.has_value()) {
bandwidth_down_total_ = (*bandwidth).first; bandwidth_down_total_ = (*bandwidth).first;
@ -270,7 +278,7 @@ const std::string waybar::modules::Network::getNetworkState() const {
return "disconnected"; return "disconnected";
} }
if (!carrier_) return "disconnected"; if (!carrier_) return "disconnected";
if (ipaddr_.empty()) return "linked"; if (ipaddr_.empty() && ipaddr6_.empty()) return "linked";
if (essid_.empty()) return "ethernet"; if (essid_.empty()) return "ethernet";
return "wifi"; return "wifi";
} }
@ -316,12 +324,24 @@ auto waybar::modules::Network::update() -> void {
} }
getState(signal_strength_); getState(signal_strength_);
std::string final_ipaddr_;
if (addr_pref_ == ip_addr_pref::IPV4) {
final_ipaddr_ = ipaddr_;
} else if (addr_pref_ == ip_addr_pref::IPV6) {
final_ipaddr_ = ipaddr6_;
} else if (addr_pref_ == ip_addr_pref::IPV4_6) {
final_ipaddr_ = ipaddr_;
final_ipaddr_ += '\n';
final_ipaddr_ += ipaddr6_;
}
auto text = fmt::format( auto text = fmt::format(
fmt::runtime(format_), fmt::arg("essid", essid_), fmt::arg("bssid", bssid_), fmt::runtime(format_), fmt::arg("essid", essid_), fmt::arg("bssid", bssid_),
fmt::arg("signaldBm", signal_strength_dbm_), fmt::arg("signalStrength", signal_strength_), fmt::arg("signaldBm", signal_strength_dbm_), fmt::arg("signalStrength", signal_strength_),
fmt::arg("signalStrengthApp", signal_strength_app_), fmt::arg("ifname", ifname_), fmt::arg("signalStrengthApp", signal_strength_app_), fmt::arg("ifname", ifname_),
fmt::arg("netmask", netmask_), fmt::arg("ipaddr", ipaddr_), fmt::arg("gwaddr", gwaddr_), fmt::arg("netmask", netmask_), fmt::arg("netmask6", netmask6_),
fmt::arg("cidr", cidr_), fmt::arg("frequency", fmt::format("{:.1f}", frequency_)), fmt::arg("ipaddr", final_ipaddr_), fmt::arg("gwaddr", gwaddr_), fmt::arg("cidr", cidr_),
fmt::arg("cidr6", cidr6_), fmt::arg("frequency", fmt::format("{:.1f}", frequency_)),
fmt::arg("icon", getIcon(signal_strength_, state_)), fmt::arg("icon", getIcon(signal_strength_, state_)),
fmt::arg("bandwidthDownBits", pow_format(bandwidth_down * 8ull / interval_.count(), "b/s")), fmt::arg("bandwidthDownBits", pow_format(bandwidth_down * 8ull / interval_.count(), "b/s")),
fmt::arg("bandwidthUpBits", pow_format(bandwidth_up * 8ull / interval_.count(), "b/s")), fmt::arg("bandwidthUpBits", pow_format(bandwidth_up * 8ull / interval_.count(), "b/s")),
@ -352,8 +372,9 @@ auto waybar::modules::Network::update() -> void {
fmt::runtime(tooltip_format), fmt::arg("essid", essid_), fmt::arg("bssid", bssid_), fmt::runtime(tooltip_format), fmt::arg("essid", essid_), fmt::arg("bssid", bssid_),
fmt::arg("signaldBm", signal_strength_dbm_), fmt::arg("signalStrength", signal_strength_), fmt::arg("signaldBm", signal_strength_dbm_), fmt::arg("signalStrength", signal_strength_),
fmt::arg("signalStrengthApp", signal_strength_app_), fmt::arg("ifname", ifname_), fmt::arg("signalStrengthApp", signal_strength_app_), fmt::arg("ifname", ifname_),
fmt::arg("netmask", netmask_), fmt::arg("ipaddr", ipaddr_), fmt::arg("gwaddr", gwaddr_), fmt::arg("netmask", netmask_), fmt::arg("netmask6", netmask6_),
fmt::arg("cidr", cidr_), fmt::arg("frequency", fmt::format("{:.1f}", frequency_)), fmt::arg("ipaddr", final_ipaddr_), fmt::arg("gwaddr", gwaddr_), fmt::arg("cidr", cidr_),
fmt::arg("cidr6", cidr6_), fmt::arg("frequency", fmt::format("{:.1f}", frequency_)),
fmt::arg("icon", getIcon(signal_strength_, state_)), fmt::arg("icon", getIcon(signal_strength_, state_)),
fmt::arg("bandwidthDownBits", fmt::arg("bandwidthDownBits",
pow_format(bandwidth_down * 8ull / interval_.count(), "b/s")), pow_format(bandwidth_down * 8ull / interval_.count(), "b/s")),
@ -394,10 +415,13 @@ void waybar::modules::Network::clearIface() {
essid_.clear(); essid_.clear();
bssid_.clear(); bssid_.clear();
ipaddr_.clear(); ipaddr_.clear();
ipaddr6_.clear();
gwaddr_.clear(); gwaddr_.clear();
netmask_.clear(); netmask_.clear();
netmask6_.clear();
carrier_ = false; carrier_ = false;
cidr_ = 0; cidr_ = 0;
cidr6_ = 0;
signal_strength_dbm_ = 0; signal_strength_dbm_ = 0;
signal_strength_ = 0; signal_strength_ = 0;
signal_strength_app_.clear(); signal_strength_app_.clear();
@ -521,7 +545,6 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
if (ifa->ifa_scope >= RT_SCOPE_LINK) { if (ifa->ifa_scope >= RT_SCOPE_LINK) {
return NL_OK; return NL_OK;
} }
for (; RTA_OK(ifa_rta, attrlen); ifa_rta = RTA_NEXT(ifa_rta, attrlen)) { for (; RTA_OK(ifa_rta, attrlen); ifa_rta = RTA_NEXT(ifa_rta, attrlen)) {
switch (ifa_rta->rta_type) { switch (ifa_rta->rta_type) {
case IFA_ADDRESS: case IFA_ADDRESS:
@ -529,8 +552,20 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
case IFA_LOCAL: case IFA_LOCAL:
char ipaddr[INET6_ADDRSTRLEN]; char ipaddr[INET6_ADDRSTRLEN];
if (!is_del_event) { if (!is_del_event) {
net->ipaddr_ = inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta), ipaddr, sizeof(ipaddr)); if ((net->addr_pref_ == ip_addr_pref::IPV4 ||
net->cidr_ = ifa->ifa_prefixlen; net->addr_pref_ == ip_addr_pref::IPV4_6) &&
net->cidr_ == 0 && ifa->ifa_family == AF_INET) {
net->ipaddr_ =
inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta), ipaddr, sizeof(ipaddr));
net->cidr_ = ifa->ifa_prefixlen;
} else if ((net->addr_pref_ == ip_addr_pref::IPV6 ||
net->addr_pref_ == ip_addr_pref::IPV4_6) &&
net->cidr6_ == 0 && ifa->ifa_family == AF_INET6) {
net->ipaddr6_ =
inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta), ipaddr, sizeof(ipaddr));
net->cidr6_ = ifa->ifa_prefixlen;
}
switch (ifa->ifa_family) { switch (ifa->ifa_family) {
case AF_INET: { case AF_INET: {
struct in_addr netmask; struct in_addr netmask;
@ -538,21 +573,24 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
net->netmask_ = inet_ntop(ifa->ifa_family, &netmask, ipaddr, sizeof(ipaddr)); net->netmask_ = inet_ntop(ifa->ifa_family, &netmask, ipaddr, sizeof(ipaddr));
} }
case AF_INET6: { case AF_INET6: {
struct in6_addr netmask; struct in6_addr netmask6;
for (int i = 0; i < 16; i++) { for (int i = 0; i < 16; i++) {
int v = (i + 1) * 8 - ifa->ifa_prefixlen; int v = (i + 1) * 8 - ifa->ifa_prefixlen;
if (v < 0) v = 0; if (v < 0) v = 0;
if (v > 8) v = 8; if (v > 8) v = 8;
netmask.s6_addr[i] = ~0 << v; netmask6.s6_addr[i] = ~0 << v;
} }
net->netmask_ = inet_ntop(ifa->ifa_family, &netmask, ipaddr, sizeof(ipaddr)); net->netmask6_ = inet_ntop(ifa->ifa_family, &netmask6, ipaddr, sizeof(ipaddr));
} }
} }
spdlog::debug("network: {}, new addr {}/{}", net->ifname_, net->ipaddr_, net->cidr_); spdlog::debug("network: {}, new addr {}/{}", net->ifname_, net->ipaddr_, net->cidr_);
} else { } else {
net->ipaddr_.clear(); net->ipaddr_.clear();
net->ipaddr6_.clear();
net->cidr_ = 0; net->cidr_ = 0;
net->cidr6_ = 0;
net->netmask_.clear(); net->netmask_.clear();
net->netmask6_.clear();
spdlog::debug("network: {} addr deleted {}/{}", net->ifname_, spdlog::debug("network: {} addr deleted {}/{}", net->ifname_,
inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta), ipaddr, sizeof(ipaddr)), inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta), ipaddr, sizeof(ipaddr)),
ifa->ifa_prefixlen); ifa->ifa_prefixlen);

View File

@ -9,7 +9,7 @@
namespace waybar::modules::niri { namespace waybar::modules::niri {
Language::Language(const std::string &id, const Bar &bar, const Json::Value &config) Language::Language(const std::string &id, const Bar &bar, const Json::Value &config)
: ALabel(config, "language", id, "{}", 0, true), bar_(bar) { : ALabel(config, "language", id, "{}", 0, false), bar_(bar) {
label_.hide(); label_.hide();
if (!gIPC) gIPC = std::make_unique<IPC>(); if (!gIPC) gIPC = std::make_unique<IPC>();

View File

@ -15,13 +15,14 @@ using util::PipewireBackend::PRIVACY_NODE_TYPE_AUDIO_OUTPUT;
using util::PipewireBackend::PRIVACY_NODE_TYPE_NONE; using util::PipewireBackend::PRIVACY_NODE_TYPE_NONE;
using util::PipewireBackend::PRIVACY_NODE_TYPE_VIDEO_INPUT; using util::PipewireBackend::PRIVACY_NODE_TYPE_VIDEO_INPUT;
Privacy::Privacy(const std::string& id, const Json::Value& config, const std::string& pos) Privacy::Privacy(const std::string& id, const Json::Value& config, Gtk::Orientation orientation,
const std::string& pos)
: AModule(config, "privacy", id), : AModule(config, "privacy", id),
nodes_screenshare(), nodes_screenshare(),
nodes_audio_in(), nodes_audio_in(),
nodes_audio_out(), nodes_audio_out(),
visibility_conn(), visibility_conn(),
box_(Gtk::ORIENTATION_HORIZONTAL, 0) { box_(orientation, 0) {
box_.set_name(name_); box_.set_name(name_);
event_box_.add(box_); event_box_.add(box_);
@ -67,8 +68,8 @@ Privacy::Privacy(const std::string& id, const Json::Value& config, const std::st
auto iter = typeMap.find(type); auto iter = typeMap.find(type);
if (iter != typeMap.end()) { if (iter != typeMap.end()) {
auto& [nodePtr, nodeType] = iter->second; auto& [nodePtr, nodeType] = iter->second;
auto* item = Gtk::make_managed<PrivacyItem>(module, nodeType, nodePtr, pos, iconSize, auto* item = Gtk::make_managed<PrivacyItem>(module, nodeType, nodePtr, orientation, pos,
transition_duration); iconSize, transition_duration);
box_.add(*item); box_.add(*item);
} }
} }

View File

@ -11,8 +11,9 @@
namespace waybar::modules::privacy { namespace waybar::modules::privacy {
PrivacyItem::PrivacyItem(const Json::Value &config_, enum PrivacyNodeType privacy_type_, PrivacyItem::PrivacyItem(const Json::Value &config_, enum PrivacyNodeType privacy_type_,
std::list<PrivacyNodeInfo *> *nodes_, const std::string &pos, std::list<PrivacyNodeInfo *> *nodes_, Gtk::Orientation orientation,
const uint icon_size, const uint transition_duration) const std::string &pos, const uint icon_size,
const uint transition_duration)
: Gtk::Revealer(), : Gtk::Revealer(),
privacy_type(privacy_type_), privacy_type(privacy_type_),
nodes(nodes_), nodes(nodes_),
@ -40,16 +41,24 @@ PrivacyItem::PrivacyItem(const Json::Value &config_, enum PrivacyNodeType privac
// Set the reveal transition to not look weird when sliding in // Set the reveal transition to not look weird when sliding in
if (pos == "modules-left") { if (pos == "modules-left") {
set_transition_type(Gtk::REVEALER_TRANSITION_TYPE_SLIDE_RIGHT); set_transition_type(orientation == Gtk::ORIENTATION_HORIZONTAL
? Gtk::REVEALER_TRANSITION_TYPE_SLIDE_RIGHT
: Gtk::REVEALER_TRANSITION_TYPE_SLIDE_DOWN);
} else if (pos == "modules-center") { } else if (pos == "modules-center") {
set_transition_type(Gtk::REVEALER_TRANSITION_TYPE_CROSSFADE); set_transition_type(Gtk::REVEALER_TRANSITION_TYPE_CROSSFADE);
} else if (pos == "modules-right") { } else if (pos == "modules-right") {
set_transition_type(Gtk::REVEALER_TRANSITION_TYPE_SLIDE_LEFT); set_transition_type(orientation == Gtk::ORIENTATION_HORIZONTAL
? Gtk::REVEALER_TRANSITION_TYPE_SLIDE_LEFT
: Gtk::REVEALER_TRANSITION_TYPE_SLIDE_UP);
} }
set_transition_duration(transition_duration); set_transition_duration(transition_duration);
box_.set_name("privacy-item"); box_.set_name("privacy-item");
box_.add(icon_);
// We use `set_center_widget` instead of `add` to make sure the icon is
// centered even if the orientation is vertical
box_.set_center_widget(icon_);
icon_.set_pixel_size(icon_size); icon_.set_pixel_size(icon_size);
add(box_); add(box_);

View File

@ -5,10 +5,12 @@
#include <gtkmm/tooltip.h> #include <gtkmm/tooltip.h>
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
#include <filesystem>
#include <fstream> #include <fstream>
#include <map> #include <map>
#include "gdk/gdk.h" #include "gdk/gdk.h"
#include "modules/sni/icon_manager.hpp"
#include "util/format.hpp" #include "util/format.hpp"
#include "util/gtk_icon.hpp" #include "util/gtk_icon.hpp"
@ -138,6 +140,7 @@ void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) {
category = get_variant<std::string>(value); category = get_variant<std::string>(value);
} else if (name == "Id") { } else if (name == "Id") {
id = get_variant<std::string>(value); id = get_variant<std::string>(value);
setCustomIcon(id);
} else if (name == "Title") { } else if (name == "Title") {
title = get_variant<std::string>(value); title = get_variant<std::string>(value);
if (tooltip.text.empty()) { if (tooltip.text.empty()) {
@ -199,6 +202,19 @@ void Item::setStatus(const Glib::ustring& value) {
style->add_class(lower); style->add_class(lower);
} }
void Item::setCustomIcon(const std::string& id) {
std::string custom_icon = IconManager::instance().getIconForApp(id);
if (!custom_icon.empty()) {
if (std::filesystem::exists(custom_icon)) {
Glib::RefPtr<Gdk::Pixbuf> custom_pixbuf = Gdk::Pixbuf::create_from_file(custom_icon);
icon_name = ""; // icon_name has priority over pixmap
icon_pixmap = custom_pixbuf;
} else { // if file doesn't exist it's most likely an icon_name
icon_name = custom_icon;
}
}
}
void Item::getUpdatedProperties() { void Item::getUpdatedProperties() {
auto params = Glib::VariantContainerBase::create_tuple( auto params = Glib::VariantContainerBase::create_tuple(
{Glib::Variant<Glib::ustring>::create(SNI_INTERFACE_NAME)}); {Glib::Variant<Glib::ustring>::create(SNI_INTERFACE_NAME)});

View File

@ -2,6 +2,8 @@
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
#include "modules/sni/icon_manager.hpp"
namespace waybar::modules::SNI { namespace waybar::modules::SNI {
Tray::Tray(const std::string& id, const Bar& bar, const Json::Value& config) Tray::Tray(const std::string& id, const Bar& bar, const Json::Value& config)
@ -20,6 +22,9 @@ Tray::Tray(const std::string& id, const Bar& bar, const Json::Value& config)
box_.set_spacing(config_["spacing"].asUInt()); box_.set_spacing(config_["spacing"].asUInt());
} }
nb_hosts_ += 1; nb_hosts_ += 1;
if (config_["icons"].isObject()) {
IconManager::instance().setIconsConfig(config_["icons"]);
}
dp.emit(); dp.emit();
} }

View File

@ -494,16 +494,34 @@ std::string Workspaces::trimWorkspaceName(std::string name) {
return name; return name;
} }
bool is_focused_recursive(const Json::Value& node) {
// If a workspace has a focused container then get_tree will say
// that the workspace itself isn't focused. Therefore we need to
// check if any of its nodes are focused as well.
// some layouts like tabbed have many nested nodes
// all nested nodes must be checked for focused flag
if (node["focused"].asBool()) {
return true;
}
for (const auto& child : node["nodes"]) {
if (is_focused_recursive(child)) {
return true;
}
}
for (const auto& child : node["floating_nodes"]) {
if (is_focused_recursive(child)) {
return true;
}
}
return false;
}
void Workspaces::onButtonReady(const Json::Value &node, Gtk::Button &button) { void Workspaces::onButtonReady(const Json::Value &node, Gtk::Button &button) {
if (config_["current-only"].asBool()) { if (config_["current-only"].asBool()) {
// If a workspace has a focused container then get_tree will say if (is_focused_recursive(node)) {
// that the workspace itself isn't focused. Therefore we need to
// check if any of its nodes are focused as well.
bool focused = node["focused"].asBool() ||
std::any_of(node["nodes"].begin(), node["nodes"].end(),
[](const auto &child) { return child["focused"].asBool(); });
if (focused) {
button.show(); button.show();
} else { } else {
button.hide(); button.hide();

View File

@ -114,13 +114,17 @@ float waybar::modules::Temperature::getTemperature() {
auto zone = config_["thermal-zone"].isInt() ? config_["thermal-zone"].asInt() : 0; auto zone = config_["thermal-zone"].isInt() ? config_["thermal-zone"].asInt() : 0;
if (sysctlbyname(fmt::format("hw.acpi.thermal.tz{}.temperature", zone).c_str(), &temp, &size, // First, try with dev.cpu
NULL, 0) != 0) { if ((sysctlbyname(fmt::format("dev.cpu.{}.temperature", zone).c_str(), &temp, &size, NULL, 0) ==
throw std::runtime_error(fmt::format( 0) ||
"sysctl hw.acpi.thermal.tz{}.temperature or dev.cpu.{}.temperature failed", zone, zone)); (sysctlbyname(fmt::format("hw.acpi.thermal.tz{}.temperature", zone).c_str(), &temp, &size,
NULL, 0) == 0)) {
auto temperature_c = ((float)temp - 2732) / 10;
return temperature_c;
} }
auto temperature_c = ((float)temp - 2732) / 10;
return temperature_c; throw std::runtime_error(fmt::format(
"sysctl hw.acpi.thermal.tz{}.temperature and dev.cpu.{}.temperature failed", zone, zone));
#else // Linux #else // Linux
std::ifstream temp(file_path_); std::ifstream temp(file_path_);
@ -148,4 +152,4 @@ bool waybar::modules::Temperature::isWarning(uint16_t temperature_c) {
bool waybar::modules::Temperature::isCritical(uint16_t temperature_c) { bool waybar::modules::Temperature::isCritical(uint16_t temperature_c) {
return config_["critical-threshold"].isInt() && return config_["critical-threshold"].isInt() &&
temperature_c >= config_["critical-threshold"].asInt(); temperature_c >= config_["critical-threshold"].asInt();
} }

View File

@ -358,10 +358,12 @@ void UPower::resetDevices() {
void UPower::setDisplayDevice() { void UPower::setDisplayDevice() {
std::lock_guard<std::mutex> guard{mutex_}; std::lock_guard<std::mutex> guard{mutex_};
if (nativePath_.empty() && model_.empty()) { if (upDevice_.upDevice != NULL) {
// Unref current upDevice g_object_unref(upDevice_.upDevice);
if (upDevice_.upDevice != NULL) g_object_unref(upDevice_.upDevice); upDevice_.upDevice = NULL;
}
if (nativePath_.empty() && model_.empty()) {
upDevice_.upDevice = up_client_get_display_device(upClient_); upDevice_.upDevice = up_client_get_display_device(upClient_);
getUpDeviceInfo(upDevice_); getUpDeviceInfo(upDevice_);
} else { } else {
@ -386,7 +388,6 @@ void UPower::setDisplayDevice() {
} }
// Unref current upDevice if it exists // Unref current upDevice if it exists
if (displayDevice.upDevice != NULL) { if (displayDevice.upDevice != NULL) {
if (thisPtr->upDevice_.upDevice != NULL) g_object_unref(thisPtr->upDevice_.upDevice);
thisPtr->upDevice_ = displayDevice; thisPtr->upDevice_ = displayDevice;
} }
}, },

View File

@ -4,6 +4,8 @@
bool isValidNodeId(uint32_t id) { return id > 0 && id < G_MAXUINT32; } bool isValidNodeId(uint32_t id) { return id > 0 && id < G_MAXUINT32; }
std::list<waybar::modules::Wireplumber*> waybar::modules::Wireplumber::modules;
waybar::modules::Wireplumber::Wireplumber(const std::string& id, const Json::Value& config) waybar::modules::Wireplumber::Wireplumber(const std::string& id, const Json::Value& config)
: ALabel(config, "wireplumber", id, "{volume}%"), : ALabel(config, "wireplumber", id, "{volume}%"),
wp_core_(nullptr), wp_core_(nullptr),
@ -16,22 +18,28 @@ waybar::modules::Wireplumber::Wireplumber(const std::string& id, const Json::Val
muted_(false), muted_(false),
volume_(0.0), volume_(0.0),
min_step_(0.0), min_step_(0.0),
node_id_(0) { node_id_(0),
type_(nullptr) {
waybar::modules::Wireplumber::modules.push_back(this);
wp_init(WP_INIT_PIPEWIRE); wp_init(WP_INIT_PIPEWIRE);
wp_core_ = wp_core_new(nullptr, nullptr, nullptr); wp_core_ = wp_core_new(nullptr, nullptr, nullptr);
apis_ = g_ptr_array_new_with_free_func(g_object_unref); apis_ = g_ptr_array_new_with_free_func(g_object_unref);
om_ = wp_object_manager_new(); om_ = wp_object_manager_new();
prepare(); type_ = g_strdup(config_["node-type"].isString() ? config_["node-type"].asString().c_str()
: "Audio/Sink");
spdlog::debug("[{}]: connecting to pipewire...", name_); prepare(this);
spdlog::debug("[{}]: connecting to pipewire: '{}'...", name_, type_);
if (wp_core_connect(wp_core_) == 0) { if (wp_core_connect(wp_core_) == 0) {
spdlog::error("[{}]: Could not connect to PipeWire", name_); spdlog::error("[{}]: Could not connect to PipeWire: '{}'", name_, type_);
throw std::runtime_error("Could not connect to PipeWire\n"); throw std::runtime_error("Could not connect to PipeWire\n");
} }
spdlog::debug("[{}]: connected!", name_); spdlog::debug("[{}]: {} connected!", name_, type_);
g_signal_connect_swapped(om_, "installed", (GCallback)onObjectManagerInstalled, this); g_signal_connect_swapped(om_, "installed", (GCallback)onObjectManagerInstalled, this);
@ -39,6 +47,7 @@ waybar::modules::Wireplumber::Wireplumber(const std::string& id, const Json::Val
} }
waybar::modules::Wireplumber::~Wireplumber() { waybar::modules::Wireplumber::~Wireplumber() {
waybar::modules::Wireplumber::modules.remove(this);
wp_core_disconnect(wp_core_); wp_core_disconnect(wp_core_);
g_clear_pointer(&apis_, g_ptr_array_unref); g_clear_pointer(&apis_, g_ptr_array_unref);
g_clear_object(&om_); g_clear_object(&om_);
@ -46,13 +55,15 @@ waybar::modules::Wireplumber::~Wireplumber() {
g_clear_object(&mixer_api_); g_clear_object(&mixer_api_);
g_clear_object(&def_nodes_api_); g_clear_object(&def_nodes_api_);
g_free(default_node_name_); g_free(default_node_name_);
g_free(type_);
} }
void waybar::modules::Wireplumber::updateNodeName(waybar::modules::Wireplumber* self, uint32_t id) { void waybar::modules::Wireplumber::updateNodeName(waybar::modules::Wireplumber* self, uint32_t id) {
spdlog::debug("[{}]: updating node name with node.id {}", self->name_, id); spdlog::debug("[{}]: updating '{}' node name with node.id {}", self->name_, self->type_, id);
if (!isValidNodeId(id)) { if (!isValidNodeId(id)) {
spdlog::warn("[{}]: '{}' is not a valid node ID. Ignoring node name update.", self->name_, id); spdlog::warn("[{}]: '{}' is not a valid node ID. Ignoring '{}' node name update.", self->name_,
id, self->type_);
return; return;
} }
@ -80,7 +91,7 @@ void waybar::modules::Wireplumber::updateNodeName(waybar::modules::Wireplumber*
self->node_name_ = nick != nullptr ? nick self->node_name_ = nick != nullptr ? nick
: description != nullptr ? description : description != nullptr ? description
: "Unknown node name"; : "Unknown node name";
spdlog::debug("[{}]: Updating node name to: {}", self->name_, self->node_name_); spdlog::debug("[{}]: Updating '{}' node name to: {}", self->name_, self->type_, self->node_name_);
} }
void waybar::modules::Wireplumber::updateVolume(waybar::modules::Wireplumber* self, uint32_t id) { void waybar::modules::Wireplumber::updateVolume(waybar::modules::Wireplumber* self, uint32_t id) {
@ -88,7 +99,8 @@ void waybar::modules::Wireplumber::updateVolume(waybar::modules::Wireplumber* se
GVariant* variant = nullptr; GVariant* variant = nullptr;
if (!isValidNodeId(id)) { if (!isValidNodeId(id)) {
spdlog::error("[{}]: '{}' is not a valid node ID. Ignoring volume update.", self->name_, id); spdlog::error("[{}]: '{}' is not a valid '{}' node ID. Ignoring volume update.", self->name_,
id, self->type_);
return; return;
} }
@ -109,13 +121,22 @@ void waybar::modules::Wireplumber::updateVolume(waybar::modules::Wireplumber* se
} }
void waybar::modules::Wireplumber::onMixerChanged(waybar::modules::Wireplumber* self, uint32_t id) { void waybar::modules::Wireplumber::onMixerChanged(waybar::modules::Wireplumber* self, uint32_t id) {
spdlog::debug("[{}]: (onMixerChanged) - id: {}", self->name_, id);
g_autoptr(WpNode) node = static_cast<WpNode*>(wp_object_manager_lookup( g_autoptr(WpNode) node = static_cast<WpNode*>(wp_object_manager_lookup(
self->om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_G_PROPERTY, "bound-id", "=u", id, nullptr)); self->om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_G_PROPERTY, "bound-id", "=u", id, nullptr));
if (node == nullptr) { if (node == nullptr) {
spdlog::warn("[{}]: (onMixerChanged) - Object with id {} not found", self->name_, id); // log a warning only if no other widget is targeting the id.
// this reduces log spam when multiple instances of the module are used on different node types.
if (id != self->node_id_) {
for (auto const& module : waybar::modules::Wireplumber::modules) {
if (module->node_id_ == id) {
return;
}
}
}
spdlog::warn("[{}]: (onMixerChanged: {}) - Object with id {} not found", self->name_,
self->type_, id);
return; return;
} }
@ -123,26 +144,27 @@ void waybar::modules::Wireplumber::onMixerChanged(waybar::modules::Wireplumber*
if (self->node_id_ != id) { if (self->node_id_ != id) {
spdlog::debug( spdlog::debug(
"[{}]: (onMixerChanged) - ignoring mixer update for node: id: {}, name: {} as it is not " "[{}]: (onMixerChanged: {}) - ignoring mixer update for node: id: {}, name: {} as it is "
"the default node: {} with id: {}", "not the default node: {} with id: {}",
self->name_, id, name, self->default_node_name_, self->node_id_); self->name_, self->type_, id, name, self->default_node_name_, self->node_id_);
return; return;
} }
spdlog::debug("[{}]: (onMixerChanged) - Need to update volume for node with id {} and name {}", spdlog::debug(
self->name_, id, name); "[{}]: (onMixerChanged: {}) - Need to update volume for node with id {} and name {}",
self->name_, self->type_, id, name);
updateVolume(self, id); updateVolume(self, id);
} }
void waybar::modules::Wireplumber::onDefaultNodesApiChanged(waybar::modules::Wireplumber* self) { void waybar::modules::Wireplumber::onDefaultNodesApiChanged(waybar::modules::Wireplumber* self) {
spdlog::debug("[{}]: (onDefaultNodesApiChanged)", self->name_); spdlog::debug("[{}]: (onDefaultNodesApiChanged: {})", self->name_, self->type_);
uint32_t defaultNodeId; uint32_t defaultNodeId;
g_signal_emit_by_name(self->def_nodes_api_, "get-default-node", "Audio/Sink", &defaultNodeId); g_signal_emit_by_name(self->def_nodes_api_, "get-default-node", self->type_, &defaultNodeId);
if (!isValidNodeId(defaultNodeId)) { if (!isValidNodeId(defaultNodeId)) {
spdlog::warn("[{}]: '{}' is not a valid node ID. Ignoring node change.", self->name_, spdlog::warn("[{}]: '{}' is not a valid node ID. Ignoring '{}' node change.", self->name_,
defaultNodeId); defaultNodeId, self->type_);
return; return;
} }
@ -151,8 +173,8 @@ void waybar::modules::Wireplumber::onDefaultNodesApiChanged(waybar::modules::Wir
"=u", defaultNodeId, nullptr)); "=u", defaultNodeId, nullptr));
if (node == nullptr) { if (node == nullptr) {
spdlog::warn("[{}]: (onDefaultNodesApiChanged) - Object with id {} not found", self->name_, spdlog::warn("[{}]: (onDefaultNodesApiChanged: {}) - Object with id {} not found", self->name_,
defaultNodeId); self->type_, defaultNodeId);
return; return;
} }
@ -160,21 +182,22 @@ void waybar::modules::Wireplumber::onDefaultNodesApiChanged(waybar::modules::Wir
wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(node), "node.name"); wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(node), "node.name");
spdlog::debug( spdlog::debug(
"[{}]: (onDefaultNodesApiChanged) - got the following default node: Node(name: {}, id: {})", "[{}]: (onDefaultNodesApiChanged: {}) - got the following default node: Node(name: {}, id: "
self->name_, defaultNodeName, defaultNodeId); "{})",
self->name_, self->type_, defaultNodeName, defaultNodeId);
if (g_strcmp0(self->default_node_name_, defaultNodeName) == 0 && if (g_strcmp0(self->default_node_name_, defaultNodeName) == 0 &&
self->node_id_ == defaultNodeId) { self->node_id_ == defaultNodeId) {
spdlog::debug( spdlog::debug(
"[{}]: (onDefaultNodesApiChanged) - Default node has not changed. Node(name: {}, id: {}). " "[{}]: (onDefaultNodesApiChanged: {}) - Default node has not changed. Node(name: {}, id: "
"Ignoring.", "{}). Ignoring.",
self->name_, self->default_node_name_, defaultNodeId); self->name_, self->type_, self->default_node_name_, defaultNodeId);
return; return;
} }
spdlog::debug( spdlog::debug(
"[{}]: (onDefaultNodesApiChanged) - Default node changed to -> Node(name: {}, id: {})", "[{}]: (onDefaultNodesApiChanged: {}) - Default node changed to -> Node(name: {}, id: {})",
self->name_, defaultNodeName, defaultNodeId); self->name_, self->type_, defaultNodeName, defaultNodeId);
g_free(self->default_node_name_); g_free(self->default_node_name_);
self->default_node_name_ = g_strdup(defaultNodeName); self->default_node_name_ = g_strdup(defaultNodeName);
@ -200,13 +223,14 @@ void waybar::modules::Wireplumber::onObjectManagerInstalled(waybar::modules::Wir
throw std::runtime_error("Mixer api is not loaded\n"); throw std::runtime_error("Mixer api is not loaded\n");
} }
g_signal_emit_by_name(self->def_nodes_api_, "get-default-configured-node-name", "Audio/Sink", g_signal_emit_by_name(self->def_nodes_api_, "get-default-configured-node-name", self->type_,
&self->default_node_name_); &self->default_node_name_);
g_signal_emit_by_name(self->def_nodes_api_, "get-default-node", "Audio/Sink", &self->node_id_); g_signal_emit_by_name(self->def_nodes_api_, "get-default-node", self->type_, &self->node_id_);
if (self->default_node_name_ != nullptr) { if (self->default_node_name_ != nullptr) {
spdlog::debug("[{}]: (onObjectManagerInstalled) - default configured node name: {} and id: {}", spdlog::debug(
self->name_, self->default_node_name_, self->node_id_); "[{}]: (onObjectManagerInstalled: {}) - default configured node name: {} and id: {}",
self->name_, self->type_, self->default_node_name_, self->node_id_);
} }
updateVolume(self, self->node_id_); updateVolume(self, self->node_id_);
@ -243,10 +267,10 @@ void waybar::modules::Wireplumber::activatePlugins() {
} }
} }
void waybar::modules::Wireplumber::prepare() { void waybar::modules::Wireplumber::prepare(waybar::modules::Wireplumber* self) {
spdlog::debug("[{}]: preparing object manager", name_); spdlog::debug("[{}]: preparing object manager: '{}'", name_, self->type_);
wp_object_manager_add_interest(om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_PW_PROPERTY, "media.class", wp_object_manager_add_interest(om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_PW_PROPERTY, "media.class",
"=s", "Audio/Sink", nullptr); "=s", self->type_, nullptr);
} }
void waybar::modules::Wireplumber::onDefaultNodesApiLoaded(WpObject* p, GAsyncResult* res, void waybar::modules::Wireplumber::onDefaultNodesApiLoaded(WpObject* p, GAsyncResult* res,
@ -275,7 +299,7 @@ void waybar::modules::Wireplumber::onMixerApiLoaded(WpObject* p, GAsyncResult* r
gboolean success = FALSE; gboolean success = FALSE;
g_autoptr(GError) error = nullptr; g_autoptr(GError) error = nullptr;
success = wp_core_load_component_finish(self->wp_core_, res, nullptr); success = wp_core_load_component_finish(self->wp_core_, res, &error);
if (success == FALSE) { if (success == FALSE) {
spdlog::error("[{}]: mixer API load failed", self->name_); spdlog::error("[{}]: mixer API load failed", self->name_);

View File

@ -383,8 +383,38 @@ std::string Task::state_string(bool shortened) const {
} }
void Task::handle_title(const char *title) { void Task::handle_title(const char *title) {
if (title_.empty()) {
spdlog::debug(fmt::format("Task ({}) setting title to {}", id_, title_));
} else {
spdlog::debug(fmt::format("Task ({}) overwriting title '{}' with '{}'", id_, title_, title));
}
title_ = title; title_ = title;
hide_if_ignored(); hide_if_ignored();
if (!with_icon_ && !with_name_ || app_info_) {
return;
}
set_app_info_from_app_id_list(title_);
name_ = app_info_ ? app_info_->get_display_name() : title;
if (!with_icon_) {
return;
}
int icon_size = config_["icon-size"].isInt() ? config_["icon-size"].asInt() : 16;
bool found = false;
for (auto &icon_theme : tbar_->icon_themes()) {
if (image_load_icon(icon_, icon_theme, app_info_, icon_size)) {
found = true;
break;
}
}
if (found)
icon_.show();
else
spdlog::debug("Couldn't find icon for {}", title_);
} }
void Task::set_minimize_hint() { void Task::set_minimize_hint() {

View File

@ -118,8 +118,8 @@ auto WorkspaceManager::sort_workspaces() -> void {
} }
} }
auto WorkspaceManager::register_manager(wl_registry *registry, uint32_t name, auto WorkspaceManager::register_manager(wl_registry *registry, uint32_t name, uint32_t version)
uint32_t version) -> void { -> void {
if (workspace_manager_) { if (workspace_manager_) {
spdlog::warn("Register workspace manager again although already registered!"); spdlog::warn("Register workspace manager again although already registered!");
return; return;

View File

@ -24,6 +24,8 @@ AudioBackend::AudioBackend(std::function<void()> on_updated_cb, private_construc
source_volume_(0), source_volume_(0),
source_muted_(false), source_muted_(false),
on_updated_cb_(std::move(on_updated_cb)) { on_updated_cb_(std::move(on_updated_cb)) {
// Initialize pa_volume_ with safe defaults
pa_cvolume_init(&pa_volume_);
mainloop_ = pa_threaded_mainloop_new(); mainloop_ = pa_threaded_mainloop_new();
if (mainloop_ == nullptr) { if (mainloop_ == nullptr) {
throw std::runtime_error("pa_mainloop_new() failed."); throw std::runtime_error("pa_mainloop_new() failed.");
@ -131,7 +133,12 @@ void AudioBackend::subscribeCb(pa_context *context, pa_subscription_event_type_t
void AudioBackend::volumeModifyCb(pa_context *c, int success, void *data) { void AudioBackend::volumeModifyCb(pa_context *c, int success, void *data) {
auto *backend = static_cast<AudioBackend *>(data); auto *backend = static_cast<AudioBackend *>(data);
if (success != 0) { if (success != 0) {
pa_context_get_sink_info_by_index(backend->context_, backend->sink_idx_, sinkInfoCb, data); if ((backend->context_ != nullptr) &&
pa_context_get_state(backend->context_) == PA_CONTEXT_READY) {
pa_context_get_sink_info_by_index(backend->context_, backend->sink_idx_, sinkInfoCb, data);
}
} else {
spdlog::debug("Volume modification failed");
} }
} }
@ -180,11 +187,20 @@ void AudioBackend::sinkInfoCb(pa_context * /*context*/, const pa_sink_info *i, i
} }
if (backend->current_sink_name_ == i->name) { if (backend->current_sink_name_ == i->name) {
backend->pa_volume_ = i->volume; // Safely copy the volume structure
float volume = if (pa_cvolume_valid(&i->volume) != 0) {
static_cast<float>(pa_cvolume_avg(&(backend->pa_volume_))) / float{PA_VOLUME_NORM}; backend->pa_volume_ = i->volume;
backend->sink_idx_ = i->index; float volume =
backend->volume_ = std::round(volume * 100.0F); static_cast<float>(pa_cvolume_avg(&(backend->pa_volume_))) / float{PA_VOLUME_NORM};
backend->sink_idx_ = i->index;
backend->volume_ = std::round(volume * 100.0F);
} else {
spdlog::error("Invalid volume structure received from PulseAudio");
// Initialize with safe defaults
pa_cvolume_init(&backend->pa_volume_);
backend->volume_ = 0;
}
backend->muted_ = i->mute != 0; backend->muted_ = i->mute != 0;
backend->desc_ = i->description; backend->desc_ = i->description;
backend->monitor_ = i->monitor_source_name; backend->monitor_ = i->monitor_source_name;
@ -230,43 +246,109 @@ void AudioBackend::serverInfoCb(pa_context *context, const pa_server_info *i, vo
} }
void AudioBackend::changeVolume(uint16_t volume, uint16_t min_volume, uint16_t max_volume) { void AudioBackend::changeVolume(uint16_t volume, uint16_t min_volume, uint16_t max_volume) {
double volume_tick = static_cast<double>(PA_VOLUME_NORM) / 100; // Early return if context is not ready
pa_cvolume pa_volume = pa_volume_; if ((context_ == nullptr) || pa_context_get_state(context_) != PA_CONTEXT_READY) {
spdlog::error("PulseAudio context not ready");
return;
}
// Prepare volume structure
pa_cvolume pa_volume;
pa_cvolume_init(&pa_volume);
// Use existing volume structure if valid, otherwise create a safe default
if ((pa_cvolume_valid(&pa_volume_) != 0) && (pa_channels_valid(pa_volume_.channels) != 0)) {
pa_volume = pa_volume_;
} else {
// Set stereo as a safe default
pa_volume.channels = 2;
spdlog::debug("Using default stereo volume structure");
}
// Set the volume safely
volume = std::clamp(volume, min_volume, max_volume); volume = std::clamp(volume, min_volume, max_volume);
pa_cvolume_set(&pa_volume, pa_volume_.channels, volume * volume_tick); pa_volume_t vol = volume * (static_cast<double>(PA_VOLUME_NORM) / 100);
// Set all channels to the same volume manually to avoid pa_cvolume_set
for (uint8_t i = 0; i < pa_volume.channels; i++) {
pa_volume.values[i] = vol;
}
// Apply the volume change
pa_threaded_mainloop_lock(mainloop_); pa_threaded_mainloop_lock(mainloop_);
pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume, volumeModifyCb, this); pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume, volumeModifyCb, this);
pa_threaded_mainloop_unlock(mainloop_); pa_threaded_mainloop_unlock(mainloop_);
} }
void AudioBackend::changeVolume(ChangeType change_type, double step, uint16_t max_volume) { void AudioBackend::changeVolume(ChangeType change_type, double step, uint16_t max_volume) {
double volume_tick = static_cast<double>(PA_VOLUME_NORM) / 100; // Early return if context is not ready
pa_volume_t change = volume_tick; if ((context_ == nullptr) || pa_context_get_state(context_) != PA_CONTEXT_READY) {
pa_cvolume pa_volume = pa_volume_; spdlog::error("PulseAudio context not ready");
return;
}
// Prepare volume structure
pa_cvolume pa_volume;
pa_cvolume_init(&pa_volume);
// Use existing volume structure if valid, otherwise create a safe default
if ((pa_cvolume_valid(&pa_volume_) != 0) && (pa_channels_valid(pa_volume_.channels) != 0)) {
pa_volume = pa_volume_;
} else {
// Set stereo as a safe default
pa_volume.channels = 2;
spdlog::debug("Using default stereo volume structure");
// Initialize all channels to current volume level
double volume_tick = static_cast<double>(PA_VOLUME_NORM) / 100;
pa_volume_t vol = volume_ * volume_tick;
for (uint8_t i = 0; i < pa_volume.channels; i++) {
pa_volume.values[i] = vol;
}
// No need to continue with volume change if we had to create a new structure
pa_threaded_mainloop_lock(mainloop_);
pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume, volumeModifyCb, this);
pa_threaded_mainloop_unlock(mainloop_);
return;
}
// Calculate volume change
double volume_tick = static_cast<double>(PA_VOLUME_NORM) / 100;
pa_volume_t change;
max_volume = std::min(max_volume, static_cast<uint16_t>(PA_VOLUME_UI_MAX)); max_volume = std::min(max_volume, static_cast<uint16_t>(PA_VOLUME_UI_MAX));
if (change_type == ChangeType::Increase) { if (change_type == ChangeType::Increase && volume_ < max_volume) {
if (volume_ < max_volume) { // Calculate how much to increase
if (volume_ + step > max_volume) { if (volume_ + step > max_volume) {
change = round((max_volume - volume_) * volume_tick); change = round((max_volume - volume_) * volume_tick);
} else { } else {
change = round(step * volume_tick); change = round(step * volume_tick);
}
pa_cvolume_inc(&pa_volume, change);
} }
} else if (change_type == ChangeType::Decrease) {
if (volume_ > 0) { // Manually increase each channel's volume
if (volume_ - step < 0) { for (uint8_t i = 0; i < pa_volume.channels; i++) {
change = round(volume_ * volume_tick); pa_volume.values[i] = std::min(pa_volume.values[i] + change, PA_VOLUME_MAX);
} else {
change = round(step * volume_tick);
}
pa_cvolume_dec(&pa_volume, change);
} }
} else if (change_type == ChangeType::Decrease && volume_ > 0) {
// Calculate how much to decrease
if (volume_ - step < 0) {
change = round(volume_ * volume_tick);
} else {
change = round(step * volume_tick);
}
// Manually decrease each channel's volume
for (uint8_t i = 0; i < pa_volume.channels; i++) {
pa_volume.values[i] = (pa_volume.values[i] > change) ? (pa_volume.values[i] - change) : 0;
}
} else {
// No change needed
return;
} }
// Apply the volume change
pa_threaded_mainloop_lock(mainloop_); pa_threaded_mainloop_lock(mainloop_);
pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume, volumeModifyCb, this); pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume, volumeModifyCb, this);
pa_threaded_mainloop_unlock(mainloop_); pa_threaded_mainloop_unlock(mainloop_);

View File

@ -150,6 +150,7 @@ BacklightBackend::BacklightBackend(std::chrono::milliseconds interval,
throw std::runtime_error("No backlight found"); throw std::runtime_error("No backlight found");
} }
#ifdef HAVE_LOGIN_PROXY
// Connect to the login interface // Connect to the login interface
login_proxy_ = Gio::DBus::Proxy::create_for_bus_sync( login_proxy_ = Gio::DBus::Proxy::create_for_bus_sync(
Gio::DBus::BusType::BUS_TYPE_SYSTEM, "org.freedesktop.login1", Gio::DBus::BusType::BUS_TYPE_SYSTEM, "org.freedesktop.login1",
@ -160,6 +161,7 @@ BacklightBackend::BacklightBackend(std::chrono::milliseconds interval,
Gio::DBus::BusType::BUS_TYPE_SYSTEM, "org.freedesktop.login1", Gio::DBus::BusType::BUS_TYPE_SYSTEM, "org.freedesktop.login1",
"/org/freedesktop/login1/session/self", "org.freedesktop.login1.Session"); "/org/freedesktop/login1/session/self", "org.freedesktop.login1.Session");
} }
#endif
udev_thread_ = [this] { udev_thread_ = [this] {
std::unique_ptr<udev, UdevDeleter> udev{udev_new()}; std::unique_ptr<udev, UdevDeleter> udev{udev_new()};

View File

@ -126,7 +126,7 @@ void PipewireBackend::handleRegistryEventGlobal(uint32_t id, uint32_t permission
if (proxy == nullptr) return; if (proxy == nullptr) return;
auto *pNodeInfo = (PrivacyNodeInfo *)pw_proxy_get_user_data(proxy); auto *pNodeInfo = (PrivacyNodeInfo *)pw_proxy_get_user_data(proxy);
new(pNodeInfo) PrivacyNodeInfo{}; new (pNodeInfo) PrivacyNodeInfo{};
pNodeInfo->id = id; pNodeInfo->id = id;
pNodeInfo->data = this; pNodeInfo->data = this;
pNodeInfo->type = mediaType; pNodeInfo->type = mediaType;

View File

@ -1,7 +1,7 @@
[wrap-file] [wrap-file]
directory = cava-0.10.3 directory = cava-0.10.4
source_url = https://github.com/LukashonakV/cava/archive/0.10.3.tar.gz source_url = https://github.com/LukashonakV/cava/archive/0.10.4.tar.gz
source_filename = cava-0.10.3.tar.gz source_filename = cava-0.10.4.tar.gz
source_hash = aab0a4ed3f999e8461ad9de63ef8a77f28b6b2011f7dd0c69ba81819d442f6f9 source_hash =7bc1c1f9535f2bcc5cd2ae8a2434a2e3a05f5670b1c96316df304137ffc65756
[provide] [provide]
cava = cava_dep cava = cava_dep