Merge branch 'master' of https://github.com/Alexays/Waybar
Some checks failed
clang-format / lint (push) Has been cancelled
freebsd / build (push) Has been cancelled
linux / build (c++20, alpine) (push) Has been cancelled
linux / build (c++20, archlinux) (push) Has been cancelled
linux / build (c++20, debian) (push) Has been cancelled
linux / build (c++20, fedora) (push) Has been cancelled
linux / build (c++20, gentoo) (push) Has been cancelled
linux / build (c++20, opensuse) (push) Has been cancelled
Nix-Tests / nix-flake-check (push) Has been cancelled

This commit is contained in:
2026-04-01 14:57:33 -07:00
82 changed files with 1467 additions and 512 deletions

1
.gitignore vendored
View File

@@ -52,3 +52,4 @@ result-*
.ccls-cache .ccls-cache
_codeql_detected_source_root _codeql_detected_source_root
heaptrack*

View File

@@ -35,6 +35,7 @@ class Custom : public ALabel {
std::string id_; std::string id_;
std::string alt_; std::string alt_;
std::string tooltip_; std::string tooltip_;
std::string last_tooltip_markup_;
const bool tooltip_format_enabled_; const bool tooltip_format_enabled_;
std::vector<std::string> class_; std::vector<std::string> class_;
int percentage_; int percentage_;

View File

@@ -22,7 +22,7 @@ class Disk : public ALabel {
std::string path_; std::string path_;
std::string unit_; std::string unit_;
float calc_specific_divisor(const std::string divisor); float calc_specific_divisor(const std::string& divisor);
}; };
} // namespace waybar::modules } // namespace waybar::modules

View File

@@ -26,7 +26,7 @@ class Gamemode : public AModule {
const std::string DEFAULT_FORMAT = "{glyph}"; const std::string DEFAULT_FORMAT = "{glyph}";
const std::string DEFAULT_FORMAT_ALT = "{glyph} {count}"; const std::string DEFAULT_FORMAT_ALT = "{glyph} {count}";
const std::string DEFAULT_TOOLTIP_FORMAT = "Games running: {count}"; const std::string DEFAULT_TOOLTIP_FORMAT = "Games running: {count}";
const std::string DEFAULT_GLYPH = ""; const std::string DEFAULT_GLYPH = "󰊴";
void appear(const Glib::RefPtr<Gio::DBus::Connection>& connection, const Glib::ustring& name, void appear(const Glib::RefPtr<Gio::DBus::Connection>& connection, const Glib::ustring& name,
const Glib::ustring& name_owner); const Glib::ustring& name_owner);

View File

@@ -1,5 +1,6 @@
#pragma once #pragma once
#include <atomic>
#include <filesystem> #include <filesystem>
#include <list> #include <list>
#include <mutex> #include <mutex>
@@ -43,10 +44,11 @@ class IPC {
std::thread ipcThread_; std::thread ipcThread_;
std::mutex callbackMutex_; std::mutex callbackMutex_;
std::mutex socketMutex_;
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 int socketfd_ = -1; // the hyprland socket file descriptor
pid_t socketOwnerPid_; pid_t socketOwnerPid_ = -1;
bool running_ = true; // the ipcThread will stop running when this is false std::atomic<bool> running_ = true; // the ipcThread will stop running when this is false
}; };
}; // namespace waybar::modules::hyprland }; // namespace waybar::modules::hyprland

View File

@@ -20,8 +20,8 @@ class Window : public waybar::AAppIconLabel, public EventHandler {
private: private:
struct Workspace { struct Workspace {
int id; int id = 0;
int windows; int windows = 0;
std::string last_window; std::string last_window;
std::string last_window_title; std::string last_window_title;
@@ -29,14 +29,14 @@ class Window : public waybar::AAppIconLabel, public EventHandler {
}; };
struct WindowData { struct WindowData {
bool floating; bool floating = false;
int monitor = -1; int monitor = -1;
std::string class_name; std::string class_name;
std::string initial_class_name; std::string initial_class_name;
std::string title; std::string title;
std::string initial_title; std::string initial_title;
bool fullscreen; bool fullscreen = false;
bool grouped; bool grouped = false;
static auto parse(const Json::Value&) -> WindowData; static auto parse(const Json::Value&) -> WindowData;
}; };
@@ -47,7 +47,7 @@ class Window : public waybar::AAppIconLabel, public EventHandler {
void queryActiveWorkspace(); void queryActiveWorkspace();
void setClass(const std::string&, bool enable); void setClass(const std::string&, bool enable);
bool separateOutputs_; bool separateOutputs_ = false;
std::mutex mutex_; std::mutex mutex_;
const Bar& bar_; const Bar& bar_;
util::JsonParser parser_; util::JsonParser parser_;
@@ -55,11 +55,11 @@ class Window : public waybar::AAppIconLabel, public EventHandler {
Workspace workspace_; Workspace workspace_;
std::string soloClass_; std::string soloClass_;
std::string lastSoloClass_; std::string lastSoloClass_;
bool solo_; bool solo_ = false;
bool allFloating_; bool allFloating_ = false;
bool swallowing_; bool swallowing_ = false;
bool fullscreen_; bool fullscreen_ = false;
bool focused_; bool focused_ = false;
IPC& m_ipc; IPC& m_ipc;
}; };

View File

@@ -40,10 +40,11 @@ struct WindowRepr {
class WindowCreationPayload { class WindowCreationPayload {
public: public:
WindowCreationPayload(std::string workspace_name, WindowAddress window_address, WindowCreationPayload(const std::string& workspace_name, WindowAddress window_address,
WindowRepr window_repr); WindowRepr window_repr);
WindowCreationPayload(std::string workspace_name, WindowAddress window_address, WindowCreationPayload(const std::string& workspace_name, WindowAddress window_address,
std::string window_class, std::string window_title, bool is_active); const std::string& window_class, const std::string& window_title,
bool is_active);
WindowCreationPayload(Json::Value const& client_data); WindowCreationPayload(Json::Value const& client_data);
int incrementTimeSpentUncreated(); int incrementTimeSpentUncreated();

View File

@@ -4,6 +4,7 @@
#include <gtkmm/enums.h> #include <gtkmm/enums.h>
#include <gtkmm/label.h> #include <gtkmm/label.h>
#include <json/value.h> #include <json/value.h>
#include <sigc++/connection.h>
#include <cstdint> #include <cstdint>
#include <map> #include <map>
@@ -59,7 +60,7 @@ class Workspaces : public AModule, public EventHandler {
enum class ActiveWindowPosition { NONE, FIRST, LAST }; enum class ActiveWindowPosition { NONE, FIRST, LAST };
auto activeWindowPosition() const -> ActiveWindowPosition { return m_activeWindowPosition; } auto activeWindowPosition() const -> ActiveWindowPosition { return m_activeWindowPosition; }
std::string getRewrite(std::string window_class, std::string window_title); std::string getRewrite(const std::string& window_class, const std::string& window_title);
std::string& getWindowSeparator() { return m_formatWindowSeparator; } std::string& getWindowSeparator() { return m_formatWindowSeparator; }
bool isWorkspaceIgnored(std::string const& workspace_name); bool isWorkspaceIgnored(std::string const& workspace_name);
@@ -208,6 +209,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;
sigc::connection m_scrollEventConnection_;
IPC& m_ipc; IPC& m_ipc;
}; };

View File

@@ -44,7 +44,7 @@ class MPD : public ALabel {
std::string getFilename() const; std::string getFilename() const;
void setLabel(); void setLabel();
std::string getStateIcon() const; std::string getStateIcon() const;
std::string getOptionIcon(std::string optionName, bool activated) const; std::string getOptionIcon(const std::string& optionName, bool activated) const;
// GUI-side methods // GUI-side methods
bool handlePlayPause(GdkEventButton* const&); bool handlePlayPause(GdkEventButton* const&);

View File

@@ -70,6 +70,7 @@ class Network : public ALabel {
unsigned long long bandwidth_down_total_{0}; unsigned long long bandwidth_down_total_{0};
unsigned long long bandwidth_up_total_{0}; unsigned long long bandwidth_up_total_{0};
std::chrono::steady_clock::time_point bandwidth_last_sample_time_;
std::string state_; std::string state_;
std::string essid_; std::string essid_;

View File

@@ -16,7 +16,7 @@ class Host {
public: public:
Host(const std::size_t id, const Json::Value&, const Bar&, Host(const std::size_t id, const Json::Value&, const Bar&,
const std::function<void(std::unique_ptr<Item>&)>&, const std::function<void(std::unique_ptr<Item>&)>&,
const std::function<void(std::unique_ptr<Item>&)>&); const std::function<void(std::unique_ptr<Item>&)>&, const std::function<void()>&);
~Host(); ~Host();
private: private:
@@ -28,9 +28,13 @@ class Host {
static void registerHost(GObject*, GAsyncResult*, gpointer); static void registerHost(GObject*, GAsyncResult*, gpointer);
static void itemRegistered(SnWatcher*, const gchar*, gpointer); static void itemRegistered(SnWatcher*, const gchar*, gpointer);
static void itemUnregistered(SnWatcher*, const gchar*, gpointer); static void itemUnregistered(SnWatcher*, const gchar*, gpointer);
void itemReady(Item&);
void itemInvalidated(Item&);
void removeItem(std::vector<std::unique_ptr<Item>>::iterator);
void clearItems();
std::tuple<std::string, std::string> getBusNameAndObjectPath(const std::string); std::tuple<std::string, std::string> getBusNameAndObjectPath(const std::string);
void addRegisteredItem(std::string service); void addRegisteredItem(const std::string& service);
std::vector<std::unique_ptr<Item>> items_; std::vector<std::unique_ptr<Item>> items_;
const std::string bus_name_; const std::string bus_name_;
@@ -43,6 +47,7 @@ class Host {
const Bar& bar_; const Bar& bar_;
const std::function<void(std::unique_ptr<Item>&)> on_add_; const std::function<void(std::unique_ptr<Item>&)> on_add_;
const std::function<void(std::unique_ptr<Item>&)> on_remove_; const std::function<void(std::unique_ptr<Item>&)> on_remove_;
const std::function<void()> on_update_;
}; };
} // namespace waybar::modules::SNI } // namespace waybar::modules::SNI

View File

@@ -11,6 +11,7 @@
#include <libdbusmenu-gtk/dbusmenu-gtk.h> #include <libdbusmenu-gtk/dbusmenu-gtk.h>
#include <sigc++/trackable.h> #include <sigc++/trackable.h>
#include <functional>
#include <set> #include <set>
#include <string_view> #include <string_view>
@@ -25,9 +26,13 @@ struct ToolTip {
class Item : public sigc::trackable { class Item : public sigc::trackable {
public: public:
Item(const std::string&, const std::string&, const Json::Value&, const Bar&); Item(const std::string&, const std::string&, const Json::Value&, const Bar&,
const std::function<void(Item&)>&, const std::function<void(Item&)>&,
const std::function<void()>&);
~Item(); ~Item();
bool isReady() const;
std::string bus_name; std::string bus_name;
std::string object_path; std::string object_path;
@@ -43,7 +48,9 @@ class Item : public sigc::trackable {
Glib::RefPtr<Gdk::Pixbuf> icon_pixmap; Glib::RefPtr<Gdk::Pixbuf> icon_pixmap;
Glib::RefPtr<Gtk::IconTheme> icon_theme; Glib::RefPtr<Gtk::IconTheme> icon_theme;
std::string overlay_icon_name; std::string overlay_icon_name;
Glib::RefPtr<Gdk::Pixbuf> overlay_icon_pixmap;
std::string attention_icon_name; std::string attention_icon_name;
Glib::RefPtr<Gdk::Pixbuf> attention_icon_pixmap;
std::string attention_movie_name; std::string attention_movie_name;
std::string icon_theme_path; std::string icon_theme_path;
std::string menu; std::string menu;
@@ -62,6 +69,8 @@ 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 setReady();
void invalidate();
void setCustomIcon(const std::string& id); void setCustomIcon(const std::string& id);
void getUpdatedProperties(); void getUpdatedProperties();
void processUpdatedProperties(Glib::RefPtr<Gio::AsyncResult>& result); void processUpdatedProperties(Glib::RefPtr<Gio::AsyncResult>& result);
@@ -69,8 +78,13 @@ class Item : public sigc::trackable {
const Glib::VariantContainerBase& arguments); const Glib::VariantContainerBase& arguments);
void updateImage(); void updateImage();
Glib::RefPtr<Gdk::Pixbuf> extractPixBuf(GVariant* variant); static Glib::RefPtr<Gdk::Pixbuf> extractPixBuf(GVariant* variant);
Glib::RefPtr<Gdk::Pixbuf> getIconPixbuf(); Glib::RefPtr<Gdk::Pixbuf> getIconPixbuf();
Glib::RefPtr<Gdk::Pixbuf> getAttentionIconPixbuf();
Glib::RefPtr<Gdk::Pixbuf> getOverlayIconPixbuf();
Glib::RefPtr<Gdk::Pixbuf> loadIconFromNameOrFile(const std::string& name, bool log_failure);
static Glib::RefPtr<Gdk::Pixbuf> overlayPixbufs(const Glib::RefPtr<Gdk::Pixbuf>&,
const Glib::RefPtr<Gdk::Pixbuf>&);
Glib::RefPtr<Gdk::Pixbuf> getIconByName(const std::string& name, int size); Glib::RefPtr<Gdk::Pixbuf> getIconByName(const std::string& name, int size);
double getScaledIconSize(); double getScaledIconSize();
static void onMenuDestroyed(Item* self, GObject* old_menu_pointer); static void onMenuDestroyed(Item* self, GObject* old_menu_pointer);
@@ -86,8 +100,13 @@ class Item : public sigc::trackable {
gdouble distance_scrolled_y_ = 0; gdouble distance_scrolled_y_ = 0;
// visibility of items with Status == Passive // visibility of items with Status == Passive
bool show_passive_ = false; bool show_passive_ = false;
bool ready_ = false;
Glib::ustring status_ = "active";
const Bar& bar_; const Bar& bar_;
const std::function<void(Item&)> on_ready_;
const std::function<void(Item&)> on_invalidate_;
const std::function<void()> on_updated_;
Glib::RefPtr<Gio::DBus::Proxy> proxy_; Glib::RefPtr<Gio::DBus::Proxy> proxy_;
Glib::RefPtr<Gio::Cancellable> cancellable_; Glib::RefPtr<Gio::Cancellable> cancellable_;

View File

@@ -19,6 +19,7 @@ class Tray : public AModule {
private: private:
void onAdd(std::unique_ptr<Item>& item); void onAdd(std::unique_ptr<Item>& item);
void onRemove(std::unique_ptr<Item>& item); void onRemove(std::unique_ptr<Item>& item);
void queueUpdate();
static inline std::size_t nb_hosts_ = 0; static inline std::size_t nb_hosts_ = 0;
bool show_passive_ = false; bool show_passive_ = false;

View File

@@ -37,7 +37,7 @@ class BarIpcClient {
void onModeUpdate(bool visible_by_modifier); void onModeUpdate(bool visible_by_modifier);
void onUrgencyUpdate(bool visible_by_urgency); void onUrgencyUpdate(bool visible_by_urgency);
void update(); void update();
bool isModuleEnabled(std::string name); bool isModuleEnabled(const std::string& name);
Bar& bar_; Bar& bar_;
util::JsonParser parser_; util::JsonParser parser_;

View File

@@ -13,6 +13,7 @@
#include "ipc.hpp" #include "ipc.hpp"
#include "util/SafeSignal.hpp" #include "util/SafeSignal.hpp"
#include "util/scoped_fd.hpp"
#include "util/sleeper_thread.hpp" #include "util/sleeper_thread.hpp"
namespace waybar::modules::sway { namespace waybar::modules::sway {
@@ -45,8 +46,8 @@ class Ipc {
struct ipc_response send(int fd, uint32_t type, const std::string& payload = ""); struct ipc_response send(int fd, uint32_t type, const std::string& payload = "");
struct ipc_response recv(int fd); struct ipc_response recv(int fd);
int fd_; util::ScopedFd fd_;
int fd_event_; util::ScopedFd fd_event_;
std::mutex mutex_; std::mutex mutex_;
util::SleeperThread thread_; util::SleeperThread thread_;
}; };

View File

@@ -47,7 +47,7 @@ class Language : public ALabel, public sigc::trackable {
void onEvent(const struct Ipc::ipc_response&); void onEvent(const struct Ipc::ipc_response&);
void onCmd(const struct Ipc::ipc_response&); void onCmd(const struct Ipc::ipc_response&);
auto set_current_layout(std::string current_layout) -> void; auto set_current_layout(const std::string& current_layout) -> void;
auto init_layouts_map(const std::vector<std::string>& used_layouts) -> void; auto init_layouts_map(const std::vector<std::string>& used_layouts) -> void;
const static std::string XKB_LAYOUT_NAMES_KEY; const static std::string XKB_LAYOUT_NAMES_KEY;

View File

@@ -27,7 +27,7 @@ class Workspaces : public AModule, public sigc::trackable {
static constexpr std::string_view persistent_workspace_switch_cmd_ = static constexpr std::string_view persistent_workspace_switch_cmd_ =
R"(workspace {} "{}"; move workspace to output "{}"; workspace {} "{}")"; R"(workspace {} "{}"; move workspace to output "{}"; workspace {} "{}")";
static int convertWorkspaceNameToNum(std::string name); static int convertWorkspaceNameToNum(const std::string& name);
static int windowRewritePriorityFunction(std::string const& window_rule); static int windowRewritePriorityFunction(std::string const& window_rule);
void onCmd(const struct Ipc::ipc_response&); void onCmd(const struct Ipc::ipc_response&);
@@ -40,7 +40,7 @@ class Workspaces : public AModule, public sigc::trackable {
std::string getIcon(const std::string&, const Json::Value&); std::string getIcon(const std::string&, const Json::Value&);
std::string getCycleWorkspace(std::vector<Json::Value>::iterator, bool prev) const; std::string getCycleWorkspace(std::vector<Json::Value>::iterator, bool prev) const;
uint16_t getWorkspaceIndex(const std::string& name) const; uint16_t getWorkspaceIndex(const std::string& name) const;
static std::string trimWorkspaceName(std::string); static std::string trimWorkspaceName(const std::string&);
bool handleScroll(GdkEventScroll* /*unused*/) override; bool handleScroll(GdkEventScroll* /*unused*/) override;
const Bar& bar_; const Bar& bar_;

View File

@@ -3,6 +3,7 @@
#include <giomm/dbusproxy.h> #include <giomm/dbusproxy.h>
#include <string> #include <string>
#include <vector>
#include "ALabel.hpp" #include "ALabel.hpp"
@@ -11,23 +12,42 @@ namespace waybar::modules {
class SystemdFailedUnits : public ALabel { class SystemdFailedUnits : public ALabel {
public: public:
SystemdFailedUnits(const std::string&, const Json::Value&); SystemdFailedUnits(const std::string&, const Json::Value&);
virtual ~SystemdFailedUnits(); virtual ~SystemdFailedUnits() = default;
auto update() -> void override; auto update() -> void override;
private: private:
bool hide_on_ok; struct FailedUnit {
std::string format_ok; std::string name;
std::string description;
std::string load_state;
std::string active_state;
std::string sub_state;
std::string scope;
};
bool update_pending; bool hide_on_ok_;
std::string system_state, user_state, overall_state; std::string format_ok_;
uint32_t nr_failed_system, nr_failed_user, nr_failed; std::string tooltip_format_;
std::string last_status; std::string tooltip_format_ok_;
Glib::RefPtr<Gio::DBus::Proxy> system_proxy, user_proxy; std::string tooltip_unit_format_;
bool update_pending_;
std::string system_state_, user_state_, overall_state_;
uint32_t nr_failed_system_, nr_failed_user_, nr_failed_;
std::string last_status_;
Glib::RefPtr<Gio::DBus::Proxy> system_props_proxy_, user_props_proxy_;
Glib::RefPtr<Gio::DBus::Proxy> system_manager_proxy_, user_manager_proxy_;
std::vector<FailedUnit> failed_units_;
void notify_cb(const Glib::ustring& sender_name, const Glib::ustring& signal_name, void notify_cb(const Glib::ustring& sender_name, const Glib::ustring& signal_name,
const Glib::VariantContainerBase& arguments); const Glib::VariantContainerBase& arguments);
void RequestFailedUnits(); void RequestFailedUnits();
void RequestFailedUnitsList();
void RequestSystemState(); void RequestSystemState();
std::vector<FailedUnit> LoadFailedUnitsList(const char* kind,
Glib::RefPtr<Gio::DBus::Proxy>& proxy,
const std::string& scope);
std::string BuildTooltipFailedList() const;
void updateData(); void updateData();
}; };

View File

@@ -12,6 +12,8 @@
#include <unordered_map> #include <unordered_map>
#include <utility> #include <utility>
#include "util/scoped_fd.hpp"
namespace waybar::modules::wayfire { namespace waybar::modules::wayfire {
using EventHandler = std::function<void(const std::string& event)>; using EventHandler = std::function<void(const std::string& event)>;
@@ -71,23 +73,7 @@ struct State {
auto update_view(const Json::Value& view) -> void; auto update_view(const Json::Value& view) -> void;
}; };
struct Sock { using Sock = util::ScopedFd;
int fd;
Sock(int fd) : fd{fd} {}
~Sock() { close(fd); }
Sock(const Sock&) = delete;
auto operator=(const Sock&) = delete;
Sock(Sock&& rhs) noexcept {
fd = rhs.fd;
rhs.fd = -1;
}
auto& operator=(Sock&& rhs) noexcept {
fd = rhs.fd;
rhs.fd = -1;
return *this;
}
};
class IPC : public std::enable_shared_from_this<IPC> { class IPC : public std::enable_shared_from_this<IPC> {
static std::weak_ptr<IPC> instance; static std::weak_ptr<IPC> instance;

View File

@@ -3,6 +3,7 @@
#include <glibmm/dispatcher.h> #include <glibmm/dispatcher.h>
#include <sigc++/signal.h> #include <sigc++/signal.h>
#include <cstddef>
#include <functional> #include <functional>
#include <mutex> #include <mutex>
#include <queue> #include <queue>
@@ -27,6 +28,12 @@ struct SafeSignal : sigc::signal<void(std::decay_t<Args>...)> {
public: public:
SafeSignal() { dp_.connect(sigc::mem_fun(*this, &SafeSignal::handle_event)); } SafeSignal() { dp_.connect(sigc::mem_fun(*this, &SafeSignal::handle_event)); }
void set_max_queued_events(std::size_t max_queued_events) {
std::unique_lock lock(mutex_);
max_queued_events_ = max_queued_events;
trim_queue_locked();
}
template <typename... EmitArgs> template <typename... EmitArgs>
void emit(EmitArgs&&... args) { void emit(EmitArgs&&... args) {
if (main_tid_ == std::this_thread::get_id()) { if (main_tid_ == std::this_thread::get_id()) {
@@ -41,6 +48,9 @@ struct SafeSignal : sigc::signal<void(std::decay_t<Args>...)> {
} else { } else {
{ {
std::unique_lock lock(mutex_); std::unique_lock lock(mutex_);
if (max_queued_events_ != 0 && queue_.size() >= max_queued_events_) {
queue_.pop();
}
queue_.emplace(std::forward<EmitArgs>(args)...); queue_.emplace(std::forward<EmitArgs>(args)...);
} }
dp_.emit(); dp_.emit();
@@ -60,6 +70,15 @@ struct SafeSignal : sigc::signal<void(std::decay_t<Args>...)> {
using signal_t::emit_reverse; using signal_t::emit_reverse;
using signal_t::make_slot; using signal_t::make_slot;
void trim_queue_locked() {
if (max_queued_events_ == 0) {
return;
}
while (queue_.size() > max_queued_events_) {
queue_.pop();
}
}
void handle_event() { void handle_event() {
for (std::unique_lock lock(mutex_); !queue_.empty(); lock.lock()) { for (std::unique_lock lock(mutex_); !queue_.empty(); lock.lock()) {
auto args = queue_.front(); auto args = queue_.front();
@@ -72,6 +91,7 @@ struct SafeSignal : sigc::signal<void(std::decay_t<Args>...)> {
Glib::Dispatcher dp_; Glib::Dispatcher dp_;
std::mutex mutex_; std::mutex mutex_;
std::queue<arg_tuple_t> queue_; std::queue<arg_tuple_t> queue_;
std::size_t max_queued_events_ = 4096;
const std::thread::id main_tid_ = std::this_thread::get_id(); const std::thread::id main_tid_ = std::this_thread::get_id();
// cache functor for signal emission to avoid recreating it on each event // cache functor for signal emission to avoid recreating it on each event
const slot_t cached_fn_ = make_slot(); const slot_t cached_fn_ = make_slot();

View File

@@ -20,6 +20,8 @@ extern std::list<pid_t> reap;
namespace waybar::util::command { namespace waybar::util::command {
constexpr int kExecFailureExitCode = 127;
struct res { struct res {
int exit_code; int exit_code;
std::string out; std::string out;
@@ -114,7 +116,9 @@ inline FILE* open(const std::string& cmd, int& pid, const std::string& output_na
setenv("WAYBAR_OUTPUT_NAME", output_name.c_str(), 1); setenv("WAYBAR_OUTPUT_NAME", output_name.c_str(), 1);
} }
execlp("/bin/sh", "sh", "-c", cmd.c_str(), (char*)0); execlp("/bin/sh", "sh", "-c", cmd.c_str(), (char*)0);
exit(0); const int saved_errno = errno;
spdlog::error("execlp(/bin/sh) failed in open: {}", strerror(saved_errno));
_exit(kExecFailureExitCode);
} else { } else {
::close(fd[1]); ::close(fd[1]);
} }
@@ -162,7 +166,9 @@ inline int32_t forkExec(const std::string& cmd, const std::string& output_name)
setenv("WAYBAR_OUTPUT_NAME", output_name.c_str(), 1); setenv("WAYBAR_OUTPUT_NAME", output_name.c_str(), 1);
} }
execl("/bin/sh", "sh", "-c", cmd.c_str(), (char*)0); execl("/bin/sh", "sh", "-c", cmd.c_str(), (char*)0);
exit(0); const int saved_errno = errno;
spdlog::error("execl(/bin/sh) failed in forkExec: {}", strerror(saved_errno));
_exit(kExecFailureExitCode);
} else { } else {
reap_mtx.lock(); reap_mtx.lock();
reap.push_back(pid); reap.push_back(pid);

View File

@@ -0,0 +1,17 @@
#pragma once
#include <string>
/**
* Result of transforming 8-bit hex codes to rgba().
*/
struct TransformResult {
std::string css;
bool was_transformed;
};
/**
* Reads a CSS file, searches for 8-bit hex codes (#RRGGBBAA),
* and transforms them into GTK-compatible rgba() syntax.
*/
TransformResult transform_8bit_to_hex(const std::string& file_path);

View File

@@ -0,0 +1,54 @@
#pragma once
#include <unistd.h>
namespace waybar::util {
class ScopedFd {
public:
explicit ScopedFd(int fd = -1) : fd_(fd) {}
~ScopedFd() {
if (fd_ != -1) {
close(fd_);
}
}
// ScopedFd is non-copyable
ScopedFd(const ScopedFd&) = delete;
ScopedFd& operator=(const ScopedFd&) = delete;
// ScopedFd is moveable
ScopedFd(ScopedFd&& other) noexcept : fd_(other.fd_) { other.fd_ = -1; }
ScopedFd& operator=(ScopedFd&& other) noexcept {
if (this != &other) {
if (fd_ != -1) {
close(fd_);
}
fd_ = other.fd_;
other.fd_ = -1;
}
return *this;
}
int get() const { return fd_; }
operator int() const { return fd_; }
void reset(int fd = -1) {
if (fd_ != -1) {
close(fd_);
}
fd_ = fd;
}
int release() {
int fd = fd_;
fd_ = -1;
return fd;
}
private:
int fd_;
};
} // namespace waybar::util

View File

@@ -1,5 +1,6 @@
#pragma once #pragma once
#include <atomic>
#include <chrono> #include <chrono>
#include <condition_variable> #include <condition_variable>
#include <ctime> #include <ctime>
@@ -31,8 +32,8 @@ class SleeperThread {
SleeperThread(std::function<void()> func) SleeperThread(std::function<void()> func)
: thread_{[this, func] { : thread_{[this, func] {
while (do_run_) { while (do_run_.load(std::memory_order_relaxed)) {
signal_ = false; signal_.store(false, std::memory_order_relaxed);
func(); func();
} }
}} { }} {
@@ -42,9 +43,18 @@ class SleeperThread {
} }
SleeperThread& operator=(std::function<void()> func) { SleeperThread& operator=(std::function<void()> func) {
if (thread_.joinable()) {
stop();
thread_.join();
}
{
std::lock_guard<std::mutex> lck(mutex_);
do_run_.store(true, std::memory_order_relaxed);
signal_.store(false, std::memory_order_relaxed);
}
thread_ = std::thread([this, func] { thread_ = std::thread([this, func] {
while (do_run_) { while (do_run_.load(std::memory_order_relaxed)) {
signal_ = false; signal_.store(false, std::memory_order_relaxed);
func(); func();
} }
}); });
@@ -56,12 +66,14 @@ class SleeperThread {
return *this; return *this;
} }
bool isRunning() const { return do_run_; } bool isRunning() const { return do_run_.load(std::memory_order_relaxed); }
auto sleep() { auto sleep() {
std::unique_lock lk(mutex_); std::unique_lock lk(mutex_);
CancellationGuard cancel_lock; CancellationGuard cancel_lock;
return condvar_.wait(lk, [this] { return signal_ || !do_run_; }); return condvar_.wait(lk, [this] {
return signal_.load(std::memory_order_relaxed) || !do_run_.load(std::memory_order_relaxed);
});
} }
auto sleep_for(std::chrono::system_clock::duration dur) { auto sleep_for(std::chrono::system_clock::duration dur) {
@@ -73,7 +85,9 @@ class SleeperThread {
if (now < max_time_point - dur) { if (now < max_time_point - dur) {
wait_end = now + dur; wait_end = now + dur;
} }
return condvar_.wait_until(lk, wait_end, [this] { return signal_ || !do_run_; }); return condvar_.wait_until(lk, wait_end, [this] {
return signal_.load(std::memory_order_relaxed) || !do_run_.load(std::memory_order_relaxed);
});
} }
auto sleep_until( auto sleep_until(
@@ -81,22 +95,24 @@ class SleeperThread {
time_point) { time_point) {
std::unique_lock lk(mutex_); std::unique_lock lk(mutex_);
CancellationGuard cancel_lock; CancellationGuard cancel_lock;
return condvar_.wait_until(lk, time_point, [this] { return signal_ || !do_run_; }); return condvar_.wait_until(lk, time_point, [this] {
return signal_.load(std::memory_order_relaxed) || !do_run_.load(std::memory_order_relaxed);
});
} }
void wake_up() { void wake_up() {
{ {
std::lock_guard<std::mutex> lck(mutex_); std::lock_guard<std::mutex> lck(mutex_);
signal_ = true; signal_.store(true, std::memory_order_relaxed);
} }
condvar_.notify_all(); condvar_.notify_all();
} }
auto stop() { void stop() {
{ {
std::lock_guard<std::mutex> lck(mutex_); std::lock_guard<std::mutex> lck(mutex_);
signal_ = true; signal_.store(true, std::memory_order_relaxed);
do_run_ = false; do_run_.store(false, std::memory_order_relaxed);
} }
condvar_.notify_all(); condvar_.notify_all();
auto handle = thread_.native_handle(); auto handle = thread_.native_handle();
@@ -118,8 +134,8 @@ class SleeperThread {
std::thread thread_; std::thread thread_;
std::condition_variable condvar_; std::condition_variable condvar_;
std::mutex mutex_; std::mutex mutex_;
bool do_run_ = true; std::atomic<bool> do_run_ = true;
bool signal_ = false; std::atomic<bool> signal_ = false;
sigc::connection connection_; sigc::connection connection_;
}; };

View File

@@ -204,7 +204,7 @@ View all valid format options in *strftime(3)* or have a look https://en.cpprefe
``` ```
"clock": { "clock": {
"format": "{:%H:%M}  ", "format": "{:%H:%M}  ",
"format-alt": "{:%A, %B %d, %Y (%R)} ", "format-alt": "{:%A, %B %d, %Y (%R)} 󰃰 ",
"tooltip-format": "<tt><small>{calendar}</small></tt>", "tooltip-format": "<tt><small>{calendar}</small></tt>",
"calendar": { "calendar": {
"mode" : "year", "mode" : "year",
@@ -259,7 +259,19 @@ View all valid format options in *strftime(3)* or have a look https://en.cpprefe
"tooltip-format": "{tz_list}" "tooltip-format": "{tz_list}"
} }
``` ```
5. Simple calendar tooltip
```
"clock": {
"format": "{:%H:%M}",
"tooltip-format": "<tt>{calendar}</tt>",
"calendar": {
"format": {
"today": "<span color='#ffcc66'><b>{}</b></span>"
}
}
}
```
# STYLE # STYLE
- *#clock* - *#clock*
@@ -287,7 +299,7 @@ Example of working config
``` ```
"clock": { "clock": {
"format": "{:%H:%M}  ", "format": "{:%H:%M}  ",
"format-alt": "{:%A, %B %d, %Y (%R)} ", "format-alt": "{:%A, %B %d, %Y (%R)} 󰃰 ",
"tooltip-format": "\n<span size='9pt' font='WenQuanYi Zen Hei Mono'>{calendar}</span>", "tooltip-format": "\n<span size='9pt' font='WenQuanYi Zen Hei Mono'>{calendar}</span>",
"calendar": { "calendar": {
"mode" : "year", "mode" : "year",

View File

@@ -43,7 +43,7 @@ Feral Gamemode optimizations.
*glyph*: ++ *glyph*: ++
typeof: string ++ typeof: string ++
default: ++ default: 󰊴 ++
The string icon to display. Only visible if *use-icon* is set to false. The string icon to display. Only visible if *use-icon* is set to false.
*icon-name*: ++ *icon-name*: ++
@@ -82,7 +82,7 @@ Feral Gamemode optimizations.
"gamemode": { "gamemode": {
"format": "{glyph}", "format": "{glyph}",
"format-alt": "{glyph} {count}", "format-alt": "{glyph} {count}",
"glyph": "", "glyph": "󰊴",
"hide-not-running": true, "hide-not-running": true,
"use-icon": true, "use-icon": true,
"icon-name": "input-gaming-symbolic", "icon-name": "input-gaming-symbolic",

View File

@@ -178,8 +178,8 @@ to be selected when the corresponding audio device is muted. This applies to *de
"alsa_output.pci-0000_00_1f.3.analog-stereo": "", "alsa_output.pci-0000_00_1f.3.analog-stereo": "",
"alsa_output.pci-0000_00_1f.3.analog-stereo-muted": "", "alsa_output.pci-0000_00_1f.3.analog-stereo-muted": "",
"headphone": "", "headphone": "",
"hands-free": "", "hands-free": "󰂑",
"headset": "", "headset": "󰂑",
"phone": "", "phone": "",
"phone-muted": "", "phone-muted": "",
"portable": "", "portable": "",

View File

@@ -91,7 +91,7 @@ Addressed by *river/mode*
``` ```
"river/mode": { "river/mode": {
"format": " {}" "format": " {}"
} }
``` ```

View File

@@ -92,7 +92,7 @@ Addressed by *sway/mode*
``` ```
"sway/mode": { "sway/mode": {
"format": " {}", "format": " {}",
"max-length": 50 "max-length": 50
} }
``` ```

View File

@@ -19,7 +19,7 @@ Addressed by *systemd-failed-units*
*format-ok*: ++ *format-ok*: ++
typeof: string ++ typeof: string ++
This format is used when there is no failing units. This format is used when there are no failing units.
*user*: ++ *user*: ++
typeof: bool ++ typeof: bool ++
@@ -34,15 +34,30 @@ Addressed by *systemd-failed-units*
*hide-on-ok*: ++ *hide-on-ok*: ++
typeof: bool ++ typeof: bool ++
default: *true* ++ default: *true* ++
Option to hide this module when there is no failing units. Option to hide this module when there are no failed units.
*tooltip-format*: ++
typeof: string ++
default: *System: {system_state}\nUser: {user_state}\nFailed units ({nr_failed}):\n{failed_units_list}* ++
Tooltip format shown when there are failed units.
*tooltip-format-ok*: ++
typeof: string ++
default: *System: {system_state}\nUser: {user_state}* ++
Tooltip format used when there are no failed units.
*tooltip-unit-format*: ++
typeof: string ++
default: *{name}: {description}* ++
Format used to render each failed unit inside the tooltip. Each item is prefixed with a bullet.
*menu*: ++ *menu*: ++
typeof: string ++ typeof: string ++
Action that popups the menu. Action that pops up the menu.
*menu-file*: ++ *menu-file*: ++
typeof: string ++ typeof: string ++
Location of the menu descriptor file. There need to be an element of type Location of the menu descriptor file. There needs to be an element of type
GtkMenu with id *menu* GtkMenu with id *menu*
*menu-actions*: ++ *menu-actions*: ++
@@ -52,7 +67,7 @@ Addressed by *systemd-failed-units*
*expand*: ++ *expand*: ++
typeof: bool ++ typeof: bool ++
default: false ++ default: false ++
Enables this module to consume all left over space dynamically. Enables this module to consume all leftover space dynamically.
# FORMAT REPLACEMENTS # FORMAT REPLACEMENTS
@@ -62,11 +77,23 @@ Addressed by *systemd-failed-units*
*{nr_failed}*: Number of total failed units. *{nr_failed}*: Number of total failed units.
*{systemd_state}:* State of the systemd system session *{system_state}:* State of the systemd system session.
*{user_state}:* State of the systemd user session *{user_state}:* State of the systemd user session.
*{overall_state}:* Overall state of the systemd and user session. ("Ok" or "Degraded") *{overall_state}:* Overall state of the systemd and user session. ("ok" or "degraded")
*{failed_units_list}:* Bulleted list of failed units using *tooltip-unit-format*. Empty when
there are no failed units.
The *tooltip-unit-format* string supports the following replacements:
*{name}*: Unit name ++
*{description}*: Unit description ++
*{load_state}*: Unit load state ++
*{active_state}*: Unit active state ++
*{sub_state}*: Unit sub state ++
*{scope}*: Either *system* or *user* depending on where the unit originated
# EXAMPLES # EXAMPLES
@@ -77,6 +104,8 @@ Addressed by *systemd-failed-units*
"format-ok": "✓", "format-ok": "✓",
"system": true, "system": true,
"user": false, "user": false,
"tooltip-format": "{nr_failed} failed units:\n{failed_units_list}",
"tooltip-unit-format": "{scope}: {name} ({active_state})",
} }
``` ```

View File

@@ -128,7 +128,7 @@ The *wireplumber* module displays the current volume reported by WirePlumber.
``` ```
"wireplumber#sink": { "wireplumber#sink": {
"format": "{volume}% {icon}", "format": "{volume}% {icon}",
"format-muted": "", "format-muted": "󰅶",
"format-icons": ["", "", ""], "format-icons": ["", "", ""],
"on-click": "helvum", "on-click": "helvum",
"on-click-right": "wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle", "on-click-right": "wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle",

View File

@@ -363,6 +363,11 @@ A group may hide all but one element, showing them only on mouse hover. In order
default: false ++ default: false ++
Whether left click should reveal the content rather than mouse over. Note that grouped modules may still process their own on-click events. Whether left click should reveal the content rather than mouse over. Note that grouped modules may still process their own on-click events.
*start-expanded*: ++
typeof: bool ++
default: false ++
Defines whether the drawer should initialize in an expanded state.
*transition-left-to-right*: ++ *transition-left-to-right*: ++
typeof: bool ++ typeof: bool ++
default: true ++ default: true ++

View File

@@ -185,7 +185,8 @@ src_files = files(
'src/util/gtk_icon.cpp', 'src/util/gtk_icon.cpp',
'src/util/icon_loader.cpp', 'src/util/icon_loader.cpp',
'src/util/regex_collection.cpp', 'src/util/regex_collection.cpp',
'src/util/css_reload_helper.cpp' 'src/util/css_reload_helper.cpp',
'src/util/transform_8bit_to_rgba.cpp'
) )
man_files = files( man_files = files(

View File

@@ -6,13 +6,13 @@
}: }:
let let
libcava = rec { libcava = rec {
version = "0.10.7-beta"; version = "0.10.7";
src = pkgs.fetchFromGitHub { src = pkgs.fetchFromGitHub {
owner = "LukashonakV"; owner = "LukashonakV";
repo = "cava"; repo = "cava";
# NOTE: Needs to match the cava.wrap # NOTE: Needs to match the cava.wrap
tag = "v${version}"; tag = "${version}";
hash = "sha256-IX1B375gTwVDRjpRfwKGuzTAZOV2pgDWzUd4bW2cTDU="; hash = "sha256-zkyj1vBzHtoypX4Bxdh1Vmwh967DKKxN751v79hzmgQ=";
}; };
}; };
in in

View File

@@ -128,7 +128,7 @@
"critical-threshold": 80, "critical-threshold": 80,
// "format-critical": "{temperatureC}°C {icon}", // "format-critical": "{temperatureC}°C {icon}",
"format": "{temperatureC}°C {icon}", "format": "{temperatureC}°C {icon}",
"format-icons": ["", "", ""] "format-icons": ["󰉬", "", "󰉪"]
}, },
"backlight": { "backlight": {
// "device": "acpi_video1", // "device": "acpi_video1",
@@ -143,7 +143,7 @@
}, },
"format": "{capacity}% {icon}", "format": "{capacity}% {icon}",
"format-full": "{capacity}% {icon}", "format-full": "{capacity}% {icon}",
"format-charging": "{capacity}% ", "format-charging": "{capacity}% 󰃨",
"format-plugged": "{capacity}% ", "format-plugged": "{capacity}% ",
"format-alt": "{time} {icon}", "format-alt": "{time} {icon}",
// "format-good": "", // An empty format will hide the module // "format-good": "", // An empty format will hide the module
@@ -167,9 +167,9 @@
"network": { "network": {
// "interface": "wlp2*", // (Optional) To force the use of this interface // "interface": "wlp2*", // (Optional) To force the use of this interface
"format-wifi": "{essid} ({signalStrength}%) ", "format-wifi": "{essid} ({signalStrength}%) ",
"format-ethernet": "{ipaddr}/{cidr} ", "format-ethernet": "{ipaddr}/{cidr} 󰊗",
"tooltip-format": "{ifname} via {gwaddr} ", "tooltip-format": "{ifname} via {gwaddr} 󰊗",
"format-linked": "{ifname} (No IP) ", "format-linked": "{ifname} (No IP) 󰊗",
"format-disconnected": "Disconnected ⚠", "format-disconnected": "Disconnected ⚠",
"format-alt": "{ifname}: {ipaddr}/{cidr}" "format-alt": "{ifname}: {ipaddr}/{cidr}"
}, },
@@ -177,14 +177,14 @@
// "scroll-step": 1, // %, can be a float // "scroll-step": 1, // %, can be a float
"format": "{volume}% {icon} {format_source}", "format": "{volume}% {icon} {format_source}",
"format-bluetooth": "{volume}% {icon} {format_source}", "format-bluetooth": "{volume}% {icon} {format_source}",
"format-bluetooth-muted": " {icon} {format_source}", "format-bluetooth-muted": "󰅶 {icon} {format_source}",
"format-muted": " {format_source}", "format-muted": "󰅶 {format_source}",
"format-source": "{volume}% ", "format-source": "{volume}% ",
"format-source-muted": "", "format-source-muted": "",
"format-icons": { "format-icons": {
"headphone": "", "headphone": "",
"hands-free": "", "hands-free": "󰂑",
"headset": "", "headset": "󰂑",
"phone": "", "phone": "",
"portable": "", "portable": "",
"car": "", "car": "",

View File

@@ -26,7 +26,7 @@ ALabel::ALabel(const Json::Value& config, const std::string& name, const std::st
: std::chrono::milliseconds( : std::chrono::milliseconds(
(config_["interval"].isNumeric() (config_["interval"].isNumeric()
? std::max(1L, // Minimum 1ms due to millisecond precision ? std::max(1L, // Minimum 1ms due to millisecond precision
static_cast<long>(config_["interval"].asDouble()) * 1000) static_cast<long>(config_["interval"].asDouble() * 1000))
: 1000 * (long)interval))), : 1000 * (long)interval))),
default_format_(format_) { default_format_(format_) {
label_.set_name(name); label_.set_name(name);
@@ -100,6 +100,8 @@ ALabel::ALabel(const Json::Value& config, const std::string& name, const std::st
g_object_unref(builder); g_object_unref(builder);
throw std::runtime_error("Failed to get 'menu' object from GtkBuilder"); throw std::runtime_error("Failed to get 'menu' object from GtkBuilder");
} }
// Keep the menu alive after dropping the transient GtkBuilder.
g_object_ref(menu_);
submenus_ = std::map<std::string, GtkMenuItem*>(); submenus_ = std::map<std::string, GtkMenuItem*>();
menuActionsMap_ = std::map<std::string, std::string>(); menuActionsMap_ = std::map<std::string, std::string>();

View File

@@ -88,6 +88,10 @@ AModule::~AModule() {
killpg(pid, SIGTERM); killpg(pid, SIGTERM);
} }
} }
if (menu_ != nullptr) {
g_object_unref(menu_);
menu_ = nullptr;
}
} }
auto AModule::update() -> void { auto AModule::update() -> void {

View File

@@ -11,6 +11,7 @@
#include "idle-inhibit-unstable-v1-client-protocol.h" #include "idle-inhibit-unstable-v1-client-protocol.h"
#include "util/clara.hpp" #include "util/clara.hpp"
#include "util/format.hpp" #include "util/format.hpp"
#include "util/hex_checker.hpp"
waybar::Client* waybar::Client::inst() { waybar::Client* waybar::Client::inst() {
static auto* c = new Client(); static auto* c = new Client();
@@ -20,11 +21,23 @@ waybar::Client* waybar::Client::inst() {
void waybar::Client::handleGlobal(void* data, struct wl_registry* registry, uint32_t name, void waybar::Client::handleGlobal(void* data, struct wl_registry* registry, uint32_t name,
const char* interface, uint32_t version) { const char* interface, uint32_t version) {
auto* client = static_cast<Client*>(data); auto* client = static_cast<Client*>(data);
if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0 && if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0 &&
version >= ZXDG_OUTPUT_V1_NAME_SINCE_VERSION) { version >= ZXDG_OUTPUT_V1_NAME_SINCE_VERSION) {
if (client->xdg_output_manager != nullptr) {
zxdg_output_manager_v1_destroy(client->xdg_output_manager);
client->xdg_output_manager = nullptr;
}
client->xdg_output_manager = static_cast<struct zxdg_output_manager_v1*>(wl_registry_bind( client->xdg_output_manager = static_cast<struct zxdg_output_manager_v1*>(wl_registry_bind(
registry, name, &zxdg_output_manager_v1_interface, ZXDG_OUTPUT_V1_NAME_SINCE_VERSION)); registry, name, &zxdg_output_manager_v1_interface, ZXDG_OUTPUT_V1_NAME_SINCE_VERSION));
} else if (strcmp(interface, zwp_idle_inhibit_manager_v1_interface.name) == 0) { } else if (strcmp(interface, zwp_idle_inhibit_manager_v1_interface.name) == 0) {
if (client->idle_inhibit_manager != nullptr) {
zwp_idle_inhibit_manager_v1_destroy(client->idle_inhibit_manager);
client->idle_inhibit_manager = nullptr;
}
client->idle_inhibit_manager = static_cast<struct zwp_idle_inhibit_manager_v1*>( client->idle_inhibit_manager = static_cast<struct zwp_idle_inhibit_manager_v1*>(
wl_registry_bind(registry, name, &zwp_idle_inhibit_manager_v1_interface, 1)); wl_registry_bind(registry, name, &zwp_idle_inhibit_manager_v1_interface, 1));
} }
@@ -195,11 +208,15 @@ auto waybar::Client::setupCss(const std::string& css_file) -> void {
} }
css_provider_ = Gtk::CssProvider::create(); css_provider_ = Gtk::CssProvider::create();
auto [modified_css, was_transformed] = transform_8bit_to_hex(css_file);
if (was_transformed) {
css_provider_->load_from_data(modified_css);
} else {
if (!css_provider_->load_from_path(css_file)) { if (!css_provider_->load_from_path(css_file)) {
css_provider_.reset(); css_provider_.reset();
throw std::runtime_error("Can't open style file"); throw std::runtime_error("Can't open style file");
} }
}
Gtk::StyleContext::add_provider_for_screen(screen, css_provider_, Gtk::StyleContext::add_provider_for_screen(screen, css_provider_,
GTK_STYLE_PROVIDER_PRIORITY_USER); GTK_STYLE_PROVIDER_PRIORITY_USER);
} }

View File

@@ -73,11 +73,19 @@ Group::Group(const std::string& name, const std::string& id, const Json::Value&
R"(A group cannot have both "click-to-reveal" and "toggle-signal".)"); R"(A group cannot have both "click-to-reveal" and "toggle-signal".)");
} }
const bool start_expanded =
(drawer_config["start-expanded"].isBool() ? drawer_config["start-expanded"].asBool()
: false);
auto transition_type = getPreferredTransitionType(vertical); auto transition_type = getPreferredTransitionType(vertical);
revealer.set_transition_type(transition_type); revealer.set_transition_type(transition_type);
revealer.set_transition_duration(transition_duration); revealer.set_transition_duration(transition_duration);
revealer.set_reveal_child(false); revealer.set_reveal_child(start_expanded);
if (start_expanded) {
box.set_state_flags(Gtk::StateFlags::STATE_FLAG_PRELIGHT);
}
revealer.get_style_context()->add_class("drawer"); revealer.get_style_context()->add_class("drawer");

View File

@@ -71,6 +71,7 @@ CFFI::CFFI(const std::string& name, const std::string& id, const Json::Value& co
// Prepare config_entries array // Prepare config_entries array
std::vector<ffi::wbcffi_config_entry> config_entries; std::vector<ffi::wbcffi_config_entry> config_entries;
config_entries.reserve(keys.size());
for (size_t i = 0; i < keys.size(); i++) { for (size_t i = 0; i < keys.size(); i++) {
config_entries.push_back({keys[i].c_str(), config_entries_stringstor[i].c_str()}); config_entries.push_back({keys[i].c_str(), config_entries_stringstor[i].c_str()});
} }

View File

@@ -26,7 +26,7 @@ std::vector<float> waybar::modules::CpuFrequency::parseCpuFrequencies() {
if (std::filesystem::exists(cpufreq_dir)) { if (std::filesystem::exists(cpufreq_dir)) {
std::vector<std::string> frequency_files = {"/cpuinfo_min_freq", "/cpuinfo_max_freq"}; std::vector<std::string> frequency_files = {"/cpuinfo_min_freq", "/cpuinfo_max_freq"};
for (auto& p : std::filesystem::directory_iterator(cpufreq_dir)) { for (auto& p : std::filesystem::directory_iterator(cpufreq_dir)) {
for (auto freq_file : frequency_files) { for (const auto& freq_file : frequency_files) {
std::string freq_file_path = p.path().string() + freq_file; std::string freq_file_path = p.path().string() + freq_file;
if (std::filesystem::exists(freq_file_path)) { if (std::filesystem::exists(freq_file_path)) {
std::string freq_value; std::string freq_value;

View File

@@ -2,6 +2,8 @@
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
#include <utility>
#include "util/scope_guard.hpp" #include "util/scope_guard.hpp"
waybar::modules::Custom::Custom(const std::string& name, const std::string& id, waybar::modules::Custom::Custom(const std::string& name, const std::string& id,
@@ -180,21 +182,22 @@ auto waybar::modules::Custom::update() -> void {
} else { } else {
label_.set_markup(str); label_.set_markup(str);
if (tooltipEnabled()) { if (tooltipEnabled()) {
std::string tooltip_markup;
if (tooltip_format_enabled_) { if (tooltip_format_enabled_) {
auto tooltip = config_["tooltip-format"].asString(); auto tooltip = config_["tooltip-format"].asString();
tooltip = fmt::format(fmt::runtime(tooltip), fmt::arg("text", text_), tooltip_markup = fmt::format(fmt::runtime(tooltip), fmt::arg("text", text_),
fmt::arg("tooltip", tooltip_), fmt::arg("alt", alt_), fmt::arg("tooltip", tooltip_), fmt::arg("alt", alt_),
fmt::arg("icon", getIcon(percentage_, alt_)), fmt::arg("icon", getIcon(percentage_, alt_)),
fmt::arg("percentage", percentage_)); fmt::arg("percentage", percentage_));
label_.set_tooltip_markup(tooltip);
} else if (text_ == tooltip_) { } else if (text_ == tooltip_) {
if (label_.get_tooltip_markup() != str) { tooltip_markup = str;
label_.set_tooltip_markup(str);
}
} else { } else {
if (label_.get_tooltip_markup() != tooltip_) { tooltip_markup = tooltip_;
label_.set_tooltip_markup(tooltip_);
} }
if (last_tooltip_markup_ != tooltip_markup) {
label_.set_tooltip_markup(tooltip_markup);
last_tooltip_markup_ = std::move(tooltip_markup);
} }
} }
auto style = label_.get_style_context(); auto style = label_.get_style_context();

View File

@@ -92,7 +92,7 @@ auto waybar::modules::Disk::update() -> void {
ALabel::update(); ALabel::update();
} }
float waybar::modules::Disk::calc_specific_divisor(std::string divisor) { float waybar::modules::Disk::calc_specific_divisor(const std::string& divisor) {
if (divisor == "kB") { if (divisor == "kB") {
return 1000.0; return 1000.0;
} else if (divisor == "kiB") { } else if (divisor == "kiB") {

View File

@@ -70,17 +70,33 @@ static const zdwl_ipc_output_v2_listener output_status_listener_impl{
static void handle_global(void* data, struct wl_registry* registry, uint32_t name, static void handle_global(void* data, struct wl_registry* registry, uint32_t name,
const char* interface, uint32_t version) { const char* interface, uint32_t version) {
if (std::strcmp(interface, zdwl_ipc_manager_v2_interface.name) == 0) {
static_cast<Tags*>(data)->status_manager_ = static_cast<struct zdwl_ipc_manager_v2*>( if (std::strcmp(interface, zdwl_ipc_manager_v2_interface.name) == 0) {
(zdwl_ipc_manager_v2*)wl_registry_bind(registry, name, &zdwl_ipc_manager_v2_interface, 1)); auto* self = static_cast<Tags*>(data);
}
if (std::strcmp(interface, wl_seat_interface.name) == 0) { if (self->status_manager_) {
version = std::min<uint32_t>(version, 1); zdwl_ipc_manager_v2_destroy(self->status_manager_);
static_cast<Tags*>(data)->seat_ = self->status_manager_ = nullptr;
static_cast<struct wl_seat*>(wl_registry_bind(registry, name, &wl_seat_interface, version));
}
} }
self->status_manager_ = static_cast<struct zdwl_ipc_manager_v2*>(
wl_registry_bind(registry, name, &zdwl_ipc_manager_v2_interface, 1));
}
if (std::strcmp(interface, wl_seat_interface.name) == 0) {
auto* self = static_cast<Tags*>(data);
if (self->seat_) {
wl_seat_destroy(self->seat_);
self->seat_ = nullptr;
}
version = std::min<uint32_t>(version, 1);
self->seat_ = static_cast<struct wl_seat*>(
wl_registry_bind(registry, name, &wl_seat_interface, version));
}
}
static void handle_global_remove(void* data, struct wl_registry* registry, uint32_t name) { static void handle_global_remove(void* data, struct wl_registry* registry, uint32_t name) {
/* Ignore event */ /* Ignore event */
} }

View File

@@ -9,9 +9,14 @@
#include <sys/un.h> #include <sys/un.h>
#include <unistd.h> #include <unistd.h>
#include <array>
#include <cerrno>
#include <cstring>
#include <filesystem> #include <filesystem>
#include <string> #include <string>
#include "util/scoped_fd.hpp"
namespace waybar::modules::hyprland { namespace waybar::modules::hyprland {
std::filesystem::path IPC::socketFolder_; std::filesystem::path IPC::socketFolder_;
@@ -20,11 +25,7 @@ std::filesystem::path IPC::getSocketFolder(const char* instanceSig) {
static std::mutex folderMutex; static std::mutex folderMutex;
std::unique_lock lock(folderMutex); std::unique_lock lock(folderMutex);
// socket path, specified by EventManager of Hyprland if (socketFolder_.empty()) {
if (!socketFolder_.empty()) {
return socketFolder_;
}
const char* xdgRuntimeDirEnv = std::getenv("XDG_RUNTIME_DIR"); const char* xdgRuntimeDirEnv = std::getenv("XDG_RUNTIME_DIR");
std::filesystem::path xdgRuntimeDir; std::filesystem::path xdgRuntimeDir;
// Only set path if env variable is set // Only set path if env variable is set
@@ -38,15 +39,15 @@ std::filesystem::path IPC::getSocketFolder(const char* instanceSig) {
spdlog::warn("$XDG_RUNTIME_DIR/hypr does not exist, falling back to /tmp/hypr"); spdlog::warn("$XDG_RUNTIME_DIR/hypr does not exist, falling back to /tmp/hypr");
socketFolder_ = std::filesystem::path("/tmp") / "hypr"; socketFolder_ = std::filesystem::path("/tmp") / "hypr";
} }
}
socketFolder_ = socketFolder_ / instanceSig; return socketFolder_ / instanceSig;
return socketFolder_;
} }
IPC::IPC() { IPC::IPC() {
// will start IPC and relay events to parseIPC // will start IPC and relay events to parseIPC
ipcThread_ = std::thread([this]() { socketListener(); });
socketOwnerPid_ = getpid(); socketOwnerPid_ = getpid();
ipcThread_ = std::thread([this]() { socketListener(); });
} }
IPC::~IPC() { IPC::~IPC() {
@@ -54,19 +55,20 @@ IPC::~IPC() {
// failed exec()) exits. // failed exec()) exits.
if (getpid() != socketOwnerPid_) return; if (getpid() != socketOwnerPid_) return;
running_ = false; running_.store(false, std::memory_order_relaxed);
spdlog::info("Hyprland IPC stopping..."); spdlog::info("Hyprland IPC stopping...");
{
std::lock_guard<std::mutex> lock(socketMutex_);
if (socketfd_ != -1) { if (socketfd_ != -1) {
spdlog::trace("Shutting down socket"); spdlog::trace("Shutting down socket");
if (shutdown(socketfd_, SHUT_RDWR) == -1) { if (shutdown(socketfd_, SHUT_RDWR) == -1 && errno != ENOTCONN) {
spdlog::error("Hyprland IPC: Couldn't shutdown socket"); spdlog::error("Hyprland IPC: Couldn't shutdown socket");
} }
spdlog::trace("Closing socket");
if (close(socketfd_) == -1) {
spdlog::error("Hyprland IPC: Couldn't close socket");
} }
} }
if (ipcThread_.joinable()) {
ipcThread_.join(); ipcThread_.join();
}
} }
IPC& IPC::inst() { IPC& IPC::inst() {
@@ -85,10 +87,10 @@ void IPC::socketListener() {
spdlog::info("Hyprland IPC starting"); spdlog::info("Hyprland IPC starting");
struct sockaddr_un addr; struct sockaddr_un addr = {};
socketfd_ = socket(AF_UNIX, SOCK_STREAM, 0); const int socketfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (socketfd_ == -1) { if (socketfd == -1) {
spdlog::error("Hyprland IPC: socketfd failed"); spdlog::error("Hyprland IPC: socketfd failed");
return; return;
} }
@@ -96,33 +98,57 @@ void IPC::socketListener() {
addr.sun_family = AF_UNIX; addr.sun_family = AF_UNIX;
auto socketPath = IPC::getSocketFolder(his) / ".socket2.sock"; auto socketPath = IPC::getSocketFolder(his) / ".socket2.sock";
if (socketPath.native().size() >= sizeof(addr.sun_path)) {
spdlog::error("Hyprland IPC: Socket path is too long: {}", socketPath.string());
close(socketfd);
return;
}
strncpy(addr.sun_path, socketPath.c_str(), sizeof(addr.sun_path) - 1); strncpy(addr.sun_path, socketPath.c_str(), sizeof(addr.sun_path) - 1);
addr.sun_path[sizeof(addr.sun_path) - 1] = 0;
int l = sizeof(struct sockaddr_un); int l = sizeof(struct sockaddr_un);
if (connect(socketfd_, (struct sockaddr*)&addr, l) == -1) { if (connect(socketfd, (struct sockaddr*)&addr, l) == -1) {
spdlog::error("Hyprland IPC: Unable to connect?"); spdlog::error("Hyprland IPC: Unable to connect? {}", std::strerror(errno));
close(socketfd);
return; return;
} }
auto* file = fdopen(socketfd_, "r");
if (file == nullptr) { {
spdlog::error("Hyprland IPC: Couldn't open file descriptor"); std::lock_guard<std::mutex> lock(socketMutex_);
return; socketfd_ = socketfd;
} }
while (running_) {
std::string pending;
while (running_.load(std::memory_order_relaxed)) {
std::array<char, 1024> buffer; // Hyprland socket2 events are max 1024 bytes std::array<char, 1024> buffer; // Hyprland socket2 events are max 1024 bytes
const ssize_t bytes_read = read(socketfd, buffer.data(), buffer.size());
auto* receivedCharPtr = fgets(buffer.data(), buffer.size(), file); if (bytes_read == 0) {
if (running_.load(std::memory_order_relaxed)) {
spdlog::warn("Hyprland IPC: Socket closed by peer");
}
break;
}
if (receivedCharPtr == nullptr) { if (bytes_read < 0) {
std::this_thread::sleep_for(std::chrono::milliseconds(1)); if (errno == EINTR) {
continue; continue;
} }
if (!running_.load(std::memory_order_relaxed)) {
break;
}
spdlog::error("Hyprland IPC: read failed: {}", std::strerror(errno));
break;
}
std::string messageReceived(buffer.data()); pending.append(buffer.data(), static_cast<std::size_t>(bytes_read));
messageReceived = messageReceived.substr(0, messageReceived.find_first_of('\n')); for (auto newline_pos = pending.find('\n'); newline_pos != std::string::npos;
newline_pos = pending.find('\n')) {
std::string messageReceived = pending.substr(0, newline_pos);
pending.erase(0, newline_pos + 1);
if (messageReceived.empty()) {
continue;
}
spdlog::debug("hyprland IPC received {}", messageReceived); spdlog::debug("hyprland IPC received {}", messageReceived);
try { try {
@@ -132,8 +158,16 @@ void IPC::socketListener() {
} catch (...) { } catch (...) {
throw; throw;
} }
}
std::this_thread::sleep_for(std::chrono::milliseconds(1)); }
{
std::lock_guard<std::mutex> lock(socketMutex_);
if (socketfd_ != -1) {
if (close(socketfd_) == -1) {
spdlog::error("Hyprland IPC: Couldn't close socket");
}
socketfd_ = -1;
}
} }
spdlog::debug("Hyprland IPC stopped"); spdlog::debug("Hyprland IPC stopped");
} }
@@ -178,7 +212,7 @@ void IPC::unregisterForIPC(EventHandler* ev_handler) {
std::string IPC::getSocket1Reply(const std::string& rq) { std::string IPC::getSocket1Reply(const std::string& rq) {
// basically hyprctl // basically hyprctl
const auto serverSocket = socket(AF_UNIX, SOCK_STREAM, 0); util::ScopedFd serverSocket(socket(AF_UNIX, SOCK_STREAM, 0));
if (serverSocket < 0) { if (serverSocket < 0) {
throw std::runtime_error("Hyprland IPC: Couldn't open a socket (1)"); throw std::runtime_error("Hyprland IPC: Couldn't open a socket (1)");
@@ -198,8 +232,10 @@ std::string IPC::getSocket1Reply(const std::string& rq) {
std::string socketPath = IPC::getSocketFolder(instanceSig) / ".socket.sock"; std::string socketPath = IPC::getSocketFolder(instanceSig) / ".socket.sock";
// Use snprintf to copy the socketPath string into serverAddress.sun_path // Use snprintf to copy the socketPath string into serverAddress.sun_path
if (snprintf(serverAddress.sun_path, sizeof(serverAddress.sun_path), "%s", socketPath.c_str()) < const auto socketPathLength =
0) { snprintf(serverAddress.sun_path, sizeof(serverAddress.sun_path), "%s", socketPath.c_str());
if (socketPathLength < 0 ||
socketPathLength >= static_cast<int>(sizeof(serverAddress.sun_path))) {
throw std::runtime_error("Hyprland IPC: Couldn't copy socket path (6)"); throw std::runtime_error("Hyprland IPC: Couldn't copy socket path (6)");
} }
@@ -208,28 +244,39 @@ std::string IPC::getSocket1Reply(const std::string& rq) {
throw std::runtime_error("Hyprland IPC: Couldn't connect to " + socketPath + ". (3)"); throw std::runtime_error("Hyprland IPC: Couldn't connect to " + socketPath + ". (3)");
} }
auto sizeWritten = write(serverSocket, rq.c_str(), rq.length()); std::size_t totalWritten = 0;
while (totalWritten < rq.length()) {
const auto sizeWritten =
write(serverSocket, rq.c_str() + totalWritten, rq.length() - totalWritten);
if (sizeWritten < 0) { if (sizeWritten < 0) {
if (errno == EINTR) {
continue;
}
spdlog::error("Hyprland IPC: Couldn't write (4)"); spdlog::error("Hyprland IPC: Couldn't write (4)");
return ""; return "";
} }
if (sizeWritten == 0) {
spdlog::error("Hyprland IPC: Socket write made no progress");
return "";
}
totalWritten += static_cast<std::size_t>(sizeWritten);
}
std::array<char, 8192> buffer = {0}; std::array<char, 8192> buffer = {0};
std::string response; std::string response;
ssize_t sizeWritten = 0;
do { do {
sizeWritten = read(serverSocket, buffer.data(), 8192); sizeWritten = read(serverSocket, buffer.data(), 8192);
if (sizeWritten < 0) { if (sizeWritten < 0) {
spdlog::error("Hyprland IPC: Couldn't read (5)"); spdlog::error("Hyprland IPC: Couldn't read (5)");
close(serverSocket);
return ""; return "";
} }
response.append(buffer.data(), sizeWritten); response.append(buffer.data(), sizeWritten);
} while (sizeWritten > 0); } while (sizeWritten > 0);
close(serverSocket);
return response; return response;
} }

View File

@@ -63,19 +63,35 @@ auto Language::update() -> void {
void Language::onEvent(const std::string& ev) { void Language::onEvent(const std::string& ev) {
std::lock_guard<std::mutex> lg(mutex_); std::lock_guard<std::mutex> lg(mutex_);
std::string kbName(begin(ev) + ev.find_last_of('>') + 1, begin(ev) + ev.find_first_of(',')); const auto payloadStart = ev.find(">>");
if (payloadStart == std::string::npos) {
spdlog::warn("hyprland language received malformed event: {}", ev);
return;
}
const auto payload = ev.substr(payloadStart + 2);
const auto kbSeparator = payload.find(',');
if (kbSeparator == std::string::npos) {
spdlog::warn("hyprland language received malformed event payload: {}", ev);
return;
}
std::string kbName = payload.substr(0, kbSeparator);
// Last comma before variants parenthesis, eg: // Last comma before variants parenthesis, eg:
// activelayout>>micro-star-int'l-co.,-ltd.-msi-gk50-elite-gaming-keyboard,English (US, intl., // activelayout>>micro-star-int'l-co.,-ltd.-msi-gk50-elite-gaming-keyboard,English (US, intl.,
// with dead keys) // with dead keys)
std::string beforeParenthesis; std::string beforeParenthesis;
auto parenthesisPos = ev.find_last_of('('); auto parenthesisPos = payload.find_last_of('(');
if (parenthesisPos == std::string::npos) { if (parenthesisPos == std::string::npos) {
beforeParenthesis = ev; beforeParenthesis = payload;
} else { } else {
beforeParenthesis = std::string(begin(ev), begin(ev) + parenthesisPos); beforeParenthesis = payload.substr(0, parenthesisPos);
} }
auto layoutName = ev.substr(beforeParenthesis.find_last_of(',') + 1); const auto layoutSeparator = beforeParenthesis.find_last_of(',');
if (layoutSeparator == std::string::npos) {
spdlog::warn("hyprland language received malformed layout payload: {}", ev);
return;
}
auto layoutName = payload.substr(layoutSeparator + 1);
if (config_.isMember("keyboard-name") && kbName != config_["keyboard-name"].asString()) if (config_.isMember("keyboard-name") && kbName != config_["keyboard-name"].asString())
return; // ignore return; // ignore

View File

@@ -75,7 +75,12 @@ void Submap::onEvent(const std::string& ev) {
return; return;
} }
auto submapName = ev.substr(ev.find_first_of('>') + 2); const auto separator = ev.find(">>");
if (separator == std::string::npos) {
spdlog::warn("hyprland submap received malformed event: {}", ev);
return;
}
auto submapName = ev.substr(separator + 2);
submap_ = submapName; submap_ = submapName;

View File

@@ -19,21 +19,19 @@ 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), m_ipc(IPC::inst()) { : AAppIconLabel(config, "window", id, "{title}", 0, true), bar_(bar), m_ipc(IPC::inst()) {
std::unique_lock<std::shared_mutex> windowIpcUniqueLock(windowIpcSmtx);
separateOutputs_ = config["separate-outputs"].asBool(); separateOutputs_ = config["separate-outputs"].asBool();
update();
// register for hyprland ipc // register for hyprland ipc
std::unique_lock<std::shared_mutex> windowIpcUniqueLock(windowIpcSmtx);
m_ipc.registerForIPC("activewindow", this); m_ipc.registerForIPC("activewindow", this);
m_ipc.registerForIPC("closewindow", this); m_ipc.registerForIPC("closewindow", this);
m_ipc.registerForIPC("movewindow", this); m_ipc.registerForIPC("movewindow", this);
m_ipc.registerForIPC("changefloatingmode", this); m_ipc.registerForIPC("changefloatingmode", this);
m_ipc.registerForIPC("fullscreen", this); m_ipc.registerForIPC("fullscreen", this);
windowIpcUniqueLock.unlock(); windowIpcUniqueLock.unlock();
queryActiveWorkspace();
update();
dp.emit(); dp.emit();
} }
@@ -124,7 +122,7 @@ auto Window::getActiveWorkspace(const std::string& monitorName) -> Workspace {
const auto monitors = IPC::inst().getSocket1JsonReply("monitors"); const auto monitors = IPC::inst().getSocket1JsonReply("monitors");
if (monitors.isArray()) { if (monitors.isArray()) {
auto monitor = std::ranges::find_if( auto monitor = std::ranges::find_if(
monitors, [&](Json::Value monitor) { return monitor["name"] == monitorName; }); monitors, [&](const 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{ return Workspace{
@@ -139,7 +137,7 @@ auto Window::getActiveWorkspace(const std::string& monitorName) -> Workspace {
const auto workspaces = IPC::inst().getSocket1JsonReply("workspaces"); const auto workspaces = IPC::inst().getSocket1JsonReply("workspaces");
if (workspaces.isArray()) { if (workspaces.isArray()) {
auto workspace = std::ranges::find_if( auto workspace = std::ranges::find_if(
workspaces, [&](Json::Value workspace) { return workspace["id"] == id; }); workspaces, [&](const 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{ return Workspace{
@@ -177,42 +175,56 @@ auto Window::WindowData::parse(const Json::Value& value) -> Window::WindowData {
} }
void Window::queryActiveWorkspace() { void Window::queryActiveWorkspace() {
std::shared_lock<std::shared_mutex> windowIpcShareLock(windowIpcSmtx);
if (separateOutputs_) { if (separateOutputs_) {
workspace_ = getActiveWorkspace(this->bar_.output->name); workspace_ = getActiveWorkspace(this->bar_.output->name);
} else { } else {
workspace_ = getActiveWorkspace(); workspace_ = getActiveWorkspace();
} }
focused_ = true;
if (workspace_.windows > 0) {
const auto clients = m_ipc.getSocket1JsonReply("clients");
if (clients.isArray()) {
auto activeWindow = std::ranges::find_if(
clients, [&](Json::Value window) { return window["address"] == workspace_.last_window; });
if (activeWindow == std::end(clients)) {
focused_ = false; focused_ = false;
windowData_ = WindowData{};
allFloating_ = false;
swallowing_ = false;
fullscreen_ = false;
solo_ = false;
soloClass_.clear();
if (workspace_.windows <= 0) {
return; return;
} }
const auto clients = m_ipc.getSocket1JsonReply("clients");
if (!clients.isArray()) {
return;
}
auto activeWindow = std::ranges::find_if(clients, [&](const Json::Value& window) {
return window["address"] == workspace_.last_window;
});
if (activeWindow == std::end(clients)) {
return;
}
focused_ = true;
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::ranges::copy_if(clients, std::back_inserter(workspaceWindows), [&](Json::Value window) { std::ranges::copy_if(
clients, std::back_inserter(workspaceWindows), [&](const 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_ = std::ranges::any_of(workspaceWindows, [&](const Json::Value& window) {
return !window["swallowing"].isNull() && window["swallowing"].asString() != "0x0"; return !window["swallowing"].isNull() && window["swallowing"].asString() != "0x0";
}); });
std::vector<Json::Value> visibleWindows; std::vector<Json::Value> visibleWindows;
std::ranges::copy_if(workspaceWindows, std::back_inserter(visibleWindows), std::ranges::copy_if(workspaceWindows, std::back_inserter(visibleWindows),
[&](Json::Value window) { return !window["hidden"].asBool(); }); [&](const Json::Value& window) { return !window["hidden"].asBool(); });
solo_ = 1 == std::count_if(visibleWindows.begin(), visibleWindows.end(), solo_ = 1 == std::count_if(
[&](Json::Value window) { return !window["floating"].asBool(); }); visibleWindows.begin(), visibleWindows.end(),
[&](const Json::Value& window) { return !window["floating"].asBool(); });
allFloating_ = std::ranges::all_of( allFloating_ = std::ranges::all_of(
visibleWindows, [&](Json::Value window) { return window["floating"].asBool(); }); visibleWindows, [&](const 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
@@ -222,18 +234,6 @@ void Window::queryActiveWorkspace() {
if (solo_) { if (solo_) {
soloClass_ = windowData_.class_name; soloClass_ = windowData_.class_name;
} else {
soloClass_ = "";
}
}
} else {
focused_ = false;
windowData_ = WindowData{};
allFloating_ = false;
swallowing_ = false;
fullscreen_ = false;
solo_ = false;
soloClass_ = "";
} }
} }

View File

@@ -79,7 +79,7 @@ auto WindowCount::getActiveWorkspace(const std::string& monitorName) -> Workspac
const auto monitors = m_ipc.getSocket1JsonReply("monitors"); const auto monitors = m_ipc.getSocket1JsonReply("monitors");
if (monitors.isArray()) { if (monitors.isArray()) {
auto monitor = std::ranges::find_if( auto monitor = std::ranges::find_if(
monitors, [&](Json::Value monitor) { return monitor["name"] == monitorName; }); monitors, [&](const 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{ return Workspace{
@@ -93,7 +93,7 @@ auto WindowCount::getActiveWorkspace(const std::string& monitorName) -> Workspac
const auto workspaces = m_ipc.getSocket1JsonReply("workspaces"); const auto workspaces = m_ipc.getSocket1JsonReply("workspaces");
if (workspaces.isArray()) { if (workspaces.isArray()) {
auto workspace = std::ranges::find_if( auto workspace = std::ranges::find_if(
workspaces, [&](Json::Value workspace) { return workspace["id"] == id; }); workspaces, [&](const 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{ return Workspace{

View File

@@ -19,7 +19,7 @@ WindowCreationPayload::WindowCreationPayload(Json::Value const& client_data)
clearWorkspaceName(); clearWorkspaceName();
} }
WindowCreationPayload::WindowCreationPayload(std::string workspace_name, WindowCreationPayload::WindowCreationPayload(const std::string& workspace_name,
WindowAddress window_address, WindowRepr window_repr) WindowAddress window_address, WindowRepr window_repr)
: m_window(std::move(window_repr)), : m_window(std::move(window_repr)),
m_windowAddress(std::move(window_address)), m_windowAddress(std::move(window_address)),
@@ -28,9 +28,10 @@ WindowCreationPayload::WindowCreationPayload(std::string workspace_name,
clearWorkspaceName(); clearWorkspaceName();
} }
WindowCreationPayload::WindowCreationPayload(std::string workspace_name, WindowCreationPayload::WindowCreationPayload(const std::string& workspace_name,
WindowAddress window_address, std::string window_class, WindowAddress window_address,
std::string window_title, bool is_active) const std::string& window_class,
const std::string& window_title, bool is_active)
: m_window(std::make_pair(std::move(window_class), std::move(window_title))), : m_window(std::make_pair(std::move(window_class), std::move(window_title))),
m_windowAddress(std::move(window_address)), m_windowAddress(std::move(window_address)),
m_workspaceName(std::move(workspace_name)), m_workspaceName(std::move(workspace_name)),

View File

@@ -96,7 +96,7 @@ bool Workspace::handleClicked(GdkEventButton* bt) const {
void Workspace::initializeWindowMap(const Json::Value& clients_data) { void Workspace::initializeWindowMap(const Json::Value& clients_data) {
m_windowMap.clear(); m_windowMap.clear();
for (auto client : clients_data) { for (const auto& client : clients_data) {
if (client["workspace"]["id"].asInt() == id()) { if (client["workspace"]["id"].asInt() == id()) {
insertWindow({client}); insertWindow({client});
} }

View File

@@ -34,6 +34,9 @@ Workspaces::Workspaces(const std::string& id, const Bar& bar, const Json::Value&
} }
Workspaces::~Workspaces() { Workspaces::~Workspaces() {
if (m_scrollEventConnection_.connected()) {
m_scrollEventConnection_.disconnect();
}
m_ipc.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);
@@ -44,9 +47,13 @@ void Workspaces::init() {
initializeWorkspaces(); initializeWorkspaces();
if (m_scrollEventConnection_.connected()) {
m_scrollEventConnection_.disconnect();
}
if (barScroll()) { if (barScroll()) {
auto& window = const_cast<Bar&>(m_bar).window; auto& window = const_cast<Bar&>(m_bar).window;
window.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK); window.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);
m_scrollEventConnection_ =
window.signal_scroll_event().connect(sigc::mem_fun(*this, &Workspaces::handleScroll)); window.signal_scroll_event().connect(sigc::mem_fun(*this, &Workspaces::handleScroll));
} }
@@ -155,7 +162,8 @@ void Workspaces::extendOrphans(int workspaceId, Json::Value const& clientsJson)
} }
} }
std::string Workspaces::getRewrite(std::string window_class, std::string window_title) { std::string Workspaces::getRewrite(const std::string& window_class,
const std::string& window_title) {
std::string windowReprKey; std::string windowReprKey;
if (windowRewriteConfigUsesTitle()) { if (windowRewriteConfigUsesTitle()) {
windowReprKey = fmt::format("class<{}> title<{}>", window_class, window_title); windowReprKey = fmt::format("class<{}> title<{}>", window_class, window_title);
@@ -196,7 +204,7 @@ void Workspaces::initializeWorkspaces() {
auto const workspacesJson = m_ipc.getSocket1JsonReply("workspaces"); auto const workspacesJson = m_ipc.getSocket1JsonReply("workspaces");
auto const clientsJson = m_ipc.getSocket1JsonReply("clients"); auto const clientsJson = m_ipc.getSocket1JsonReply("clients");
for (Json::Value workspaceJson : workspacesJson) { for (const auto& workspaceJson : workspacesJson) {
std::string workspaceName = workspaceJson["name"].asString(); std::string workspaceName = workspaceJson["name"].asString();
if ((allOutputs() || m_bar.output->name == workspaceJson["monitor"].asString()) && if ((allOutputs() || m_bar.output->name == workspaceJson["monitor"].asString()) &&
(!workspaceName.starts_with("special") || showSpecial()) && (!workspaceName.starts_with("special") || showSpecial()) &&
@@ -270,7 +278,7 @@ void Workspaces::loadPersistentWorkspacesFromConfig(Json::Value const& clientsJs
// key is the workspace and value is array of monitors to create on // key is the workspace and value is array of monitors to create on
for (const Json::Value& monitor : value) { for (const Json::Value& monitor : value) {
if (monitor.isString() && monitor.asString() == currentMonitor) { if (monitor.isString() && monitor.asString() == currentMonitor) {
persistentWorkspacesToCreate.emplace_back(currentMonitor); persistentWorkspacesToCreate.emplace_back(key);
break; break;
} }
} }
@@ -331,8 +339,13 @@ void Workspaces::loadPersistentWorkspacesFromWorkspaceRules(const Json::Value& c
void Workspaces::onEvent(const std::string& ev) { void Workspaces::onEvent(const std::string& ev) {
std::lock_guard<std::mutex> lock(m_mutex); std::lock_guard<std::mutex> lock(m_mutex);
std::string eventName(begin(ev), begin(ev) + ev.find_first_of('>')); const auto separator = ev.find(">>");
std::string payload = ev.substr(eventName.size() + 2); if (separator == std::string::npos) {
spdlog::warn("Malformed Hyprland workspace event: {}", ev);
return;
}
std::string eventName = ev.substr(0, separator);
std::string payload = ev.substr(separator + 2);
if (eventName == "workspacev2") { if (eventName == "workspacev2") {
onWorkspaceActivated(payload); onWorkspaceActivated(payload);
@@ -400,7 +413,7 @@ void Workspaces::onWorkspaceCreated(std::string const& payload, Json::Value cons
auto const workspaceRules = m_ipc.getSocket1JsonReply("workspacerules"); auto const workspaceRules = m_ipc.getSocket1JsonReply("workspacerules");
auto const workspacesJson = m_ipc.getSocket1JsonReply("workspaces"); auto const workspacesJson = m_ipc.getSocket1JsonReply("workspaces");
for (Json::Value workspaceJson : workspacesJson) { for (auto workspaceJson : workspacesJson) {
const auto currentId = workspaceJson["id"].asInt(); const auto currentId = workspaceJson["id"].asInt();
if (currentId == *workspaceId) { if (currentId == *workspaceId) {
std::string workspaceName = workspaceJson["name"].asString(); std::string workspaceName = workspaceJson["name"].asString();
@@ -495,19 +508,21 @@ void Workspaces::onMonitorFocused(std::string const& payload) {
void Workspaces::onWindowOpened(std::string const& payload) { void Workspaces::onWindowOpened(std::string const& payload) {
spdlog::trace("Window opened: {}", payload); spdlog::trace("Window opened: {}", payload);
updateWindowCount(); updateWindowCount();
size_t lastCommaIdx = 0; const auto firstComma = payload.find(',');
size_t nextCommaIdx = payload.find(','); const auto secondComma =
std::string windowAddress = payload.substr(lastCommaIdx, nextCommaIdx - lastCommaIdx); firstComma == std::string::npos ? std::string::npos : payload.find(',', firstComma + 1);
const auto thirdComma =
secondComma == std::string::npos ? std::string::npos : payload.find(',', secondComma + 1);
if (firstComma == std::string::npos || secondComma == std::string::npos ||
thirdComma == std::string::npos) {
spdlog::warn("Malformed Hyprland openwindow payload: {}", payload);
return;
}
lastCommaIdx = nextCommaIdx; std::string windowAddress = payload.substr(0, firstComma);
nextCommaIdx = payload.find(',', nextCommaIdx + 1); std::string workspaceName = payload.substr(firstComma + 1, secondComma - firstComma - 1);
std::string workspaceName = payload.substr(lastCommaIdx + 1, nextCommaIdx - lastCommaIdx - 1); std::string windowClass = payload.substr(secondComma + 1, thirdComma - secondComma - 1);
std::string windowTitle = payload.substr(thirdComma + 1);
lastCommaIdx = nextCommaIdx;
nextCommaIdx = payload.find(',', nextCommaIdx + 1);
std::string windowClass = payload.substr(lastCommaIdx + 1, nextCommaIdx - lastCommaIdx - 1);
std::string windowTitle = payload.substr(nextCommaIdx + 1, payload.length() - nextCommaIdx);
bool isActive = m_currentActiveWindowAddress == windowAddress; bool isActive = m_currentActiveWindowAddress == windowAddress;
m_windowsToCreate.emplace_back(workspaceName, windowAddress, windowClass, windowTitle, isActive); m_windowsToCreate.emplace_back(workspaceName, windowAddress, windowClass, windowTitle, isActive);
@@ -1001,10 +1016,12 @@ void Workspaces::sortWorkspaces() {
void Workspaces::setUrgentWorkspace(std::string const& windowaddress) { void Workspaces::setUrgentWorkspace(std::string const& windowaddress) {
const Json::Value clientsJson = m_ipc.getSocket1JsonReply("clients"); const Json::Value clientsJson = m_ipc.getSocket1JsonReply("clients");
const std::string normalizedAddress =
windowaddress.starts_with("0x") ? windowaddress : fmt::format("0x{}", windowaddress);
int workspaceId = -1; int workspaceId = -1;
for (Json::Value clientJson : clientsJson) { for (const auto& clientJson : clientsJson) {
if (clientJson["address"].asString().ends_with(windowaddress)) { if (clientJson["address"].asString() == normalizedAddress) {
workspaceId = clientJson["workspace"]["id"].asInt(); workspaceId = clientJson["workspace"]["id"].asInt();
break; break;
} }
@@ -1133,7 +1150,11 @@ std::string Workspaces::makePayload(Args const&... args) {
} }
std::pair<std::string, std::string> Workspaces::splitDoublePayload(std::string const& payload) { std::pair<std::string, std::string> Workspaces::splitDoublePayload(std::string const& payload) {
const std::string part1 = payload.substr(0, payload.find(',')); const auto separator = payload.find(',');
if (separator == std::string::npos) {
throw std::invalid_argument("Expected a two-part Hyprland payload");
}
const std::string part1 = payload.substr(0, separator);
const std::string part2 = payload.substr(part1.size() + 1); const std::string part2 = payload.substr(part1.size() + 1);
return {part1, part2}; return {part1, part2};
} }
@@ -1142,6 +1163,9 @@ std::tuple<std::string, std::string, std::string> Workspaces::splitTriplePayload
std::string const& payload) { std::string const& payload) {
const size_t firstComma = payload.find(','); const size_t firstComma = payload.find(',');
const size_t secondComma = payload.find(',', firstComma + 1); const size_t secondComma = payload.find(',', firstComma + 1);
if (firstComma == std::string::npos || secondComma == std::string::npos) {
throw std::invalid_argument("Expected a three-part Hyprland payload");
}
const std::string part1 = payload.substr(0, firstComma); const std::string part1 = payload.substr(0, firstComma);
const std::string part2 = payload.substr(firstComma + 1, secondComma - (firstComma + 1)); const std::string part2 = payload.substr(firstComma + 1, secondComma - (firstComma + 1));

View File

@@ -85,7 +85,8 @@ auto getInhibitors(const Json::Value& config) -> std::string {
if (config["what"].isArray()) { if (config["what"].isArray()) {
inhibitors = checkInhibitor(config["what"][0].asString()); inhibitors = checkInhibitor(config["what"][0].asString());
for (decltype(config["what"].size()) i = 1; i < config["what"].size(); ++i) { for (decltype(config["what"].size()) i = 1; i < config["what"].size(); ++i) {
inhibitors += ":" + checkInhibitor(config["what"][i].asString()); inhibitors.append(":");
inhibitors.append(checkInhibitor(config["what"][i].asString()));
} }
return inhibitors; return inhibitors;
} }

View File

@@ -239,7 +239,8 @@ std::string waybar::modules::MPD::getStateIcon() const {
} }
} }
std::string waybar::modules::MPD::getOptionIcon(std::string optionName, bool activated) const { std::string waybar::modules::MPD::getOptionIcon(const std::string& optionName,
bool activated) const {
if (!config_[optionName + "-icons"].isObject()) { if (!config_[optionName + "-icons"].isObject()) {
return ""; return "";
} }

View File

@@ -109,6 +109,7 @@ Mpris::Mpris(const std::string& id, const Json::Value& config)
player_ = config_["player"].asString(); player_ = config_["player"].asString();
} }
if (config_["ignored-players"].isArray()) { if (config_["ignored-players"].isArray()) {
ignored_players_.reserve(config_["ignored-players"].size());
for (const auto& item : config_["ignored-players"]) { for (const auto& item : config_["ignored-players"]) {
if (item.isString()) { if (item.isString()) {
ignored_players_.push_back(item.asString()); ignored_players_.push_back(item.asString());
@@ -161,8 +162,7 @@ Mpris::Mpris(const std::string& id, const Json::Value& config)
if (player) { if (player) {
g_object_connect(player, "signal::play", G_CALLBACK(onPlayerPlay), this, "signal::pause", g_object_connect(player, "signal::play", G_CALLBACK(onPlayerPlay), this, "signal::pause",
G_CALLBACK(onPlayerPause), this, "signal::stop", G_CALLBACK(onPlayerStop), G_CALLBACK(onPlayerPause), this, "signal::stop", G_CALLBACK(onPlayerStop),
this, "signal::stop", G_CALLBACK(onPlayerStop), this, "signal::metadata", this, "signal::metadata", G_CALLBACK(onPlayerMetadata), this, NULL);
G_CALLBACK(onPlayerMetadata), this, NULL);
} }
// allow setting an interval count that triggers periodic refreshes // allow setting an interval count that triggers periodic refreshes
@@ -178,9 +178,17 @@ Mpris::Mpris(const std::string& id, const Json::Value& config)
} }
Mpris::~Mpris() { Mpris::~Mpris() {
if (last_active_player_ && last_active_player_ != player) g_object_unref(last_active_player_); if (manager != nullptr) {
if (manager != nullptr) g_object_unref(manager); g_signal_handlers_disconnect_by_data(manager, this);
if (player != nullptr) g_object_unref(player); }
if (player != nullptr) {
g_signal_handlers_disconnect_by_data(player, this);
}
if (last_active_player_ != nullptr && last_active_player_ != player) {
g_object_unref(last_active_player_);
}
g_clear_object(&manager);
g_clear_object(&player);
} }
auto Mpris::getIconFromJson(const Json::Value& icons, const std::string& key) -> std::string { auto Mpris::getIconFromJson(const Json::Value& icons, const std::string& key) -> std::string {
@@ -411,11 +419,14 @@ auto Mpris::onPlayerNameAppeared(PlayerctlPlayerManager* manager, PlayerctlPlaye
return; return;
} }
if (mpris->player != nullptr) {
g_signal_handlers_disconnect_by_data(mpris->player, mpris);
g_clear_object(&mpris->player);
}
mpris->player = playerctl_player_new_from_name(player_name, nullptr); mpris->player = playerctl_player_new_from_name(player_name, nullptr);
g_object_connect(mpris->player, "signal::play", G_CALLBACK(onPlayerPlay), mpris, "signal::pause", g_object_connect(mpris->player, "signal::play", G_CALLBACK(onPlayerPlay), mpris, "signal::pause",
G_CALLBACK(onPlayerPause), mpris, "signal::stop", G_CALLBACK(onPlayerStop), G_CALLBACK(onPlayerPause), mpris, "signal::stop", G_CALLBACK(onPlayerStop),
mpris, "signal::stop", G_CALLBACK(onPlayerStop), mpris, "signal::metadata", mpris, "signal::metadata", G_CALLBACK(onPlayerMetadata), mpris, NULL);
G_CALLBACK(onPlayerMetadata), mpris, NULL);
mpris->dp.emit(); mpris->dp.emit();
} }

View File

@@ -105,6 +105,7 @@ waybar::modules::Network::Network(const std::string& id, const Json::Value& conf
bandwidth_down_total_ = 0; bandwidth_down_total_ = 0;
bandwidth_up_total_ = 0; bandwidth_up_total_ = 0;
} }
bandwidth_last_sample_time_ = std::chrono::steady_clock::now();
if (!config_["interface"].isString()) { if (!config_["interface"].isString()) {
// "interface" isn't configured, then try to guess the external // "interface" isn't configured, then try to guess the external
@@ -277,6 +278,12 @@ const std::string waybar::modules::Network::getNetworkState() const {
auto waybar::modules::Network::update() -> void { auto waybar::modules::Network::update() -> void {
std::lock_guard<std::mutex> lock(mutex_); std::lock_guard<std::mutex> lock(mutex_);
std::string tooltip_format; std::string tooltip_format;
auto now = std::chrono::steady_clock::now();
auto elapsed_seconds = std::chrono::duration<double>(now - bandwidth_last_sample_time_).count();
if (elapsed_seconds <= 0.0) {
elapsed_seconds = std::chrono::duration<double>(interval_).count();
}
bandwidth_last_sample_time_ = now;
auto bandwidth = readBandwidthUsage(); auto bandwidth = readBandwidthUsage();
auto bandwidth_down = 0ull; auto bandwidth_down = 0ull;
@@ -321,6 +328,7 @@ auto waybar::modules::Network::update() -> void {
} else if (addr_pref_ == ip_addr_pref::IPV6) { } else if (addr_pref_ == ip_addr_pref::IPV6) {
final_ipaddr_ = ipaddr6_; final_ipaddr_ = ipaddr6_;
} else if (addr_pref_ == ip_addr_pref::IPV4_6) { } else if (addr_pref_ == ip_addr_pref::IPV4_6) {
final_ipaddr_.reserve(ipaddr_.length() + ipaddr6_.length() + 1);
final_ipaddr_ = ipaddr_; final_ipaddr_ = ipaddr_;
final_ipaddr_ += '\n'; final_ipaddr_ += '\n';
final_ipaddr_ += ipaddr6_; final_ipaddr_ += ipaddr6_;
@@ -334,23 +342,18 @@ auto waybar::modules::Network::update() -> void {
fmt::arg("ipaddr", final_ipaddr_), fmt::arg("gwaddr", gwaddr_), fmt::arg("cidr", cidr_), 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("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 / elapsed_seconds, "b/s")),
pow_format(bandwidth_down * 8ull / (interval_.count() / 1000.0), "b/s")), fmt::arg("bandwidthUpBits", pow_format(bandwidth_up * 8ull / elapsed_seconds, "b/s")),
fmt::arg("bandwidthUpBits", fmt::arg("bandwidthTotalBits",
pow_format(bandwidth_up * 8ull / (interval_.count() / 1000.0), "b/s")), pow_format((bandwidth_up + bandwidth_down) * 8ull / elapsed_seconds, "b/s")),
fmt::arg( fmt::arg("bandwidthDownOctets", pow_format(bandwidth_down / elapsed_seconds, "o/s")),
"bandwidthTotalBits", fmt::arg("bandwidthUpOctets", pow_format(bandwidth_up / elapsed_seconds, "o/s")),
pow_format((bandwidth_up + bandwidth_down) * 8ull / (interval_.count() / 1000.0), "b/s")),
fmt::arg("bandwidthDownOctets",
pow_format(bandwidth_down / (interval_.count() / 1000.0), "o/s")),
fmt::arg("bandwidthUpOctets", pow_format(bandwidth_up / (interval_.count() / 1000.0), "o/s")),
fmt::arg("bandwidthTotalOctets", fmt::arg("bandwidthTotalOctets",
pow_format((bandwidth_up + bandwidth_down) / (interval_.count() / 1000.0), "o/s")), pow_format((bandwidth_up + bandwidth_down) / elapsed_seconds, "o/s")),
fmt::arg("bandwidthDownBytes", fmt::arg("bandwidthDownBytes", pow_format(bandwidth_down / elapsed_seconds, "B/s")),
pow_format(bandwidth_down / (interval_.count() / 1000.0), "B/s")), fmt::arg("bandwidthUpBytes", pow_format(bandwidth_up / elapsed_seconds, "B/s")),
fmt::arg("bandwidthUpBytes", pow_format(bandwidth_up / (interval_.count() / 1000.0), "B/s")),
fmt::arg("bandwidthTotalBytes", fmt::arg("bandwidthTotalBytes",
pow_format((bandwidth_up + bandwidth_down) / (interval_.count() / 1000.0), "B/s"))); pow_format((bandwidth_up + bandwidth_down) / elapsed_seconds, "B/s")));
if (text.compare(label_.get_label()) != 0) { if (text.compare(label_.get_label()) != 0) {
label_.set_markup(text); label_.set_markup(text);
if (text.empty()) { if (text.empty()) {
@@ -372,19 +375,18 @@ auto waybar::modules::Network::update() -> void {
fmt::arg("ipaddr", final_ipaddr_), fmt::arg("gwaddr", gwaddr_), fmt::arg("cidr", cidr_), 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("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 / elapsed_seconds, "b/s")),
pow_format(bandwidth_down * 8ull / interval_.count(), "b/s")), fmt::arg("bandwidthUpBits", pow_format(bandwidth_up * 8ull / elapsed_seconds, "b/s")),
fmt::arg("bandwidthUpBits", pow_format(bandwidth_up * 8ull / interval_.count(), "b/s")),
fmt::arg("bandwidthTotalBits", fmt::arg("bandwidthTotalBits",
pow_format((bandwidth_up + bandwidth_down) * 8ull / interval_.count(), "b/s")), pow_format((bandwidth_up + bandwidth_down) * 8ull / elapsed_seconds, "b/s")),
fmt::arg("bandwidthDownOctets", pow_format(bandwidth_down / interval_.count(), "o/s")), fmt::arg("bandwidthDownOctets", pow_format(bandwidth_down / elapsed_seconds, "o/s")),
fmt::arg("bandwidthUpOctets", pow_format(bandwidth_up / interval_.count(), "o/s")), fmt::arg("bandwidthUpOctets", pow_format(bandwidth_up / elapsed_seconds, "o/s")),
fmt::arg("bandwidthTotalOctets", fmt::arg("bandwidthTotalOctets",
pow_format((bandwidth_up + bandwidth_down) / interval_.count(), "o/s")), pow_format((bandwidth_up + bandwidth_down) / elapsed_seconds, "o/s")),
fmt::arg("bandwidthDownBytes", pow_format(bandwidth_down / interval_.count(), "B/s")), fmt::arg("bandwidthDownBytes", pow_format(bandwidth_down / elapsed_seconds, "B/s")),
fmt::arg("bandwidthUpBytes", pow_format(bandwidth_up / interval_.count(), "B/s")), fmt::arg("bandwidthUpBytes", pow_format(bandwidth_up / elapsed_seconds, "B/s")),
fmt::arg("bandwidthTotalBytes", fmt::arg("bandwidthTotalBytes",
pow_format((bandwidth_up + bandwidth_down) / interval_.count(), "B/s"))); pow_format((bandwidth_up + bandwidth_down) / elapsed_seconds, "B/s")));
if (label_.get_tooltip_text() != tooltip_text) { if (label_.get_tooltip_text() != tooltip_text) {
label_.set_tooltip_markup(tooltip_text); label_.set_tooltip_markup(tooltip_text);
} }
@@ -626,18 +628,31 @@ 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) {
bool addr_changed = false;
std::string changed_ipaddr;
int changed_cidr = 0;
if ((net->addr_pref_ == ip_addr_pref::IPV4 || if ((net->addr_pref_ == ip_addr_pref::IPV4 ||
net->addr_pref_ == ip_addr_pref::IPV4_6) && net->addr_pref_ == ip_addr_pref::IPV4_6) &&
net->cidr_ == 0 && ifa->ifa_family == AF_INET) { net->cidr_ == 0 && ifa->ifa_family == AF_INET) {
net->ipaddr_ = if (inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta), ipaddr, sizeof(ipaddr)) !=
inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta), ipaddr, sizeof(ipaddr)); nullptr) {
net->ipaddr_ = ipaddr;
net->cidr_ = ifa->ifa_prefixlen; net->cidr_ = ifa->ifa_prefixlen;
addr_changed = true;
changed_ipaddr = net->ipaddr_;
changed_cidr = net->cidr_;
}
} else if ((net->addr_pref_ == ip_addr_pref::IPV6 || } else if ((net->addr_pref_ == ip_addr_pref::IPV6 ||
net->addr_pref_ == ip_addr_pref::IPV4_6) && net->addr_pref_ == ip_addr_pref::IPV4_6) &&
net->cidr6_ == 0 && ifa->ifa_family == AF_INET6) { net->cidr6_ == 0 && ifa->ifa_family == AF_INET6) {
net->ipaddr6_ = if (inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta), ipaddr, sizeof(ipaddr)) !=
inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta), ipaddr, sizeof(ipaddr)); nullptr) {
net->ipaddr6_ = ipaddr;
net->cidr6_ = ifa->ifa_prefixlen; net->cidr6_ = ifa->ifa_prefixlen;
addr_changed = true;
changed_ipaddr = net->ipaddr6_;
changed_cidr = net->cidr6_;
}
} }
switch (ifa->ifa_family) { switch (ifa->ifa_family) {
@@ -657,7 +672,10 @@ int waybar::modules::Network::handleEvents(struct nl_msg* msg, void* data) {
net->netmask6_ = inet_ntop(ifa->ifa_family, &netmask6, 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_); if (addr_changed) {
spdlog::debug("network: {}, new addr {}/{}", net->ifname_, changed_ipaddr,
changed_cidr);
}
} else { } else {
net->ipaddr_.clear(); net->ipaddr_.clear();
net->ipaddr6_.clear(); net->ipaddr6_.clear();
@@ -719,16 +737,20 @@ int waybar::modules::Network::handleEvents(struct nl_msg* msg, void* data) {
/* The destination address. /* The destination address.
* Should be either missing, or maybe all 0s. Accept both. * Should be either missing, or maybe all 0s. Accept both.
*/ */
const uint32_t nr_zeroes = (family == AF_INET) ? 4 : 16; auto* dest = (const unsigned char*)RTA_DATA(attr);
unsigned char c = 0; size_t dest_size = RTA_PAYLOAD(attr);
size_t dstlen = RTA_PAYLOAD(attr); for (size_t i = 0; i < dest_size; ++i) {
if (dstlen != nr_zeroes) { if (dest[i] != 0) {
has_destination = true;
break; break;
} }
for (uint32_t i = 0; i < dstlen; i += 1) {
c |= *((unsigned char*)RTA_DATA(attr) + i);
} }
has_destination = (c == 0);
if (rtm->rtm_dst_len != 0) {
// We have found a destination like 0.0.0.0/24, this is not a
// default gateway route.
has_destination = true;
}
break; break;
} }
case RTA_OIF: case RTA_OIF:

View File

@@ -17,6 +17,7 @@
#include "giomm/dataoutputstream.h" #include "giomm/dataoutputstream.h"
#include "giomm/unixinputstream.h" #include "giomm/unixinputstream.h"
#include "giomm/unixoutputstream.h" #include "giomm/unixoutputstream.h"
#include "util/scoped_fd.hpp"
namespace waybar::modules::niri { namespace waybar::modules::niri {
@@ -30,7 +31,7 @@ int IPC::connectToSocket() {
} }
struct sockaddr_un addr; struct sockaddr_un addr;
int socketfd = socket(AF_UNIX, SOCK_STREAM, 0); util::ScopedFd socketfd(socket(AF_UNIX, SOCK_STREAM, 0));
if (socketfd == -1) { if (socketfd == -1) {
throw std::runtime_error("socketfd failed"); throw std::runtime_error("socketfd failed");
@@ -45,11 +46,10 @@ int IPC::connectToSocket() {
int l = sizeof(struct sockaddr_un); int l = sizeof(struct sockaddr_un);
if (connect(socketfd, (struct sockaddr*)&addr, l) == -1) { if (connect(socketfd, (struct sockaddr*)&addr, l) == -1) {
close(socketfd);
throw std::runtime_error("unable to connect"); throw std::runtime_error("unable to connect");
} }
return socketfd; return socketfd.release();
} }
void IPC::startIPC() { void IPC::startIPC() {
@@ -235,7 +235,7 @@ void IPC::unregisterForIPC(EventHandler* ev_handler) {
} }
Json::Value IPC::send(const Json::Value& request) { Json::Value IPC::send(const Json::Value& request) {
int socketfd = connectToSocket(); util::ScopedFd socketfd(connectToSocket());
auto unix_istream = Gio::UnixInputStream::create(socketfd, true); auto unix_istream = Gio::UnixInputStream::create(socketfd, true);
auto unix_ostream = Gio::UnixOutputStream::create(socketfd, false); auto unix_ostream = Gio::UnixOutputStream::create(socketfd, false);

View File

@@ -32,6 +32,7 @@ void Language::updateFromIPC() {
auto ipcLock = gIPC->lockData(); auto ipcLock = gIPC->lockData();
layouts_.clear(); layouts_.clear();
layouts_.reserve(gIPC->keyboardLayoutNames().size());
for (const auto& fullName : gIPC->keyboardLayoutNames()) layouts_.push_back(getLayout(fullName)); for (const auto& fullName : gIPC->keyboardLayoutNames()) layouts_.push_back(getLayout(fullName));
current_idx_ = gIPC->keyboardLayoutCurrent(); current_idx_ = gIPC->keyboardLayoutCurrent();

View File

@@ -8,7 +8,8 @@ namespace waybar::modules::SNI {
Host::Host(const std::size_t id, const Json::Value& config, const Bar& bar, Host::Host(const std::size_t id, const Json::Value& config, const Bar& bar,
const std::function<void(std::unique_ptr<Item>&)>& on_add, const std::function<void(std::unique_ptr<Item>&)>& on_add,
const std::function<void(std::unique_ptr<Item>&)>& on_remove) const std::function<void(std::unique_ptr<Item>&)>& on_remove,
const std::function<void()>& on_update)
: bus_name_("org.kde.StatusNotifierHost-" + std::to_string(getpid()) + "-" + : bus_name_("org.kde.StatusNotifierHost-" + std::to_string(getpid()) + "-" +
std::to_string(id)), std::to_string(id)),
object_path_("/StatusNotifierHost/" + std::to_string(id)), object_path_("/StatusNotifierHost/" + std::to_string(id)),
@@ -17,7 +18,8 @@ Host::Host(const std::size_t id, const Json::Value& config, const Bar& bar,
config_(config), config_(config),
bar_(bar), bar_(bar),
on_add_(on_add), on_add_(on_add),
on_remove_(on_remove) {} on_remove_(on_remove),
on_update_(on_update) {}
Host::~Host() { Host::~Host() {
if (bus_name_id_ > 0) { if (bus_name_id_ > 0) {
@@ -54,7 +56,7 @@ void Host::nameVanished(const Glib::RefPtr<Gio::DBus::Connection>& conn, const G
g_cancellable_cancel(cancellable_); g_cancellable_cancel(cancellable_);
g_clear_object(&cancellable_); g_clear_object(&cancellable_);
g_clear_object(&watcher_); g_clear_object(&watcher_);
items_.clear(); clearItems();
} }
void Host::proxyReady(GObject* src, GAsyncResult* res, gpointer data) { void Host::proxyReady(GObject* src, GAsyncResult* res, gpointer data) {
@@ -117,13 +119,50 @@ void Host::itemUnregistered(SnWatcher* watcher, const gchar* service, gpointer d
auto [bus_name, object_path] = host->getBusNameAndObjectPath(service); auto [bus_name, object_path] = host->getBusNameAndObjectPath(service);
for (auto it = host->items_.begin(); it != host->items_.end(); ++it) { for (auto it = host->items_.begin(); it != host->items_.end(); ++it) {
if ((*it)->bus_name == bus_name && (*it)->object_path == object_path) { if ((*it)->bus_name == bus_name && (*it)->object_path == object_path) {
host->on_remove_(*it); host->removeItem(it);
host->items_.erase(it);
break; break;
} }
} }
} }
void Host::itemReady(Item& item) {
auto it = std::find_if(items_.begin(), items_.end(),
[&item](const auto& candidate) { return candidate.get() == &item; });
if (it != items_.end() && (*it)->isReady()) {
on_add_(*it);
}
}
void Host::itemInvalidated(Item& item) {
auto it = std::find_if(items_.begin(), items_.end(),
[&item](const auto& candidate) { return candidate.get() == &item; });
if (it != items_.end()) {
removeItem(it);
}
}
void Host::removeItem(std::vector<std::unique_ptr<Item>>::iterator it) {
if ((*it)->isReady()) {
on_remove_(*it);
}
items_.erase(it);
}
void Host::clearItems() {
bool removed_ready_item = false;
for (auto& item : items_) {
if (item->isReady()) {
on_remove_(item);
removed_ready_item = true;
}
}
bool had_items = !items_.empty();
items_.clear();
if (had_items && !removed_ready_item) {
on_update_();
}
}
std::tuple<std::string, std::string> Host::getBusNameAndObjectPath(const std::string service) { std::tuple<std::string, std::string> Host::getBusNameAndObjectPath(const std::string service) {
auto it = service.find('/'); auto it = service.find('/');
if (it != std::string::npos) { if (it != std::string::npos) {
@@ -132,15 +171,16 @@ std::tuple<std::string, std::string> Host::getBusNameAndObjectPath(const std::st
return {service, "/StatusNotifierItem"}; return {service, "/StatusNotifierItem"};
} }
void Host::addRegisteredItem(std::string service) { void Host::addRegisteredItem(const std::string& service) {
std::string bus_name, object_path; std::string bus_name, object_path;
std::tie(bus_name, object_path) = getBusNameAndObjectPath(service); std::tie(bus_name, object_path) = getBusNameAndObjectPath(service);
auto it = std::find_if(items_.begin(), items_.end(), [&bus_name, &object_path](const auto& item) { auto it = std::find_if(items_.begin(), items_.end(), [&bus_name, &object_path](const auto& item) {
return bus_name == item->bus_name && object_path == item->object_path; return bus_name == item->bus_name && object_path == item->object_path;
}); });
if (it == items_.end()) { if (it == items_.end()) {
items_.emplace_back(new Item(bus_name, object_path, config_, bar_)); items_.emplace_back(new Item(
on_add_(items_.back()); bus_name, object_path, config_, bar_, [this](Item& item) { itemReady(item); },
[this](Item& item) { itemInvalidated(item); }, on_update_));
} }
} }

View File

@@ -5,6 +5,7 @@
#include <gtkmm/tooltip.h> #include <gtkmm/tooltip.h>
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
#include <algorithm>
#include <filesystem> #include <filesystem>
#include <fstream> #include <fstream>
#include <map> #include <map>
@@ -37,13 +38,18 @@ namespace waybar::modules::SNI {
static const Glib::ustring SNI_INTERFACE_NAME = sn_item_interface_info()->name; static const Glib::ustring SNI_INTERFACE_NAME = sn_item_interface_info()->name;
static const unsigned UPDATE_DEBOUNCE_TIME = 10; static const unsigned UPDATE_DEBOUNCE_TIME = 10;
Item::Item(const std::string& bn, const std::string& op, const Json::Value& config, const Bar& bar) Item::Item(const std::string& bn, const std::string& op, const Json::Value& config, const Bar& bar,
const std::function<void(Item&)>& on_ready,
const std::function<void(Item&)>& on_invalidate, const std::function<void()>& on_updated)
: bus_name(bn), : bus_name(bn),
object_path(op), object_path(op),
icon_size(16), icon_size(16),
effective_icon_size(0), effective_icon_size(0),
icon_theme(Gtk::IconTheme::create()), icon_theme(Gtk::IconTheme::create()),
bar_(bar) { bar_(bar),
on_ready_(on_ready),
on_invalidate_(on_invalidate),
on_updated_(on_updated) {
if (config["icon-size"].isUInt()) { if (config["icon-size"].isUInt()) {
icon_size = config["icon-size"].asUInt(); icon_size = config["icon-size"].asUInt();
} }
@@ -85,6 +91,8 @@ Item::~Item() {
} }
} }
bool Item::isReady() const { return ready_; }
bool Item::handleMouseEnter(GdkEventCrossing* const& e) { bool Item::handleMouseEnter(GdkEventCrossing* const& e) {
event_box.set_state_flags(Gtk::StateFlags::STATE_FLAG_PRELIGHT); event_box.set_state_flags(Gtk::StateFlags::STATE_FLAG_PRELIGHT);
return false; return false;
@@ -112,14 +120,18 @@ void Item::proxyReady(Glib::RefPtr<Gio::AsyncResult>& result) {
if (this->id.empty() || this->category.empty()) { if (this->id.empty() || this->category.empty()) {
spdlog::error("Invalid Status Notifier Item: {}, {}", bus_name, object_path); spdlog::error("Invalid Status Notifier Item: {}, {}", bus_name, object_path);
invalidate();
return; return;
} }
this->updateImage(); this->updateImage();
setReady();
} catch (const Glib::Error& err) { } catch (const Glib::Error& err) {
spdlog::error("Failed to create DBus Proxy for {} {}: {}", bus_name, object_path, err.what()); spdlog::error("Failed to create DBus Proxy for {} {}: {}", bus_name, object_path, err.what());
invalidate();
} catch (const std::exception& err) { } catch (const std::exception& err) {
spdlog::error("Failed to create DBus Proxy for {} {}: {}", bus_name, object_path, err.what()); spdlog::error("Failed to create DBus Proxy for {} {}: {}", bus_name, object_path, err.what());
invalidate();
} }
} }
@@ -184,11 +196,11 @@ void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) {
} else if (name == "OverlayIconName") { } else if (name == "OverlayIconName") {
overlay_icon_name = get_variant<std::string>(value); overlay_icon_name = get_variant<std::string>(value);
} else if (name == "OverlayIconPixmap") { } else if (name == "OverlayIconPixmap") {
// TODO: overlay_icon_pixmap overlay_icon_pixmap = extractPixBuf(value.gobj());
} else if (name == "AttentionIconName") { } else if (name == "AttentionIconName") {
attention_icon_name = get_variant<std::string>(value); attention_icon_name = get_variant<std::string>(value);
} else if (name == "AttentionIconPixmap") { } else if (name == "AttentionIconPixmap") {
// TODO: attention_icon_pixmap attention_icon_pixmap = extractPixBuf(value.gobj());
} else if (name == "AttentionMovieName") { } else if (name == "AttentionMovieName") {
attention_movie_name = get_variant<std::string>(value); attention_movie_name = get_variant<std::string>(value);
} else if (name == "ToolTip") { } else if (name == "ToolTip") {
@@ -217,18 +229,35 @@ void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) {
} }
void Item::setStatus(const Glib::ustring& value) { void Item::setStatus(const Glib::ustring& value) {
Glib::ustring lower = value.lowercase(); status_ = value.lowercase();
event_box.set_visible(show_passive_ || lower.compare("passive") != 0); event_box.set_visible(show_passive_ || status_.compare("passive") != 0);
auto style = event_box.get_style_context(); auto style = event_box.get_style_context();
for (const auto& class_name : style->list_classes()) { for (const auto& class_name : style->list_classes()) {
style->remove_class(class_name); style->remove_class(class_name);
} }
if (lower.compare("needsattention") == 0) { auto css_class = status_;
if (css_class.compare("needsattention") == 0) {
// convert status to dash-case for CSS // convert status to dash-case for CSS
lower = "needs-attention"; css_class = "needs-attention";
} }
style->add_class(lower); style->add_class(css_class);
on_updated_();
}
void Item::setReady() {
if (ready_) {
return;
}
ready_ = true;
on_ready_(*this);
}
void Item::invalidate() {
if (ready_) {
ready_ = false;
}
on_invalidate_(*this);
} }
void Item::setCustomIcon(const std::string& id) { void Item::setCustomIcon(const std::string& id) {
@@ -287,8 +316,8 @@ void Item::processUpdatedProperties(Glib::RefPtr<Gio::AsyncResult>& _result) {
static const std::map<std::string_view, std::set<std::string_view>> signal2props = { static const std::map<std::string_view, std::set<std::string_view>> signal2props = {
{"NewTitle", {"Title"}}, {"NewTitle", {"Title"}},
{"NewIcon", {"IconName", "IconPixmap"}}, {"NewIcon", {"IconName", "IconPixmap"}},
// {"NewAttentionIcon", {"AttentionIconName", "AttentionIconPixmap", "AttentionMovieName"}}, {"NewAttentionIcon", {"AttentionIconName", "AttentionIconPixmap", "AttentionMovieName"}},
// {"NewOverlayIcon", {"OverlayIconName", "OverlayIconPixmap"}}, {"NewOverlayIcon", {"OverlayIconName", "OverlayIconPixmap"}},
{"NewIconThemePath", {"IconThemePath"}}, {"NewIconThemePath", {"IconThemePath"}},
{"NewToolTip", {"ToolTip"}}, {"NewToolTip", {"ToolTip"}},
{"NewStatus", {"Status"}}, {"NewStatus", {"Status"}},
@@ -336,11 +365,20 @@ Glib::RefPtr<Gdk::Pixbuf> Item::extractPixBuf(GVariant* variant) {
if (array != nullptr) { if (array != nullptr) {
g_free(array); g_free(array);
} }
#if GLIB_MAJOR_VERSION >= 2 && GLIB_MINOR_VERSION >= 68 // We must allocate our own array because the data from GVariant is read-only
array = static_cast<guchar*>(g_memdup2(data, size)); // and we need to modify it to convert ARGB to RGBA.
#else array = static_cast<guchar*>(g_malloc(size));
array = static_cast<guchar*>(g_memdup(data, size));
#endif // Copy and convert ARGB to RGBA in one pass to avoid g_memdup2 overhead
const guchar* src = static_cast<const guchar*>(data);
for (gsize i = 0; i < size; i += 4) {
guchar alpha = src[i];
array[i] = src[i + 1];
array[i + 1] = src[i + 2];
array[i + 2] = src[i + 3];
array[i + 3] = alpha;
}
lwidth = width; lwidth = width;
lheight = height; lheight = height;
} }
@@ -349,14 +387,6 @@ Glib::RefPtr<Gdk::Pixbuf> Item::extractPixBuf(GVariant* variant) {
} }
g_variant_iter_free(it); g_variant_iter_free(it);
if (array != nullptr) { if (array != nullptr) {
/* argb to rgba */
for (uint32_t i = 0; i < 4U * lwidth * lheight; i += 4) {
guchar alpha = array[i];
array[i] = array[i + 1];
array[i + 1] = array[i + 2];
array[i + 2] = array[i + 3];
array[i + 3] = alpha;
}
return Gdk::Pixbuf::create_from_data(array, Gdk::Colorspace::COLORSPACE_RGB, true, 8, lwidth, return Gdk::Pixbuf::create_from_data(array, Gdk::Colorspace::COLORSPACE_RGB, true, 8, lwidth,
lheight, 4 * lwidth, &pixbuf_data_deleter); lheight, 4 * lwidth, &pixbuf_data_deleter);
} }
@@ -377,36 +407,24 @@ void Item::updateImage() {
pixbuf = pixbuf->scale_simple(width, scaled_icon_size, Gdk::InterpType::INTERP_BILINEAR); pixbuf = pixbuf->scale_simple(width, scaled_icon_size, Gdk::InterpType::INTERP_BILINEAR);
} }
pixbuf = overlayPixbufs(pixbuf, getOverlayIconPixbuf());
auto surface = auto surface =
Gdk::Cairo::create_surface_from_pixbuf(pixbuf, image.get_scale_factor(), image.get_window()); Gdk::Cairo::create_surface_from_pixbuf(pixbuf, image.get_scale_factor(), image.get_window());
image.set(surface); image.set(surface);
} }
Glib::RefPtr<Gdk::Pixbuf> Item::getIconPixbuf() { Glib::RefPtr<Gdk::Pixbuf> Item::getIconPixbuf() {
if (!icon_name.empty()) { if (status_ == "needsattention") {
try { if (auto attention_pixbuf = getAttentionIconPixbuf()) {
std::ifstream temp(icon_name); return attention_pixbuf;
if (temp.is_open()) {
return Gdk::Pixbuf::create_from_file(icon_name);
}
} catch (Glib::Error& e) {
// Ignore because we want to also try different methods of getting an icon.
//
// But a warning is logged, as the file apparently exists, but there was
// a failure in creating a pixbuf out of it.
spdlog::warn("Item '{}': {}", id, static_cast<std::string>(e.what()));
}
try {
// Will throw if it can not find an icon.
return getIconByName(icon_name, getScaledIconSize());
} catch (Glib::Error& e) {
spdlog::trace("Item '{}': {}", id, static_cast<std::string>(e.what()));
} }
} }
// Return the pixmap only if an icon for the given name could not be found. if (auto pixbuf = loadIconFromNameOrFile(icon_name, true)) {
return pixbuf;
}
if (icon_pixmap) { if (icon_pixmap) {
return icon_pixmap; return icon_pixmap;
} }
@@ -421,9 +439,78 @@ Glib::RefPtr<Gdk::Pixbuf> Item::getIconPixbuf() {
return getIconByName("image-missing", getScaledIconSize()); return getIconByName("image-missing", getScaledIconSize());
} }
Glib::RefPtr<Gdk::Pixbuf> Item::getIconByName(const std::string& name, int request_size) { Glib::RefPtr<Gdk::Pixbuf> Item::getAttentionIconPixbuf() {
icon_theme->rescan_if_needed(); if (auto pixbuf = loadIconFromNameOrFile(attention_icon_name, false)) {
return pixbuf;
}
if (auto pixbuf = loadIconFromNameOrFile(attention_movie_name, false)) {
return pixbuf;
}
return attention_icon_pixmap;
}
Glib::RefPtr<Gdk::Pixbuf> Item::getOverlayIconPixbuf() {
if (auto pixbuf = loadIconFromNameOrFile(overlay_icon_name, false)) {
return pixbuf;
}
return overlay_icon_pixmap;
}
Glib::RefPtr<Gdk::Pixbuf> Item::loadIconFromNameOrFile(const std::string& name, bool log_failure) {
if (name.empty()) {
return {};
}
try {
std::ifstream temp(name);
if (temp.is_open()) {
return Gdk::Pixbuf::create_from_file(name);
}
} catch (const Glib::Error& e) {
if (log_failure) {
spdlog::warn("Item '{}': {}", id, static_cast<std::string>(e.what()));
}
}
try {
return getIconByName(name, getScaledIconSize());
} catch (const Glib::Error& e) {
if (log_failure) {
spdlog::trace("Item '{}': {}", id, static_cast<std::string>(e.what()));
}
}
return {};
}
Glib::RefPtr<Gdk::Pixbuf> Item::overlayPixbufs(const Glib::RefPtr<Gdk::Pixbuf>& base,
const Glib::RefPtr<Gdk::Pixbuf>& overlay) {
if (!base || !overlay) {
return base;
}
auto composed = base->copy();
if (!composed) {
return base;
}
int overlay_target_size =
std::max(1, std::min(composed->get_width(), composed->get_height()) / 2);
auto scaled_overlay = overlay->scale_simple(overlay_target_size, overlay_target_size,
Gdk::InterpType::INTERP_BILINEAR);
if (!scaled_overlay) {
return composed;
}
int dest_x = std::max(0, composed->get_width() - scaled_overlay->get_width());
int dest_y = std::max(0, composed->get_height() - scaled_overlay->get_height());
scaled_overlay->composite(composed, dest_x, dest_y, scaled_overlay->get_width(),
scaled_overlay->get_height(), dest_x, dest_y, 1.0, 1.0,
Gdk::InterpType::INTERP_BILINEAR, 255);
return composed;
}
Glib::RefPtr<Gdk::Pixbuf> Item::getIconByName(const std::string& name, int request_size) {
if (!icon_theme_path.empty()) { if (!icon_theme_path.empty()) {
auto icon_info = icon_theme->lookup_icon(name.c_str(), request_size, auto icon_info = icon_theme->lookup_icon(name.c_str(), request_size,
Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE); Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE);
@@ -465,6 +552,9 @@ void Item::makeMenu() {
} }
bool Item::handleClick(GdkEventButton* const& ev) { bool Item::handleClick(GdkEventButton* const& ev) {
if (!proxy_) {
return false;
}
auto parameters = Glib::VariantContainerBase::create_tuple( auto parameters = Glib::VariantContainerBase::create_tuple(
{Glib::Variant<int>::create(ev->x_root + bar_.x_global), {Glib::Variant<int>::create(ev->x_root + bar_.x_global),
Glib::Variant<int>::create(ev->y_root + bar_.y_global)}); Glib::Variant<int>::create(ev->y_root + bar_.y_global)});
@@ -492,6 +582,9 @@ bool Item::handleClick(GdkEventButton* const& ev) {
} }
bool Item::handleScroll(GdkEventScroll* const& ev) { bool Item::handleScroll(GdkEventScroll* const& ev) {
if (!proxy_) {
return false;
}
int dx = 0, dy = 0; int dx = 0, dy = 0;
switch (ev->direction) { switch (ev->direction) {
case GDK_SCROLL_UP: case GDK_SCROLL_UP:

View File

@@ -13,7 +13,8 @@ Tray::Tray(const std::string& id, const Bar& bar, const Json::Value& config)
box_(bar.orientation, 0), box_(bar.orientation, 0),
watcher_(SNI::Watcher::getInstance()), watcher_(SNI::Watcher::getInstance()),
host_(nb_hosts_, config, bar, std::bind(&Tray::onAdd, this, std::placeholders::_1), host_(nb_hosts_, config, bar, std::bind(&Tray::onAdd, this, std::placeholders::_1),
std::bind(&Tray::onRemove, this, std::placeholders::_1)) { std::bind(&Tray::onRemove, this, std::placeholders::_1),
std::bind(&Tray::queueUpdate, this)) {
box_.set_name("tray"); box_.set_name("tray");
event_box_.add(box_); event_box_.add(box_);
if (!id.empty()) { if (!id.empty()) {
@@ -33,6 +34,8 @@ Tray::Tray(const std::string& id, const Bar& bar, const Json::Value& config)
dp.emit(); dp.emit();
} }
void Tray::queueUpdate() { dp.emit(); }
void Tray::onAdd(std::unique_ptr<Item>& item) { void Tray::onAdd(std::unique_ptr<Item>& item) {
if (config_["reverse-direction"].isBool() && config_["reverse-direction"].asBool()) { if (config_["reverse-direction"].isBool() && config_["reverse-direction"].asBool()) {
box_.pack_end(item->event_box); box_.pack_end(item->event_box);

View File

@@ -69,7 +69,7 @@ gboolean Watcher::handleRegisterHost(Watcher* obj, GDBusMethodInvocation* invoca
if (watch != nullptr) { if (watch != nullptr) {
g_warning("Status Notifier Host with bus name '%s' and object path '%s' is already registered", g_warning("Status Notifier Host with bus name '%s' and object path '%s' is already registered",
bus_name, object_path); bus_name, object_path);
sn_watcher_complete_register_item(obj->watcher_, invocation); sn_watcher_complete_register_host(obj->watcher_, invocation);
return TRUE; return TRUE;
} }
watch = gfWatchNew(GF_WATCH_TYPE_HOST, service, bus_name, object_path, obj); watch = gfWatchNew(GF_WATCH_TYPE_HOST, service, bus_name, object_path, obj);
@@ -98,8 +98,8 @@ gboolean Watcher::handleRegisterItem(Watcher* obj, GDBusMethodInvocation* invoca
} }
auto watch = gfWatchFind(obj->items_, bus_name, object_path); auto watch = gfWatchFind(obj->items_, bus_name, object_path);
if (watch != nullptr) { if (watch != nullptr) {
g_warning("Status Notifier Item with bus name '%s' and object path '%s' is already registered", spdlog::debug("Ignoring duplicate Status Notifier Item registration for '{}' at '{}'", bus_name,
bus_name, object_path); object_path);
sn_watcher_complete_register_item(obj->watcher_, invocation); sn_watcher_complete_register_item(obj->watcher_, invocation);
return TRUE; return TRUE;
} }
@@ -158,7 +158,7 @@ void Watcher::nameVanished(GDBusConnection* connection, const char* name, gpoint
watch->watcher->hosts_ = g_slist_remove(watch->watcher->hosts_, watch); watch->watcher->hosts_ = g_slist_remove(watch->watcher->hosts_, watch);
if (watch->watcher->hosts_ == nullptr) { if (watch->watcher->hosts_ == nullptr) {
sn_watcher_set_is_host_registered(watch->watcher->watcher_, FALSE); sn_watcher_set_is_host_registered(watch->watcher->watcher_, FALSE);
sn_watcher_emit_host_registered(watch->watcher->watcher_); sn_watcher_emit_host_unregistered(watch->watcher->watcher_);
} }
} else if (watch->type == GF_WATCH_TYPE_ITEM) { } else if (watch->type == GF_WATCH_TYPE_ITEM) {
watch->watcher->items_ = g_slist_remove(watch->watcher->items_, watch); watch->watcher->items_ = g_slist_remove(watch->watcher->items_, watch);
@@ -167,6 +167,7 @@ void Watcher::nameVanished(GDBusConnection* connection, const char* name, gpoint
sn_watcher_emit_item_unregistered(watch->watcher->watcher_, tmp); sn_watcher_emit_item_unregistered(watch->watcher->watcher_, tmp);
g_free(tmp); g_free(tmp);
} }
gfWatchFree(watch);
} }
void Watcher::updateRegisteredItems(SnWatcher* obj) { void Watcher::updateRegisteredItems(SnWatcher* obj) {

View File

@@ -60,7 +60,7 @@ BarIpcClient::BarIpcClient(waybar::Bar& bar) : bar_{bar} {
}); });
} }
bool BarIpcClient::isModuleEnabled(std::string name) { bool BarIpcClient::isModuleEnabled(const std::string& name) {
for (const auto& section : {"modules-left", "modules-center", "modules-right"}) { for (const auto& section : {"modules-left", "modules-center", "modules-right"}) {
if (const auto& modules = bar_.config.get(section, {}); modules.isArray()) { if (const auto& modules = bar_.config.get(section, {}); modules.isArray()) {
for (const auto& module : modules) { for (const auto& module : modules) {

View File

@@ -9,8 +9,8 @@ namespace waybar::modules::sway {
Ipc::Ipc() { Ipc::Ipc() {
const std::string& socketPath = getSocketPath(); const std::string& socketPath = getSocketPath();
fd_ = open(socketPath); fd_ = util::ScopedFd(open(socketPath));
fd_event_ = open(socketPath); fd_event_ = util::ScopedFd(open(socketPath));
} }
Ipc::~Ipc() { Ipc::~Ipc() {
@@ -21,15 +21,11 @@ Ipc::~Ipc() {
if (write(fd_, "close-sway-ipc", 14) == -1) { if (write(fd_, "close-sway-ipc", 14) == -1) {
spdlog::error("Failed to close sway IPC"); spdlog::error("Failed to close sway IPC");
} }
close(fd_);
fd_ = -1;
} }
if (fd_event_ > 0) { if (fd_event_ > 0) {
if (write(fd_event_, "close-sway-ipc", 14) == -1) { if (write(fd_event_, "close-sway-ipc", 14) == -1) {
spdlog::error("Failed to close sway IPC event handler"); spdlog::error("Failed to close sway IPC event handler");
} }
close(fd_event_);
fd_event_ = -1;
} }
} }
@@ -64,7 +60,7 @@ const std::string Ipc::getSocketPath() const {
} }
int Ipc::open(const std::string& socketPath) const { int Ipc::open(const std::string& socketPath) const {
int32_t fd = socket(AF_UNIX, SOCK_STREAM, 0); util::ScopedFd fd(socket(AF_UNIX, SOCK_STREAM, 0));
if (fd == -1) { if (fd == -1) {
throw std::runtime_error("Unable to open Unix socket"); throw std::runtime_error("Unable to open Unix socket");
} }
@@ -78,7 +74,7 @@ int Ipc::open(const std::string& socketPath) const {
if (::connect(fd, reinterpret_cast<struct sockaddr*>(&addr), l) == -1) { if (::connect(fd, reinterpret_cast<struct sockaddr*>(&addr), l) == -1) {
throw std::runtime_error("Unable to connect to Sway"); throw std::runtime_error("Unable to connect to Sway");
} }
return fd; return fd.release();
} }
struct Ipc::ipc_response Ipc::recv(int fd) { struct Ipc::ipc_response Ipc::recv(int fd) {

View File

@@ -124,7 +124,7 @@ auto Language::update() -> void {
ALabel::update(); ALabel::update();
} }
auto Language::set_current_layout(std::string current_layout) -> void { auto Language::set_current_layout(const std::string& current_layout) -> void {
label_.get_style_context()->remove_class(layout_.short_name); label_.get_style_context()->remove_class(layout_.short_name);
layout_ = layouts_map_[current_layout]; layout_ = layouts_map_[current_layout];
label_.get_style_context()->add_class(layout_.short_name); label_.get_style_context()->add_class(layout_.short_name);

View File

@@ -184,9 +184,9 @@ std::tuple<std::string, std::string, std::string, std::string> getWindowInfo(
continue; continue;
} }
if (!marks.empty()) { if (!marks.empty()) {
marks += ','; marks.append(",");
} }
marks += m.asString(); marks.append(m.asString());
} }
} }
return {app_id, app_class, shell, marks}; return {app_id, app_class, shell, marks};

View File

@@ -10,7 +10,7 @@ namespace waybar::modules::sway {
// Helper function to assign a number to a workspace, just like sway. In fact // Helper function to assign a number to a workspace, just like sway. In fact
// this is taken quite verbatim from `sway/ipc-json.c`. // this is taken quite verbatim from `sway/ipc-json.c`.
int Workspaces::convertWorkspaceNameToNum(std::string name) { int Workspaces::convertWorkspaceNameToNum(const std::string& name) {
if (isdigit(name[0]) != 0) { if (isdigit(name[0]) != 0) {
errno = 0; errno = 0;
char* endptr = nullptr; char* endptr = nullptr;
@@ -487,7 +487,7 @@ std::string Workspaces::getCycleWorkspace(std::vector<Json::Value>::iterator it,
return (*it)["name"].asString(); return (*it)["name"].asString();
} }
std::string Workspaces::trimWorkspaceName(std::string name) { std::string Workspaces::trimWorkspaceName(const std::string& name) {
std::size_t found = name.find(':'); std::size_t found = name.find(':');
if (found != std::string::npos) { if (found != std::string::npos) {
return name.substr(found + 1); return name.substr(found + 1);

View File

@@ -1,10 +1,15 @@
#include "modules/systemd_failed_units.hpp" #include "modules/systemd_failed_units.hpp"
#include <fmt/format.h>
#include <giomm/dbusproxy.h> #include <giomm/dbusproxy.h>
#include <glibmm/markup.h>
#include <glibmm/variant.h> #include <glibmm/variant.h>
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
#include <cstdint> #include <cstdint>
#include <exception>
#include <stdexcept>
#include <tuple>
static const unsigned UPDATE_DEBOUNCE_TIME_MS = 1000; static const unsigned UPDATE_DEBOUNCE_TIME_MS = 1000;
@@ -12,39 +17,71 @@ namespace waybar::modules {
SystemdFailedUnits::SystemdFailedUnits(const std::string& id, const Json::Value& config) SystemdFailedUnits::SystemdFailedUnits(const std::string& id, const Json::Value& config)
: ALabel(config, "systemd-failed-units", id, "{nr_failed} failed", 1), : ALabel(config, "systemd-failed-units", id, "{nr_failed} failed", 1),
hide_on_ok(true), hide_on_ok_(true),
update_pending(false), tooltip_format_(
nr_failed_system(0), "System: {system_state}\nUser: {user_state}\nFailed units ({nr_failed}):\n"
nr_failed_user(0), "{failed_units_list}"),
nr_failed(0), tooltip_format_ok_("System: {system_state}\nUser: {user_state}"),
last_status() { tooltip_unit_format_("{name}: {description}"),
update_pending_(false),
nr_failed_system_(0),
nr_failed_user_(0),
nr_failed_(0),
last_status_() {
if (config["hide-on-ok"].isBool()) { if (config["hide-on-ok"].isBool()) {
hide_on_ok = config["hide-on-ok"].asBool(); hide_on_ok_ = config["hide-on-ok"].asBool();
} }
if (config["format-ok"].isString()) { if (config["format-ok"].isString()) {
format_ok = config["format-ok"].asString(); format_ok_ = config["format-ok"].asString();
} else { } else {
format_ok = format_; format_ok_ = format_;
}
if (config["tooltip-format"].isString()) {
tooltip_format_ = config["tooltip-format"].asString();
}
if (config["tooltip-format-ok"].isString()) {
tooltip_format_ok_ = config["tooltip-format-ok"].asString();
}
if (config["tooltip-unit-format"].isString()) {
tooltip_unit_format_ = config["tooltip-unit-format"].asString();
} }
/* Default to enable both "system" and "user". */ /* Default to enable both "system" and "user". */
if (!config["system"].isBool() || config["system"].asBool()) { if (!config["system"].isBool() || config["system"].asBool()) {
system_proxy = Gio::DBus::Proxy::create_for_bus_sync( system_props_proxy_ = Gio::DBus::Proxy::create_for_bus_sync(
Gio::DBus::BusType::BUS_TYPE_SYSTEM, "org.freedesktop.systemd1", Gio::DBus::BusType::BUS_TYPE_SYSTEM, "org.freedesktop.systemd1",
"/org/freedesktop/systemd1", "org.freedesktop.DBus.Properties"); "/org/freedesktop/systemd1", "org.freedesktop.DBus.Properties");
if (!system_proxy) { if (!system_props_proxy_) {
throw std::runtime_error("Unable to connect to systemwide systemd DBus!"); throw std::runtime_error("Unable to connect to systemwide systemd DBus!");
} }
system_proxy->signal_signal().connect(sigc::mem_fun(*this, &SystemdFailedUnits::notify_cb)); system_props_proxy_->signal_signal().connect(
sigc::mem_fun(*this, &SystemdFailedUnits::notify_cb));
try {
system_manager_proxy_ = Gio::DBus::Proxy::create_for_bus_sync(
Gio::DBus::BusType::BUS_TYPE_SYSTEM, "org.freedesktop.systemd1",
"/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager");
} catch (const Glib::Error& e) {
spdlog::warn("Unable to connect to systemwide systemd Manager interface: {}",
e.what().c_str());
}
} }
if (!config["user"].isBool() || config["user"].asBool()) { if (!config["user"].isBool() || config["user"].asBool()) {
user_proxy = Gio::DBus::Proxy::create_for_bus_sync( user_props_proxy_ = Gio::DBus::Proxy::create_for_bus_sync(
Gio::DBus::BusType::BUS_TYPE_SESSION, "org.freedesktop.systemd1", Gio::DBus::BusType::BUS_TYPE_SESSION, "org.freedesktop.systemd1",
"/org/freedesktop/systemd1", "org.freedesktop.DBus.Properties"); "/org/freedesktop/systemd1", "org.freedesktop.DBus.Properties");
if (!user_proxy) { if (!user_props_proxy_) {
throw std::runtime_error("Unable to connect to user systemd DBus!"); throw std::runtime_error("Unable to connect to user systemd DBus!");
} }
user_proxy->signal_signal().connect(sigc::mem_fun(*this, &SystemdFailedUnits::notify_cb)); user_props_proxy_->signal_signal().connect(
sigc::mem_fun(*this, &SystemdFailedUnits::notify_cb));
try {
user_manager_proxy_ = Gio::DBus::Proxy::create_for_bus_sync(
Gio::DBus::BusType::BUS_TYPE_SESSION, "org.freedesktop.systemd1",
"/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager");
} catch (const Glib::Error& e) {
spdlog::warn("Unable to connect to user systemd Manager interface: {}", e.what().c_str());
}
} }
updateData(); updateData();
@@ -52,16 +89,11 @@ SystemdFailedUnits::SystemdFailedUnits(const std::string& id, const Json::Value&
dp.emit(); dp.emit();
} }
SystemdFailedUnits::~SystemdFailedUnits() {
if (system_proxy) system_proxy.reset();
if (user_proxy) user_proxy.reset();
}
auto SystemdFailedUnits::notify_cb(const Glib::ustring& sender_name, auto SystemdFailedUnits::notify_cb(const Glib::ustring& sender_name,
const Glib::ustring& signal_name, const Glib::ustring& signal_name,
const Glib::VariantContainerBase& arguments) -> void { const Glib::VariantContainerBase& arguments) -> void {
if (signal_name == "PropertiesChanged" && !update_pending) { if (signal_name == "PropertiesChanged" && !update_pending_) {
update_pending = true; update_pending_ = true;
/* The fail count may fluctuate due to restarting. */ /* The fail count may fluctuate due to restarting. */
Glib::signal_timeout().connect_once(sigc::mem_fun(*this, &SystemdFailedUnits::updateData), Glib::signal_timeout().connect_once(sigc::mem_fun(*this, &SystemdFailedUnits::updateData),
UPDATE_DEBOUNCE_TIME_MS); UPDATE_DEBOUNCE_TIME_MS);
@@ -88,12 +120,12 @@ void SystemdFailedUnits::RequestSystemState() {
return "unknown"; return "unknown";
}; };
system_state = load("systemwide", system_proxy); system_state_ = load("systemwide", system_props_proxy_);
user_state = load("user", user_proxy); user_state_ = load("user", user_props_proxy_);
if (system_state == "running" && user_state == "running") if (system_state_ == "running" && user_state_ == "running")
overall_state = "ok"; overall_state_ = "ok";
else else
overall_state = "degraded"; overall_state_ = "degraded";
} }
void SystemdFailedUnits::RequestFailedUnits() { void SystemdFailedUnits::RequestFailedUnits() {
@@ -116,46 +148,153 @@ void SystemdFailedUnits::RequestFailedUnits() {
return 0; return 0;
}; };
nr_failed_system = load("systemwide", system_proxy); nr_failed_system_ = load("systemwide", system_props_proxy_);
nr_failed_user = load("user", user_proxy); nr_failed_user_ = load("user", user_props_proxy_);
nr_failed = nr_failed_system + nr_failed_user; nr_failed_ = nr_failed_system_ + nr_failed_user_;
}
void SystemdFailedUnits::RequestFailedUnitsList() {
failed_units_.clear();
if (!tooltipEnabled() || nr_failed_ == 0) {
return;
}
if (system_manager_proxy_) {
auto units = LoadFailedUnitsList("systemwide", system_manager_proxy_, "system");
failed_units_.insert(failed_units_.end(), units.begin(), units.end());
}
if (user_manager_proxy_) {
auto units = LoadFailedUnitsList("user", user_manager_proxy_, "user");
failed_units_.insert(failed_units_.end(), units.begin(), units.end());
}
}
auto SystemdFailedUnits::LoadFailedUnitsList(const char* kind,
Glib::RefPtr<Gio::DBus::Proxy>& proxy,
const std::string& scope) -> std::vector<FailedUnit> {
// org.freedesktop.systemd1.Manager.ListUnits returns
// (name, description, load_state, active_state, sub_state, followed, unit_path, job_id,
// job_type, job_path).
using UnitRow = std::tuple<Glib::ustring, Glib::ustring, Glib::ustring, Glib::ustring,
Glib::ustring, Glib::ustring, Glib::DBusObjectPathString, guint32,
Glib::ustring, Glib::DBusObjectPathString>;
using ListUnitsReply = Glib::Variant<std::tuple<std::vector<UnitRow>>>;
std::vector<FailedUnit> units;
if (!proxy) {
return units;
}
try {
auto data = proxy->call_sync("ListUnits");
if (!data) return units;
if (!data.is_of_type(ListUnitsReply::variant_type())) {
spdlog::warn("Unexpected DBus signature for ListUnits: {}", data.get_type_string());
return units;
}
auto [rows] = Glib::VariantBase::cast_dynamic<ListUnitsReply>(data).get();
for (const auto& row : rows) {
const auto& name = std::get<0>(row);
const auto& description = std::get<1>(row);
const auto& load_state = std::get<2>(row);
const auto& active_state = std::get<3>(row);
const auto& sub_state = std::get<4>(row);
if (active_state == "failed" || sub_state == "failed") {
units.push_back({name, description, load_state, active_state, sub_state, scope});
}
}
} catch (const Glib::Error& e) {
spdlog::error("Failed to list {} units: {}", kind, e.what().c_str());
}
return units;
}
std::string SystemdFailedUnits::BuildTooltipFailedList() const {
if (failed_units_.empty()) {
return "";
}
std::string list;
list.reserve(failed_units_.size() * 16);
bool first = true;
for (const auto& unit : failed_units_) {
try {
auto line = fmt::format(
fmt::runtime(tooltip_unit_format_),
fmt::arg("name", Glib::Markup::escape_text(unit.name).raw()),
fmt::arg("description", Glib::Markup::escape_text(unit.description).raw()),
fmt::arg("load_state", unit.load_state), fmt::arg("active_state", unit.active_state),
fmt::arg("sub_state", unit.sub_state), fmt::arg("scope", unit.scope));
if (!first) {
list += "\n";
}
first = false;
list += "- ";
list += line;
} catch (const std::exception& e) {
spdlog::warn("Failed to format tooltip for unit {}: {}", unit.name, e.what());
}
}
return list;
} }
void SystemdFailedUnits::updateData() { void SystemdFailedUnits::updateData() {
update_pending = false; update_pending_ = false;
RequestSystemState(); RequestSystemState();
if (overall_state == "degraded") RequestFailedUnits(); if (overall_state_ == "degraded") {
RequestFailedUnits();
RequestFailedUnitsList();
} else {
nr_failed_system_ = nr_failed_user_ = nr_failed_ = 0;
failed_units_.clear();
}
dp.emit(); dp.emit();
} }
auto SystemdFailedUnits::update() -> void { auto SystemdFailedUnits::update() -> void {
if (last_status == overall_state) return;
// Hide if needed. // Hide if needed.
if (overall_state == "ok" && hide_on_ok) { if (overall_state_ == "ok" && hide_on_ok_) {
event_box_.set_visible(false); event_box_.set_visible(false);
last_status_ = overall_state_;
return; return;
} }
event_box_.set_visible(true); event_box_.set_visible(true);
// Set state class. // Set state class.
if (!last_status.empty() && label_.get_style_context()->has_class(last_status)) { if (!last_status_.empty() && label_.get_style_context()->has_class(last_status_)) {
label_.get_style_context()->remove_class(last_status); label_.get_style_context()->remove_class(last_status_);
} }
if (!label_.get_style_context()->has_class(overall_state)) { if (!label_.get_style_context()->has_class(overall_state_)) {
label_.get_style_context()->add_class(overall_state); label_.get_style_context()->add_class(overall_state_);
} }
last_status = overall_state; last_status_ = overall_state_;
label_.set_markup(fmt::format( label_.set_markup(fmt::format(
fmt::runtime(nr_failed == 0 ? format_ok : format_), fmt::arg("nr_failed", nr_failed), fmt::runtime(nr_failed_ == 0 ? format_ok_ : format_), fmt::arg("nr_failed", nr_failed_),
fmt::arg("nr_failed_system", nr_failed_system), fmt::arg("nr_failed_user", nr_failed_user), fmt::arg("nr_failed_system", nr_failed_system_), fmt::arg("nr_failed_user", nr_failed_user_),
fmt::arg("system_state", system_state), fmt::arg("user_state", user_state), fmt::arg("system_state", system_state_), fmt::arg("user_state", user_state_),
fmt::arg("overall_state", overall_state))); fmt::arg("overall_state", overall_state_)));
if (tooltipEnabled()) {
std::string failed_list = BuildTooltipFailedList();
auto tooltip_template = overall_state_ == "ok" ? tooltip_format_ok_ : tooltip_format_;
if (!tooltip_template.empty()) {
label_.set_tooltip_markup(fmt::format(
fmt::runtime(tooltip_template), fmt::arg("nr_failed", nr_failed_),
fmt::arg("nr_failed_system", nr_failed_system_),
fmt::arg("nr_failed_user", nr_failed_user_), fmt::arg("system_state", system_state_),
fmt::arg("user_state", user_state_), fmt::arg("overall_state", overall_state_),
fmt::arg("failed_units_list", failed_list)));
} else {
label_.set_tooltip_text("");
}
}
ALabel::update(); ALabel::update();
} }

View File

@@ -95,7 +95,7 @@ UPower::~UPower() {
removeDevices(); removeDevices();
} }
static const std::string getDeviceStatus(UpDeviceState& state) { static std::string_view getDeviceStatus(UpDeviceState& state) {
switch (state) { switch (state) {
case UP_DEVICE_STATE_CHARGING: case UP_DEVICE_STATE_CHARGING:
case UP_DEVICE_STATE_PENDING_CHARGE: case UP_DEVICE_STATE_PENDING_CHARGE:
@@ -112,7 +112,7 @@ static const std::string getDeviceStatus(UpDeviceState& state) {
} }
} }
static const std::string getDeviceIcon(UpDeviceKind& kind) { static std::string_view getDeviceIcon(UpDeviceKind& kind) {
switch (kind) { switch (kind) {
case UP_DEVICE_KIND_LINE_POWER: case UP_DEVICE_KIND_LINE_POWER:
return "ac-adapter-symbolic"; return "ac-adapter-symbolic";
@@ -212,7 +212,8 @@ auto UPower::update() -> void {
// Remove last status if it exists // Remove last status if it exists
if (!lastStatus_.empty() && box_.get_style_context()->has_class(lastStatus_)) if (!lastStatus_.empty() && box_.get_style_context()->has_class(lastStatus_))
box_.get_style_context()->remove_class(lastStatus_); box_.get_style_context()->remove_class(lastStatus_);
if (!box_.get_style_context()->has_class(status)) box_.get_style_context()->add_class(status); if (!box_.get_style_context()->has_class(std::string(status)))
box_.get_style_context()->add_class(std::string(status));
lastStatus_ = status; lastStatus_ = status;
if (devices_.size() == 0 && !upDeviceValid && hideIfEmpty_) { if (devices_.size() == 0 && !upDeviceValid && hideIfEmpty_) {

View File

@@ -27,14 +27,14 @@ inline auto byteswap(uint32_t x) -> uint32_t {
auto pack_and_write(Sock& sock, std::string&& buf) -> void { auto pack_and_write(Sock& sock, std::string&& buf) -> void {
uint32_t len = buf.size(); uint32_t len = buf.size();
if constexpr (std::endian::native != std::endian::little) len = byteswap(len); if constexpr (std::endian::native != std::endian::little) len = byteswap(len);
(void)write(sock.fd, &len, 4); (void)write(sock, &len, 4);
(void)write(sock.fd, buf.data(), buf.size()); (void)write(sock, buf.data(), buf.size());
} }
auto read_exact(Sock& sock, size_t n) -> std::string { auto read_exact(Sock& sock, size_t n) -> std::string {
auto buf = std::string(n, 0); auto buf = std::string(n, 0);
for (size_t i = 0; i < n;) { for (size_t i = 0; i < n;) {
auto r = read(sock.fd, &buf[i], n - i); auto r = read(sock, &buf[i], n - i);
if (r <= 0) { if (r <= 0) {
throw std::runtime_error("Wayfire IPC: read failed"); throw std::runtime_error("Wayfire IPC: read failed");
} }
@@ -111,7 +111,7 @@ auto IPC::connect() -> Sock {
throw std::runtime_error{"Wayfire IPC: ipc not available"}; throw std::runtime_error{"Wayfire IPC: ipc not available"};
} }
auto sock = socket(AF_UNIX, SOCK_STREAM, 0); util::ScopedFd sock(socket(AF_UNIX, SOCK_STREAM, 0));
if (sock == -1) { if (sock == -1) {
throw std::runtime_error{"Wayfire IPC: socket() failed"}; throw std::runtime_error{"Wayfire IPC: socket() failed"};
} }
@@ -121,11 +121,10 @@ auto IPC::connect() -> Sock {
addr.sun_path[sizeof(addr.sun_path) - 1] = 0; addr.sun_path[sizeof(addr.sun_path) - 1] = 0;
if (::connect(sock, (const sockaddr*)&addr, sizeof(addr)) == -1) { if (::connect(sock, (const sockaddr*)&addr, sizeof(addr)) == -1) {
close(sock);
throw std::runtime_error{"Wayfire IPC: connect() failed"}; throw std::runtime_error{"Wayfire IPC: connect() failed"};
} }
return {sock}; return sock;
} }
auto IPC::receive(Sock& sock) -> Json::Value { auto IPC::receive(Sock& sock) -> Json::Value {

View File

@@ -54,6 +54,15 @@ 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); waybar::modules::Wireplumber::modules.remove(this);
if (mixer_api_ != nullptr) {
g_signal_handlers_disconnect_by_data(mixer_api_, this);
}
if (def_nodes_api_ != nullptr) {
g_signal_handlers_disconnect_by_data(def_nodes_api_, this);
}
if (om_ != nullptr) {
g_signal_handlers_disconnect_by_data(om_, 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_);
@@ -528,6 +537,7 @@ bool waybar::modules::Wireplumber::handleScroll(GdkEventScroll* e) {
GVariant* variant = g_variant_new_double(newVol); GVariant* variant = g_variant_new_double(newVol);
gboolean ret; gboolean ret;
g_signal_emit_by_name(mixer_api_, "set-volume", node_id_, variant, &ret); g_signal_emit_by_name(mixer_api_, "set-volume", node_id_, variant, &ret);
g_variant_unref(variant);
} }
return true; return true;
} }

View File

@@ -198,7 +198,7 @@ void Task::handle_title(const char* title) {
title_ = title; title_ = title;
hide_if_ignored(); hide_if_ignored();
if (!with_icon_ && !with_name_ || app_info_) { if ((!with_icon_ && !with_name_) || app_info_) {
return; return;
} }

View File

@@ -40,12 +40,16 @@ AudioBackend::AudioBackend(std::function<void()> on_updated_cb, private_construc
} }
AudioBackend::~AudioBackend() { AudioBackend::~AudioBackend() {
if (mainloop_ != nullptr) {
// Lock the mainloop so we can safely disconnect the context.
// This must be done before stopping the thread.
pa_threaded_mainloop_lock(mainloop_);
if (context_ != nullptr) { if (context_ != nullptr) {
pa_context_disconnect(context_); pa_context_disconnect(context_);
pa_context_unref(context_);
context_ = nullptr;
} }
pa_threaded_mainloop_unlock(mainloop_);
if (mainloop_ != nullptr) {
mainloop_api_->quit(mainloop_api_, 0);
pa_threaded_mainloop_stop(mainloop_); pa_threaded_mainloop_stop(mainloop_);
pa_threaded_mainloop_free(mainloop_); pa_threaded_mainloop_free(mainloop_);
} }
@@ -73,7 +77,14 @@ void AudioBackend::contextStateCb(pa_context* c, void* data) {
auto* backend = static_cast<AudioBackend*>(data); auto* backend = static_cast<AudioBackend*>(data);
switch (pa_context_get_state(c)) { switch (pa_context_get_state(c)) {
case PA_CONTEXT_TERMINATED: case PA_CONTEXT_TERMINATED:
// Only quit the mainloop if this is still the active context.
// During reconnection, the old context fires TERMINATED after the new one
// has already been created; quitting in that case would kill the new context.
// Note: context_ is only written from PA callbacks (while the mainloop lock is
// held), so this comparison is safe within any PA callback.
if (backend->context_ == nullptr || backend->context_ == c) {
backend->mainloop_api_->quit(backend->mainloop_api_, 0); backend->mainloop_api_->quit(backend->mainloop_api_, 0);
}
break; break;
case PA_CONTEXT_READY: case PA_CONTEXT_READY:
pa_context_get_server_info(c, serverInfoCb, data); pa_context_get_server_info(c, serverInfoCb, data);
@@ -93,6 +104,8 @@ void AudioBackend::contextStateCb(pa_context* c, void* data) {
// So there is no need to lock it again. // So there is no need to lock it again.
if (backend->context_ != nullptr) { if (backend->context_ != nullptr) {
pa_context_disconnect(backend->context_); pa_context_disconnect(backend->context_);
pa_context_unref(backend->context_);
backend->context_ = nullptr;
} }
backend->connectContext(); backend->connectContext();
break; break;

View File

@@ -21,7 +21,6 @@ Glib::RefPtr<Gdk::Pixbuf> DefaultGtkIconThemeWrapper::load_icon(
const std::lock_guard<std::mutex> lock(default_theme_mutex); const std::lock_guard<std::mutex> lock(default_theme_mutex);
auto default_theme = Gtk::IconTheme::get_default(); auto default_theme = Gtk::IconTheme::get_default();
default_theme->rescan_if_needed();
auto icon_info = default_theme->lookup_icon(name, tmp_size, flags); auto icon_info = default_theme->lookup_icon(name, tmp_size, flags);

View File

@@ -0,0 +1,60 @@
#include <filesystem>
#include <fstream>
#include <iomanip>
#include <regex>
#include <sstream>
namespace fs = std::filesystem;
struct TransformResult {
std::string css;
bool was_transformed;
};
TransformResult transform_8bit_to_hex(const std::string& file_path) {
std::ifstream f(file_path, std::ios::in | std::ios::binary);
const auto size = fs::file_size(file_path);
std::string result(size, '\0');
if (!f.is_open() || !f.good()) {
throw std::runtime_error("Cannot open file: " + file_path);
}
if (size == 0) {
return {.css = result, .was_transformed = false};
}
f.read(result.data(), size);
static std::regex pattern(
R"(\#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2}))");
std::string final_output;
auto it = std::sregex_iterator(result.begin(), result.end(), pattern);
auto eof = std::sregex_iterator();
if (it == eof) {
return {.css = result, .was_transformed = false};
}
std::smatch match;
while (it != eof) {
match = *it;
final_output += match.prefix().str();
int r = stoi(match[1].str(), nullptr, 16);
int g = stoi(match[2].str(), nullptr, 16);
int b = stoi(match[3].str(), nullptr, 16);
double a = (stoi(match[4].str(), nullptr, 16) / 255.0);
std::stringstream ss;
ss << "rgba(" << r << "," << g << "," << b << "," << std::fixed << std::setprecision(2) << a
<< ")";
final_output += ss.str();
++it;
}
final_output += match.suffix().str();
return {.css = final_output, .was_transformed = true};
}

View File

@@ -4,9 +4,9 @@
#depth = 1 #depth = 1
[wrap-file] [wrap-file]
directory = cava-0.10.7-beta directory = cava-0.10.7
source_url = https://github.com/LukashonakV/cava/archive/v0.10.7-beta.tar.gz source_url = https://github.com/LukashonakV/cava/archive/0.10.7.tar.gz
source_filename = cava-0.10.7.tar.gz source_filename = cava-0.10.7.tar.gz
source_hash = 8915d7214f2046554c158fe6f2ae518881dfb573e421ea848727be11a5dfa8c4 source_hash = 50cc6413e9c96c503657f814744a2baf429a24ff9fed31a8343e0ed285269eff
[provide] [provide]
libcava = cava_dep libcava = cava_dep

View File

@@ -4,56 +4,132 @@
#include <catch2/catch.hpp> #include <catch2/catch.hpp>
#endif #endif
#include "fixtures/IPCTestFixture.hpp" #include <system_error>
#include "modules/hyprland/backend.hpp"
namespace fs = std::filesystem; namespace fs = std::filesystem;
namespace hyprland = waybar::modules::hyprland; namespace hyprland = waybar::modules::hyprland;
TEST_CASE_METHOD(IPCTestFixture, "XDGRuntimeDirExists", "[getSocketFolder]") { namespace {
class IPCTestHelper : public hyprland::IPC {
public:
static void resetSocketFolder() { socketFolder_.clear(); }
};
std::size_t countOpenFds() {
#if defined(__linux__)
std::size_t count = 0;
for (const auto& _ : fs::directory_iterator("/proc/self/fd")) {
(void)_;
++count;
}
return count;
#else
return 0;
#endif
}
} // namespace
TEST_CASE("XDGRuntimeDirExists", "[getSocketFolder]") {
// Test case: XDG_RUNTIME_DIR exists and contains "hypr" directory // Test case: XDG_RUNTIME_DIR exists and contains "hypr" directory
// Arrange // Arrange
tempDir = fs::temp_directory_path() / "hypr_test/run/user/1000"; constexpr auto instanceSig = "instance_sig";
const fs::path tempDir = fs::temp_directory_path() / "hypr_test/run/user/1000";
std::error_code ec;
fs::remove_all(tempDir, ec);
fs::path expectedPath = tempDir / "hypr" / instanceSig; fs::path expectedPath = tempDir / "hypr" / instanceSig;
fs::create_directories(tempDir / "hypr" / instanceSig); fs::create_directories(expectedPath);
setenv("XDG_RUNTIME_DIR", tempDir.c_str(), 1); setenv("XDG_RUNTIME_DIR", tempDir.c_str(), 1);
IPCTestHelper::resetSocketFolder();
// Act // Act
fs::path actualPath = getSocketFolder(instanceSig); fs::path actualPath = hyprland::IPC::getSocketFolder(instanceSig);
// Assert expected result // Assert expected result
REQUIRE(actualPath == expectedPath); REQUIRE(actualPath == expectedPath);
fs::remove_all(tempDir, ec);
} }
TEST_CASE_METHOD(IPCTestFixture, "XDGRuntimeDirDoesNotExist", "[getSocketFolder]") { TEST_CASE("XDGRuntimeDirDoesNotExist", "[getSocketFolder]") {
// Test case: XDG_RUNTIME_DIR does not exist // Test case: XDG_RUNTIME_DIR does not exist
// Arrange // Arrange
constexpr auto instanceSig = "instance_sig";
unsetenv("XDG_RUNTIME_DIR"); unsetenv("XDG_RUNTIME_DIR");
fs::path expectedPath = fs::path("/tmp") / "hypr" / instanceSig; fs::path expectedPath = fs::path("/tmp") / "hypr" / instanceSig;
IPCTestHelper::resetSocketFolder();
// Act // Act
fs::path actualPath = getSocketFolder(instanceSig); fs::path actualPath = hyprland::IPC::getSocketFolder(instanceSig);
// Assert expected result // Assert expected result
REQUIRE(actualPath == expectedPath); REQUIRE(actualPath == expectedPath);
} }
TEST_CASE_METHOD(IPCTestFixture, "XDGRuntimeDirExistsNoHyprDir", "[getSocketFolder]") { TEST_CASE("XDGRuntimeDirExistsNoHyprDir", "[getSocketFolder]") {
// Test case: XDG_RUNTIME_DIR exists but does not contain "hypr" directory // Test case: XDG_RUNTIME_DIR exists but does not contain "hypr" directory
// Arrange // Arrange
constexpr auto instanceSig = "instance_sig";
fs::path tempDir = fs::temp_directory_path() / "hypr_test/run/user/1000"; fs::path tempDir = fs::temp_directory_path() / "hypr_test/run/user/1000";
std::error_code ec;
fs::remove_all(tempDir, ec);
fs::create_directories(tempDir); fs::create_directories(tempDir);
setenv("XDG_RUNTIME_DIR", tempDir.c_str(), 1); setenv("XDG_RUNTIME_DIR", tempDir.c_str(), 1);
fs::path expectedPath = fs::path("/tmp") / "hypr" / instanceSig; fs::path expectedPath = fs::path("/tmp") / "hypr" / instanceSig;
IPCTestHelper::resetSocketFolder();
// Act // Act
fs::path actualPath = getSocketFolder(instanceSig); fs::path actualPath = hyprland::IPC::getSocketFolder(instanceSig);
// Assert expected result // Assert expected result
REQUIRE(actualPath == expectedPath); REQUIRE(actualPath == expectedPath);
fs::remove_all(tempDir, ec);
} }
TEST_CASE_METHOD(IPCTestFixture, "getSocket1Reply throws on no socket", "[getSocket1Reply]") { TEST_CASE("Socket folder is resolved per instance signature", "[getSocketFolder]") {
const fs::path tempDir = fs::temp_directory_path() / "hypr_test/run/user/1000";
std::error_code ec;
fs::remove_all(tempDir, ec);
fs::create_directories(tempDir / "hypr");
setenv("XDG_RUNTIME_DIR", tempDir.c_str(), 1);
IPCTestHelper::resetSocketFolder();
const auto firstPath = hyprland::IPC::getSocketFolder("instance_a");
const auto secondPath = hyprland::IPC::getSocketFolder("instance_b");
REQUIRE(firstPath == tempDir / "hypr" / "instance_a");
REQUIRE(secondPath == tempDir / "hypr" / "instance_b");
REQUIRE(firstPath != secondPath);
fs::remove_all(tempDir, ec);
}
TEST_CASE("getSocket1Reply throws on no socket", "[getSocket1Reply]") {
unsetenv("HYPRLAND_INSTANCE_SIGNATURE");
IPCTestHelper::resetSocketFolder();
std::string request = "test_request"; std::string request = "test_request";
CHECK_THROWS(getSocket1Reply(request)); CHECK_THROWS(hyprland::IPC::getSocket1Reply(request));
} }
#if defined(__linux__)
TEST_CASE("getSocket1Reply failure paths do not leak fds", "[getSocket1Reply][fd-leak]") {
const auto baseline = countOpenFds();
unsetenv("HYPRLAND_INSTANCE_SIGNATURE");
for (int i = 0; i < 16; ++i) {
IPCTestHelper::resetSocketFolder();
CHECK_THROWS(hyprland::IPC::getSocket1Reply("test_request"));
}
const auto after_missing_signature = countOpenFds();
REQUIRE(after_missing_signature == baseline);
setenv("HYPRLAND_INSTANCE_SIGNATURE", "definitely-not-running", 1);
for (int i = 0; i < 16; ++i) {
IPCTestHelper::resetSocketFolder();
CHECK_THROWS(hyprland::IPC::getSocket1Reply("test_request"));
}
const auto after_connect_failures = countOpenFds();
REQUIRE(after_connect_failures == baseline);
}
#endif

View File

@@ -1,25 +0,0 @@
#include "modules/hyprland/backend.hpp"
namespace fs = std::filesystem;
namespace hyprland = waybar::modules::hyprland;
class IPCTestFixture : public hyprland::IPC {
public:
IPCTestFixture() : IPC() { IPC::socketFolder_ = ""; }
~IPCTestFixture() { fs::remove_all(tempDir); }
protected:
const char* instanceSig = "instance_sig";
fs::path tempDir = fs::temp_directory_path() / "hypr_test";
private:
};
class IPCMock : public IPCTestFixture {
public:
// Mock getSocket1Reply to return an empty string
static std::string getSocket1Reply(const std::string& rq) { return ""; }
protected:
const char* instanceSig = "instance_sig";
};

View File

@@ -9,6 +9,7 @@
#endif #endif
#include <thread> #include <thread>
#include <type_traits> #include <type_traits>
#include <vector>
#include "fixtures/GlibTestsFixture.hpp" #include "fixtures/GlibTestsFixture.hpp"
@@ -141,3 +142,33 @@ TEST_CASE_METHOD(GlibTestsFixture, "SafeSignal copy/move counter", "[signal][thr
producer.join(); producer.join();
REQUIRE(count == NUM_EVENTS); REQUIRE(count == NUM_EVENTS);
} }
TEST_CASE_METHOD(GlibTestsFixture, "SafeSignal queue stays bounded under burst load",
"[signal][thread][util][perf]") {
constexpr int NUM_EVENTS = 200;
constexpr std::size_t MAX_QUEUED_EVENTS = 8;
std::vector<int> received;
SafeSignal<int> test_signal;
test_signal.set_max_queued_events(MAX_QUEUED_EVENTS);
setTimeout(500);
test_signal.connect([&](auto value) { received.push_back(value); });
run([&]() {
std::thread producer([&]() {
for (int i = 1; i <= NUM_EVENTS; ++i) {
test_signal.emit(i);
}
});
producer.join();
Glib::signal_timeout().connect_once([this]() { this->quit(); }, 50);
});
REQUIRE(received.size() <= MAX_QUEUED_EVENTS);
REQUIRE_FALSE(received.empty());
REQUIRE(received.back() == NUM_EVENTS);
REQUIRE(received.front() == NUM_EVENTS - static_cast<int>(received.size()) + 1);
}

57
test/utils/command.cpp Normal file
View File

@@ -0,0 +1,57 @@
#if __has_include(<catch2/catch_test_macros.hpp>)
#include <catch2/catch_test_macros.hpp>
#else
#include <catch2/catch.hpp>
#endif
#include <sys/wait.h>
#include <unistd.h>
#include <cerrno>
#include <list>
#include <mutex>
std::mutex reap_mtx;
std::list<pid_t> reap;
extern "C" int waybar_test_execl(const char* path, const char* arg, ...);
extern "C" int waybar_test_execlp(const char* file, const char* arg, ...);
#define execl waybar_test_execl
#define execlp waybar_test_execlp
#include "util/command.hpp"
#undef execl
#undef execlp
extern "C" int waybar_test_execl(const char* path, const char* arg, ...) {
(void)path;
(void)arg;
errno = ENOENT;
return -1;
}
extern "C" int waybar_test_execlp(const char* file, const char* arg, ...) {
(void)file;
(void)arg;
errno = ENOENT;
return -1;
}
TEST_CASE("command::execNoRead returns 127 when shell exec fails", "[util][command]") {
const auto result = waybar::util::command::execNoRead("echo should-not-run");
REQUIRE(result.exit_code == waybar::util::command::kExecFailureExitCode);
REQUIRE(result.out.empty());
}
TEST_CASE("command::forkExec child exits 127 when shell exec fails", "[util][command]") {
const auto pid = waybar::util::command::forkExec("echo should-not-run", "test-output");
REQUIRE(pid > 0);
int status = -1;
REQUIRE(waitpid(pid, &status, 0) == pid);
REQUIRE(WIFEXITED(status));
REQUIRE(WEXITSTATUS(status) == waybar::util::command::kExecFailureExitCode);
std::scoped_lock<std::mutex> lock(reap_mtx);
reap.remove(pid);
}

View File

@@ -13,6 +13,8 @@ test_src = files(
'../../src/config.cpp', '../../src/config.cpp',
'JsonParser.cpp', 'JsonParser.cpp',
'SafeSignal.cpp', 'SafeSignal.cpp',
'sleeper_thread.cpp',
'command.cpp',
'css_reload_helper.cpp', 'css_reload_helper.cpp',
'../../src/util/css_reload_helper.cpp', '../../src/util/css_reload_helper.cpp',
) )

View File

@@ -0,0 +1,80 @@
#if __has_include(<catch2/catch_test_macros.hpp>)
#include <catch2/catch_test_macros.hpp>
#else
#include <catch2/catch.hpp>
#endif
#include <sys/wait.h>
#include <unistd.h>
#include <chrono>
#include <thread>
#include "util/sleeper_thread.hpp"
namespace waybar::util {
SafeSignal<bool>& prepare_for_sleep() {
static SafeSignal<bool> signal;
return signal;
}
} // namespace waybar::util
namespace {
int run_in_subprocess(int (*task)()) {
const auto pid = fork();
if (pid < 0) {
return -1;
}
if (pid == 0) {
alarm(5);
_exit(task());
}
int status = -1;
if (waitpid(pid, &status, 0) != pid) {
return -1;
}
if (!WIFEXITED(status)) {
return -1;
}
return WEXITSTATUS(status);
}
int run_reassignment_regression() {
waybar::util::SleeperThread thread;
thread = [] { std::this_thread::sleep_for(std::chrono::milliseconds(10)); };
thread = [] { std::this_thread::sleep_for(std::chrono::milliseconds(1)); };
return 0;
}
int run_control_flag_stress() {
for (int i = 0; i < 200; ++i) {
waybar::util::SleeperThread thread;
thread = [&thread] { thread.sleep_for(std::chrono::milliseconds(1)); };
std::thread waker([&thread] {
for (int j = 0; j < 100; ++j) {
thread.wake_up();
std::this_thread::yield();
}
});
std::this_thread::sleep_for(std::chrono::milliseconds(2));
thread.stop();
waker.join();
if (thread.isRunning()) {
return 1;
}
}
return 0;
}
} // namespace
TEST_CASE("SleeperThread reassignment does not terminate process", "[util][sleeper_thread]") {
REQUIRE(run_in_subprocess(run_reassignment_regression) == 0);
}
TEST_CASE("SleeperThread control flags are stable under concurrent wake and stop",
"[util][sleeper_thread]") {
REQUIRE(run_in_subprocess(run_control_flag_stress) == 0);
}