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
_codeql_detected_source_root
heaptrack*

View File

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

View File

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

View File

@@ -26,7 +26,7 @@ class Gamemode : public AModule {
const std::string DEFAULT_FORMAT = "{glyph}";
const std::string DEFAULT_FORMAT_ALT = "{glyph} {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,
const Glib::ustring& name_owner);

View File

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

View File

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

View File

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

View File

@@ -4,6 +4,7 @@
#include <gtkmm/enums.h>
#include <gtkmm/label.h>
#include <json/value.h>
#include <sigc++/connection.h>
#include <cstdint>
#include <map>
@@ -59,7 +60,7 @@ class Workspaces : public AModule, public EventHandler {
enum class ActiveWindowPosition { NONE, FIRST, LAST };
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; }
bool isWorkspaceIgnored(std::string const& workspace_name);
@@ -208,6 +209,7 @@ class Workspaces : public AModule, public EventHandler {
std::mutex m_mutex;
const Bar& m_bar;
Gtk::Box m_box;
sigc::connection m_scrollEventConnection_;
IPC& m_ipc;
};

View File

@@ -44,7 +44,7 @@ class MPD : public ALabel {
std::string getFilename() const;
void setLabel();
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
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_up_total_{0};
std::chrono::steady_clock::time_point bandwidth_last_sample_time_;
std::string state_;
std::string essid_;

View File

@@ -16,7 +16,7 @@ class Host {
public:
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()>&);
~Host();
private:
@@ -28,9 +28,13 @@ class Host {
static void registerHost(GObject*, GAsyncResult*, gpointer);
static void itemRegistered(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);
void addRegisteredItem(std::string service);
void addRegisteredItem(const std::string& service);
std::vector<std::unique_ptr<Item>> items_;
const std::string bus_name_;
@@ -43,6 +47,7 @@ class Host {
const Bar& bar_;
const std::function<void(std::unique_ptr<Item>&)> on_add_;
const std::function<void(std::unique_ptr<Item>&)> on_remove_;
const std::function<void()> on_update_;
};
} // namespace waybar::modules::SNI

View File

@@ -11,6 +11,7 @@
#include <libdbusmenu-gtk/dbusmenu-gtk.h>
#include <sigc++/trackable.h>
#include <functional>
#include <set>
#include <string_view>
@@ -25,9 +26,13 @@ struct ToolTip {
class Item : public sigc::trackable {
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();
bool isReady() const;
std::string bus_name;
std::string object_path;
@@ -43,7 +48,9 @@ class Item : public sigc::trackable {
Glib::RefPtr<Gdk::Pixbuf> icon_pixmap;
Glib::RefPtr<Gtk::IconTheme> icon_theme;
std::string overlay_icon_name;
Glib::RefPtr<Gdk::Pixbuf> overlay_icon_pixmap;
std::string attention_icon_name;
Glib::RefPtr<Gdk::Pixbuf> attention_icon_pixmap;
std::string attention_movie_name;
std::string icon_theme_path;
std::string menu;
@@ -62,6 +69,8 @@ class Item : public sigc::trackable {
void proxyReady(Glib::RefPtr<Gio::AsyncResult>& result);
void setProperty(const Glib::ustring& name, Glib::VariantBase& value);
void setStatus(const Glib::ustring& value);
void setReady();
void invalidate();
void setCustomIcon(const std::string& id);
void getUpdatedProperties();
void processUpdatedProperties(Glib::RefPtr<Gio::AsyncResult>& result);
@@ -69,8 +78,13 @@ class Item : public sigc::trackable {
const Glib::VariantContainerBase& arguments);
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> 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);
double getScaledIconSize();
static void onMenuDestroyed(Item* self, GObject* old_menu_pointer);
@@ -86,8 +100,13 @@ class Item : public sigc::trackable {
gdouble distance_scrolled_y_ = 0;
// visibility of items with Status == Passive
bool show_passive_ = false;
bool ready_ = false;
Glib::ustring status_ = "active";
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::Cancellable> cancellable_;

View File

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

View File

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

View File

@@ -13,6 +13,7 @@
#include "ipc.hpp"
#include "util/SafeSignal.hpp"
#include "util/scoped_fd.hpp"
#include "util/sleeper_thread.hpp"
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 recv(int fd);
int fd_;
int fd_event_;
util::ScopedFd fd_;
util::ScopedFd fd_event_;
std::mutex mutex_;
util::SleeperThread thread_;
};

View File

@@ -47,7 +47,7 @@ class Language : public ALabel, public sigc::trackable {
void onEvent(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;
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_ =
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);
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 getCycleWorkspace(std::vector<Json::Value>::iterator, bool prev) 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;
const Bar& bar_;

View File

@@ -3,6 +3,7 @@
#include <giomm/dbusproxy.h>
#include <string>
#include <vector>
#include "ALabel.hpp"
@@ -11,23 +12,42 @@ namespace waybar::modules {
class SystemdFailedUnits : public ALabel {
public:
SystemdFailedUnits(const std::string&, const Json::Value&);
virtual ~SystemdFailedUnits();
virtual ~SystemdFailedUnits() = default;
auto update() -> void override;
private:
bool hide_on_ok;
std::string format_ok;
struct FailedUnit {
std::string name;
std::string description;
std::string load_state;
std::string active_state;
std::string sub_state;
std::string scope;
};
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_proxy, user_proxy;
bool hide_on_ok_;
std::string format_ok_;
std::string tooltip_format_;
std::string tooltip_format_ok_;
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,
const Glib::VariantContainerBase& arguments);
void RequestFailedUnits();
void RequestFailedUnitsList();
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();
};

View File

@@ -12,6 +12,8 @@
#include <unordered_map>
#include <utility>
#include "util/scoped_fd.hpp"
namespace waybar::modules::wayfire {
using EventHandler = std::function<void(const std::string& event)>;
@@ -71,23 +73,7 @@ struct State {
auto update_view(const Json::Value& view) -> void;
};
struct Sock {
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;
}
};
using Sock = util::ScopedFd;
class IPC : public std::enable_shared_from_this<IPC> {
static std::weak_ptr<IPC> instance;

View File

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

View File

@@ -20,6 +20,8 @@ extern std::list<pid_t> reap;
namespace waybar::util::command {
constexpr int kExecFailureExitCode = 127;
struct res {
int exit_code;
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);
}
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 {
::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);
}
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 {
reap_mtx.lock();
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
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <ctime>
@@ -31,8 +32,8 @@ class SleeperThread {
SleeperThread(std::function<void()> func)
: thread_{[this, func] {
while (do_run_) {
signal_ = false;
while (do_run_.load(std::memory_order_relaxed)) {
signal_.store(false, std::memory_order_relaxed);
func();
}
}} {
@@ -42,9 +43,18 @@ class SleeperThread {
}
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] {
while (do_run_) {
signal_ = false;
while (do_run_.load(std::memory_order_relaxed)) {
signal_.store(false, std::memory_order_relaxed);
func();
}
});
@@ -56,12 +66,14 @@ class SleeperThread {
return *this;
}
bool isRunning() const { return do_run_; }
bool isRunning() const { return do_run_.load(std::memory_order_relaxed); }
auto sleep() {
std::unique_lock lk(mutex_);
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) {
@@ -73,7 +85,9 @@ class SleeperThread {
if (now < max_time_point - 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(
@@ -81,22 +95,24 @@ class SleeperThread {
time_point) {
std::unique_lock lk(mutex_);
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() {
{
std::lock_guard<std::mutex> lck(mutex_);
signal_ = true;
signal_.store(true, std::memory_order_relaxed);
}
condvar_.notify_all();
}
auto stop() {
void stop() {
{
std::lock_guard<std::mutex> lck(mutex_);
signal_ = true;
do_run_ = false;
signal_.store(true, std::memory_order_relaxed);
do_run_.store(false, std::memory_order_relaxed);
}
condvar_.notify_all();
auto handle = thread_.native_handle();
@@ -118,8 +134,8 @@ class SleeperThread {
std::thread thread_;
std::condition_variable condvar_;
std::mutex mutex_;
bool do_run_ = true;
bool signal_ = false;
std::atomic<bool> do_run_ = true;
std::atomic<bool> signal_ = false;
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": {
"format": "{:%H:%M}  ",
"format-alt": "{:%A, %B %d, %Y (%R)} ",
"format-alt": "{:%A, %B %d, %Y (%R)} 󰃰 ",
"tooltip-format": "<tt><small>{calendar}</small></tt>",
"calendar": {
"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}"
}
```
5. Simple calendar tooltip
```
"clock": {
"format": "{:%H:%M}",
"tooltip-format": "<tt>{calendar}</tt>",
"calendar": {
"format": {
"today": "<span color='#ffcc66'><b>{}</b></span>"
}
}
}
```
# STYLE
- *#clock*
@@ -287,7 +299,7 @@ Example of working config
```
"clock": {
"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>",
"calendar": {
"mode" : "year",

View File

@@ -43,7 +43,7 @@ Feral Gamemode optimizations.
*glyph*: ++
typeof: string ++
default: ++
default: 󰊴 ++
The string icon to display. Only visible if *use-icon* is set to false.
*icon-name*: ++
@@ -82,7 +82,7 @@ Feral Gamemode optimizations.
"gamemode": {
"format": "{glyph}",
"format-alt": "{glyph} {count}",
"glyph": "",
"glyph": "󰊴",
"hide-not-running": true,
"use-icon": true,
"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-muted": "",
"headphone": "",
"hands-free": "",
"headset": "",
"hands-free": "󰂑",
"headset": "󰂑",
"phone": "",
"phone-muted": "",
"portable": "",

View File

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

View File

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

View File

@@ -19,7 +19,7 @@ Addressed by *systemd-failed-units*
*format-ok*: ++
typeof: string ++
This format is used when there is no failing units.
This format is used when there are no failing units.
*user*: ++
typeof: bool ++
@@ -34,15 +34,30 @@ Addressed by *systemd-failed-units*
*hide-on-ok*: ++
typeof: bool ++
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*: ++
typeof: string ++
Action that popups the menu.
Action that pops up the menu.
*menu-file*: ++
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*
*menu-actions*: ++
@@ -52,7 +67,7 @@ Addressed by *systemd-failed-units*
*expand*: ++
typeof: bool ++
default: false ++
Enables this module to consume all left over space dynamically.
Enables this module to consume all leftover space dynamically.
# FORMAT REPLACEMENTS
@@ -62,11 +77,23 @@ Addressed by *systemd-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
@@ -77,6 +104,8 @@ Addressed by *systemd-failed-units*
"format-ok": "✓",
"system": true,
"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": {
"format": "{volume}% {icon}",
"format-muted": "",
"format-muted": "󰅶",
"format-icons": ["", "", ""],
"on-click": "helvum",
"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 ++
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*: ++
typeof: bool ++
default: true ++

View File

@@ -185,7 +185,8 @@ src_files = files(
'src/util/gtk_icon.cpp',
'src/util/icon_loader.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(

View File

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

View File

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

View File

@@ -26,7 +26,7 @@ ALabel::ALabel(const Json::Value& config, const std::string& name, const std::st
: std::chrono::milliseconds(
(config_["interval"].isNumeric()
? 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))),
default_format_(format_) {
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);
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*>();
menuActionsMap_ = std::map<std::string, std::string>();

View File

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

View File

@@ -11,6 +11,7 @@
#include "idle-inhibit-unstable-v1-client-protocol.h"
#include "util/clara.hpp"
#include "util/format.hpp"
#include "util/hex_checker.hpp"
waybar::Client* waybar::Client::inst() {
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,
const char* interface, uint32_t version) {
auto* client = static_cast<Client*>(data);
if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0 &&
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(
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) {
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*>(
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();
if (!css_provider_->load_from_path(css_file)) {
css_provider_.reset();
throw std::runtime_error("Can't open style file");
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)) {
css_provider_.reset();
throw std::runtime_error("Can't open style file");
}
}
Gtk::StyleContext::add_provider_for_screen(screen, css_provider_,
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".)");
}
const bool start_expanded =
(drawer_config["start-expanded"].isBool() ? drawer_config["start-expanded"].asBool()
: false);
auto transition_type = getPreferredTransitionType(vertical);
revealer.set_transition_type(transition_type);
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");

View File

@@ -71,6 +71,7 @@ CFFI::CFFI(const std::string& name, const std::string& id, const Json::Value& co
// Prepare config_entries array
std::vector<ffi::wbcffi_config_entry> config_entries;
config_entries.reserve(keys.size());
for (size_t i = 0; i < keys.size(); i++) {
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)) {
std::vector<std::string> frequency_files = {"/cpuinfo_min_freq", "/cpuinfo_max_freq"};
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;
if (std::filesystem::exists(freq_file_path)) {
std::string freq_value;

View File

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

View File

@@ -92,7 +92,7 @@ auto waybar::modules::Disk::update() -> void {
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") {
return 1000.0;
} 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,
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*>(
(zdwl_ipc_manager_v2*)wl_registry_bind(registry, name, &zdwl_ipc_manager_v2_interface, 1));
}
if (std::strcmp(interface, wl_seat_interface.name) == 0) {
version = std::min<uint32_t>(version, 1);
static_cast<Tags*>(data)->seat_ =
static_cast<struct wl_seat*>(wl_registry_bind(registry, name, &wl_seat_interface, version));
}
if (std::strcmp(interface, zdwl_ipc_manager_v2_interface.name) == 0) {
auto* self = static_cast<Tags*>(data);
if (self->status_manager_) {
zdwl_ipc_manager_v2_destroy(self->status_manager_);
self->status_manager_ = nullptr;
}
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) {
/* Ignore event */
}

View File

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

View File

@@ -63,19 +63,35 @@ auto Language::update() -> void {
void Language::onEvent(const std::string& ev) {
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:
// activelayout>>micro-star-int'l-co.,-ltd.-msi-gk50-elite-gaming-keyboard,English (US, intl.,
// with dead keys)
std::string beforeParenthesis;
auto parenthesisPos = ev.find_last_of('(');
auto parenthesisPos = payload.find_last_of('(');
if (parenthesisPos == std::string::npos) {
beforeParenthesis = ev;
beforeParenthesis = payload;
} 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())
return; // ignore

View File

@@ -75,7 +75,12 @@ void Submap::onEvent(const std::string& ev) {
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;

View File

@@ -19,21 +19,19 @@ std::shared_mutex windowIpcSmtx;
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()) {
std::unique_lock<std::shared_mutex> windowIpcUniqueLock(windowIpcSmtx);
separateOutputs_ = config["separate-outputs"].asBool();
update();
// register for hyprland ipc
std::unique_lock<std::shared_mutex> windowIpcUniqueLock(windowIpcSmtx);
m_ipc.registerForIPC("activewindow", this);
m_ipc.registerForIPC("closewindow", this);
m_ipc.registerForIPC("movewindow", this);
m_ipc.registerForIPC("changefloatingmode", this);
m_ipc.registerForIPC("fullscreen", this);
windowIpcUniqueLock.unlock();
queryActiveWorkspace();
update();
dp.emit();
}
@@ -124,7 +122,7 @@ auto Window::getActiveWorkspace(const std::string& monitorName) -> Workspace {
const auto monitors = IPC::inst().getSocket1JsonReply("monitors");
if (monitors.isArray()) {
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)) {
spdlog::warn("Monitor not found: {}", monitorName);
return Workspace{
@@ -139,7 +137,7 @@ auto Window::getActiveWorkspace(const std::string& monitorName) -> Workspace {
const auto workspaces = IPC::inst().getSocket1JsonReply("workspaces");
if (workspaces.isArray()) {
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)) {
spdlog::warn("No workspace with id {}", id);
return Workspace{
@@ -177,63 +175,65 @@ auto Window::WindowData::parse(const Json::Value& value) -> Window::WindowData {
}
void Window::queryActiveWorkspace() {
std::shared_lock<std::shared_mutex> windowIpcShareLock(windowIpcSmtx);
if (separateOutputs_) {
workspace_ = getActiveWorkspace(this->bar_.output->name);
} else {
workspace_ = getActiveWorkspace();
}
focused_ = false;
windowData_ = WindowData{};
allFloating_ = false;
swallowing_ = false;
fullscreen_ = false;
solo_ = false;
soloClass_.clear();
if (workspace_.windows <= 0) {
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;
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;
return;
}
windowData_ = WindowData::parse(*activeWindow);
updateAppIconName(windowData_.class_name, windowData_.initial_class_name);
std::vector<Json::Value> workspaceWindows;
std::ranges::copy_if(clients, std::back_inserter(workspaceWindows), [&](Json::Value window) {
windowData_ = WindowData::parse(*activeWindow);
updateAppIconName(windowData_.class_name, windowData_.initial_class_name);
std::vector<Json::Value> workspaceWindows;
std::ranges::copy_if(
clients, std::back_inserter(workspaceWindows), [&](const Json::Value& window) {
return window["workspace"]["id"] == workspace_.id && window["mapped"].asBool();
});
swallowing_ = std::ranges::any_of(workspaceWindows, [&](Json::Value window) {
return !window["swallowing"].isNull() && window["swallowing"].asString() != "0x0";
});
std::vector<Json::Value> visibleWindows;
std::ranges::copy_if(workspaceWindows, std::back_inserter(visibleWindows),
[&](Json::Value window) { return !window["hidden"].asBool(); });
solo_ = 1 == std::count_if(visibleWindows.begin(), visibleWindows.end(),
[&](Json::Value window) { return !window["floating"].asBool(); });
allFloating_ = std::ranges::all_of(
visibleWindows, [&](Json::Value window) { return window["floating"].asBool(); });
fullscreen_ = windowData_.fullscreen;
swallowing_ = std::ranges::any_of(workspaceWindows, [&](const Json::Value& window) {
return !window["swallowing"].isNull() && window["swallowing"].asString() != "0x0";
});
std::vector<Json::Value> visibleWindows;
std::ranges::copy_if(workspaceWindows, std::back_inserter(visibleWindows),
[&](const Json::Value& window) { return !window["hidden"].asBool(); });
solo_ = 1 == std::count_if(
visibleWindows.begin(), visibleWindows.end(),
[&](const Json::Value& window) { return !window["floating"].asBool(); });
allFloating_ = std::ranges::all_of(
visibleWindows, [&](const Json::Value& window) { return window["floating"].asBool(); });
fullscreen_ = windowData_.fullscreen;
// Fullscreen windows look like they are solo
if (fullscreen_) {
solo_ = true;
}
// Fullscreen windows look like they are solo
if (fullscreen_) {
solo_ = true;
}
if (solo_) {
soloClass_ = windowData_.class_name;
} else {
soloClass_ = "";
}
}
} else {
focused_ = false;
windowData_ = WindowData{};
allFloating_ = false;
swallowing_ = false;
fullscreen_ = false;
solo_ = false;
soloClass_ = "";
if (solo_) {
soloClass_ = windowData_.class_name;
}
}

View File

@@ -79,7 +79,7 @@ auto WindowCount::getActiveWorkspace(const std::string& monitorName) -> Workspac
const auto monitors = m_ipc.getSocket1JsonReply("monitors");
if (monitors.isArray()) {
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)) {
spdlog::warn("Monitor not found: {}", monitorName);
return Workspace{
@@ -93,7 +93,7 @@ auto WindowCount::getActiveWorkspace(const std::string& monitorName) -> Workspac
const auto workspaces = m_ipc.getSocket1JsonReply("workspaces");
if (workspaces.isArray()) {
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)) {
spdlog::warn("No workspace with id {}", id);
return Workspace{

View File

@@ -19,7 +19,7 @@ WindowCreationPayload::WindowCreationPayload(Json::Value const& client_data)
clearWorkspaceName();
}
WindowCreationPayload::WindowCreationPayload(std::string workspace_name,
WindowCreationPayload::WindowCreationPayload(const std::string& workspace_name,
WindowAddress window_address, WindowRepr window_repr)
: m_window(std::move(window_repr)),
m_windowAddress(std::move(window_address)),
@@ -28,9 +28,10 @@ WindowCreationPayload::WindowCreationPayload(std::string workspace_name,
clearWorkspaceName();
}
WindowCreationPayload::WindowCreationPayload(std::string workspace_name,
WindowAddress window_address, std::string window_class,
std::string window_title, bool is_active)
WindowCreationPayload::WindowCreationPayload(const std::string& workspace_name,
WindowAddress window_address,
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_windowAddress(std::move(window_address)),
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) {
m_windowMap.clear();
for (auto client : clients_data) {
for (const auto& client : clients_data) {
if (client["workspace"]["id"].asInt() == id()) {
insertWindow({client});
}

View File

@@ -34,6 +34,9 @@ Workspaces::Workspaces(const std::string& id, const Bar& bar, const Json::Value&
}
Workspaces::~Workspaces() {
if (m_scrollEventConnection_.connected()) {
m_scrollEventConnection_.disconnect();
}
m_ipc.unregisterForIPC(this);
// wait for possible event handler to finish
std::lock_guard<std::mutex> lg(m_mutex);
@@ -44,10 +47,14 @@ void Workspaces::init() {
initializeWorkspaces();
if (m_scrollEventConnection_.connected()) {
m_scrollEventConnection_.disconnect();
}
if (barScroll()) {
auto& window = const_cast<Bar&>(m_bar).window;
window.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);
window.signal_scroll_event().connect(sigc::mem_fun(*this, &Workspaces::handleScroll));
m_scrollEventConnection_ =
window.signal_scroll_event().connect(sigc::mem_fun(*this, &Workspaces::handleScroll));
}
dp.emit();
@@ -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;
if (windowRewriteConfigUsesTitle()) {
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 clientsJson = m_ipc.getSocket1JsonReply("clients");
for (Json::Value workspaceJson : workspacesJson) {
for (const auto& workspaceJson : workspacesJson) {
std::string workspaceName = workspaceJson["name"].asString();
if ((allOutputs() || m_bar.output->name == workspaceJson["monitor"].asString()) &&
(!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
for (const Json::Value& monitor : value) {
if (monitor.isString() && monitor.asString() == currentMonitor) {
persistentWorkspacesToCreate.emplace_back(currentMonitor);
persistentWorkspacesToCreate.emplace_back(key);
break;
}
}
@@ -331,8 +339,13 @@ void Workspaces::loadPersistentWorkspacesFromWorkspaceRules(const Json::Value& c
void Workspaces::onEvent(const std::string& ev) {
std::lock_guard<std::mutex> lock(m_mutex);
std::string eventName(begin(ev), begin(ev) + ev.find_first_of('>'));
std::string payload = ev.substr(eventName.size() + 2);
const auto separator = ev.find(">>");
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") {
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 workspacesJson = m_ipc.getSocket1JsonReply("workspaces");
for (Json::Value workspaceJson : workspacesJson) {
for (auto workspaceJson : workspacesJson) {
const auto currentId = workspaceJson["id"].asInt();
if (currentId == *workspaceId) {
std::string workspaceName = workspaceJson["name"].asString();
@@ -495,19 +508,21 @@ void Workspaces::onMonitorFocused(std::string const& payload) {
void Workspaces::onWindowOpened(std::string const& payload) {
spdlog::trace("Window opened: {}", payload);
updateWindowCount();
size_t lastCommaIdx = 0;
size_t nextCommaIdx = payload.find(',');
std::string windowAddress = payload.substr(lastCommaIdx, nextCommaIdx - lastCommaIdx);
const auto firstComma = payload.find(',');
const auto secondComma =
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;
nextCommaIdx = payload.find(',', nextCommaIdx + 1);
std::string workspaceName = payload.substr(lastCommaIdx + 1, nextCommaIdx - lastCommaIdx - 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);
std::string windowAddress = payload.substr(0, firstComma);
std::string workspaceName = payload.substr(firstComma + 1, secondComma - firstComma - 1);
std::string windowClass = payload.substr(secondComma + 1, thirdComma - secondComma - 1);
std::string windowTitle = payload.substr(thirdComma + 1);
bool isActive = m_currentActiveWindowAddress == windowAddress;
m_windowsToCreate.emplace_back(workspaceName, windowAddress, windowClass, windowTitle, isActive);
@@ -1001,10 +1016,12 @@ void Workspaces::sortWorkspaces() {
void Workspaces::setUrgentWorkspace(std::string const& windowaddress) {
const Json::Value clientsJson = m_ipc.getSocket1JsonReply("clients");
const std::string normalizedAddress =
windowaddress.starts_with("0x") ? windowaddress : fmt::format("0x{}", windowaddress);
int workspaceId = -1;
for (Json::Value clientJson : clientsJson) {
if (clientJson["address"].asString().ends_with(windowaddress)) {
for (const auto& clientJson : clientsJson) {
if (clientJson["address"].asString() == normalizedAddress) {
workspaceId = clientJson["workspace"]["id"].asInt();
break;
}
@@ -1133,7 +1150,11 @@ std::string Workspaces::makePayload(Args const&... args) {
}
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);
return {part1, part2};
}
@@ -1142,6 +1163,9 @@ std::tuple<std::string, std::string, std::string> Workspaces::splitTriplePayload
std::string const& payload) {
const size_t firstComma = payload.find(',');
const size_t secondComma = payload.find(',', firstComma + 1);
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 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()) {
inhibitors = checkInhibitor(config["what"][0].asString());
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;
}

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()) {
return "";
}

View File

@@ -109,6 +109,7 @@ Mpris::Mpris(const std::string& id, const Json::Value& config)
player_ = config_["player"].asString();
}
if (config_["ignored-players"].isArray()) {
ignored_players_.reserve(config_["ignored-players"].size());
for (const auto& item : config_["ignored-players"]) {
if (item.isString()) {
ignored_players_.push_back(item.asString());
@@ -161,8 +162,7 @@ Mpris::Mpris(const std::string& id, const Json::Value& config)
if (player) {
g_object_connect(player, "signal::play", G_CALLBACK(onPlayerPlay), this, "signal::pause",
G_CALLBACK(onPlayerPause), this, "signal::stop", G_CALLBACK(onPlayerStop),
this, "signal::stop", G_CALLBACK(onPlayerStop), this, "signal::metadata",
G_CALLBACK(onPlayerMetadata), this, NULL);
this, "signal::metadata", G_CALLBACK(onPlayerMetadata), this, NULL);
}
// 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() {
if (last_active_player_ && last_active_player_ != player) g_object_unref(last_active_player_);
if (manager != nullptr) g_object_unref(manager);
if (player != nullptr) g_object_unref(player);
if (manager != nullptr) {
g_signal_handlers_disconnect_by_data(manager, this);
}
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 {
@@ -411,11 +419,14 @@ auto Mpris::onPlayerNameAppeared(PlayerctlPlayerManager* manager, PlayerctlPlaye
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);
g_object_connect(mpris->player, "signal::play", G_CALLBACK(onPlayerPlay), mpris, "signal::pause",
G_CALLBACK(onPlayerPause), mpris, "signal::stop", G_CALLBACK(onPlayerStop),
mpris, "signal::stop", G_CALLBACK(onPlayerStop), mpris, "signal::metadata",
G_CALLBACK(onPlayerMetadata), mpris, NULL);
mpris, "signal::metadata", G_CALLBACK(onPlayerMetadata), mpris, NULL);
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_up_total_ = 0;
}
bandwidth_last_sample_time_ = std::chrono::steady_clock::now();
if (!config_["interface"].isString()) {
// "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 {
std::lock_guard<std::mutex> lock(mutex_);
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_down = 0ull;
@@ -321,6 +328,7 @@ auto waybar::modules::Network::update() -> void {
} else if (addr_pref_ == ip_addr_pref::IPV6) {
final_ipaddr_ = ipaddr6_;
} else if (addr_pref_ == ip_addr_pref::IPV4_6) {
final_ipaddr_.reserve(ipaddr_.length() + ipaddr6_.length() + 1);
final_ipaddr_ = ipaddr_;
final_ipaddr_ += '\n';
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("cidr6", cidr6_), fmt::arg("frequency", fmt::format("{:.1f}", frequency_)),
fmt::arg("icon", getIcon(signal_strength_, state_)),
fmt::arg("bandwidthDownBits",
pow_format(bandwidth_down * 8ull / (interval_.count() / 1000.0), "b/s")),
fmt::arg("bandwidthUpBits",
pow_format(bandwidth_up * 8ull / (interval_.count() / 1000.0), "b/s")),
fmt::arg(
"bandwidthTotalBits",
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("bandwidthDownBits", pow_format(bandwidth_down * 8ull / elapsed_seconds, "b/s")),
fmt::arg("bandwidthUpBits", pow_format(bandwidth_up * 8ull / elapsed_seconds, "b/s")),
fmt::arg("bandwidthTotalBits",
pow_format((bandwidth_up + bandwidth_down) * 8ull / elapsed_seconds, "b/s")),
fmt::arg("bandwidthDownOctets", pow_format(bandwidth_down / elapsed_seconds, "o/s")),
fmt::arg("bandwidthUpOctets", pow_format(bandwidth_up / elapsed_seconds, "o/s")),
fmt::arg("bandwidthTotalOctets",
pow_format((bandwidth_up + bandwidth_down) / (interval_.count() / 1000.0), "o/s")),
fmt::arg("bandwidthDownBytes",
pow_format(bandwidth_down / (interval_.count() / 1000.0), "B/s")),
fmt::arg("bandwidthUpBytes", pow_format(bandwidth_up / (interval_.count() / 1000.0), "B/s")),
pow_format((bandwidth_up + bandwidth_down) / elapsed_seconds, "o/s")),
fmt::arg("bandwidthDownBytes", pow_format(bandwidth_down / elapsed_seconds, "B/s")),
fmt::arg("bandwidthUpBytes", pow_format(bandwidth_up / elapsed_seconds, "B/s")),
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) {
label_.set_markup(text);
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("cidr6", cidr6_), fmt::arg("frequency", fmt::format("{:.1f}", frequency_)),
fmt::arg("icon", getIcon(signal_strength_, state_)),
fmt::arg("bandwidthDownBits",
pow_format(bandwidth_down * 8ull / interval_.count(), "b/s")),
fmt::arg("bandwidthUpBits", pow_format(bandwidth_up * 8ull / interval_.count(), "b/s")),
fmt::arg("bandwidthDownBits", pow_format(bandwidth_down * 8ull / elapsed_seconds, "b/s")),
fmt::arg("bandwidthUpBits", pow_format(bandwidth_up * 8ull / elapsed_seconds, "b/s")),
fmt::arg("bandwidthTotalBits",
pow_format((bandwidth_up + bandwidth_down) * 8ull / interval_.count(), "b/s")),
fmt::arg("bandwidthDownOctets", pow_format(bandwidth_down / interval_.count(), "o/s")),
fmt::arg("bandwidthUpOctets", pow_format(bandwidth_up / interval_.count(), "o/s")),
pow_format((bandwidth_up + bandwidth_down) * 8ull / elapsed_seconds, "b/s")),
fmt::arg("bandwidthDownOctets", pow_format(bandwidth_down / elapsed_seconds, "o/s")),
fmt::arg("bandwidthUpOctets", pow_format(bandwidth_up / elapsed_seconds, "o/s")),
fmt::arg("bandwidthTotalOctets",
pow_format((bandwidth_up + bandwidth_down) / interval_.count(), "o/s")),
fmt::arg("bandwidthDownBytes", pow_format(bandwidth_down / interval_.count(), "B/s")),
fmt::arg("bandwidthUpBytes", pow_format(bandwidth_up / interval_.count(), "B/s")),
pow_format((bandwidth_up + bandwidth_down) / elapsed_seconds, "o/s")),
fmt::arg("bandwidthDownBytes", pow_format(bandwidth_down / elapsed_seconds, "B/s")),
fmt::arg("bandwidthUpBytes", pow_format(bandwidth_up / elapsed_seconds, "B/s")),
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) {
label_.set_tooltip_markup(tooltip_text);
}
@@ -626,18 +628,31 @@ int waybar::modules::Network::handleEvents(struct nl_msg* msg, void* data) {
case IFA_LOCAL:
char ipaddr[INET6_ADDRSTRLEN];
if (!is_del_event) {
bool addr_changed = false;
std::string changed_ipaddr;
int changed_cidr = 0;
if ((net->addr_pref_ == ip_addr_pref::IPV4 ||
net->addr_pref_ == ip_addr_pref::IPV4_6) &&
net->cidr_ == 0 && ifa->ifa_family == AF_INET) {
net->ipaddr_ =
inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta), ipaddr, sizeof(ipaddr));
net->cidr_ = ifa->ifa_prefixlen;
if (inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta), ipaddr, sizeof(ipaddr)) !=
nullptr) {
net->ipaddr_ = ipaddr;
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 ||
net->addr_pref_ == ip_addr_pref::IPV4_6) &&
net->cidr6_ == 0 && ifa->ifa_family == AF_INET6) {
net->ipaddr6_ =
inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta), ipaddr, sizeof(ipaddr));
net->cidr6_ = ifa->ifa_prefixlen;
if (inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta), ipaddr, sizeof(ipaddr)) !=
nullptr) {
net->ipaddr6_ = ipaddr;
net->cidr6_ = ifa->ifa_prefixlen;
addr_changed = true;
changed_ipaddr = net->ipaddr6_;
changed_cidr = net->cidr6_;
}
}
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));
}
}
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 {
net->ipaddr_.clear();
net->ipaddr6_.clear();
@@ -719,16 +737,20 @@ int waybar::modules::Network::handleEvents(struct nl_msg* msg, void* data) {
/* The destination address.
* Should be either missing, or maybe all 0s. Accept both.
*/
const uint32_t nr_zeroes = (family == AF_INET) ? 4 : 16;
unsigned char c = 0;
size_t dstlen = RTA_PAYLOAD(attr);
if (dstlen != nr_zeroes) {
break;
auto* dest = (const unsigned char*)RTA_DATA(attr);
size_t dest_size = RTA_PAYLOAD(attr);
for (size_t i = 0; i < dest_size; ++i) {
if (dest[i] != 0) {
has_destination = true;
break;
}
}
for (uint32_t i = 0; i < dstlen; i += 1) {
c |= *((unsigned char*)RTA_DATA(attr) + i);
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;
}
has_destination = (c == 0);
break;
}
case RTA_OIF:

View File

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

View File

@@ -32,6 +32,7 @@ void Language::updateFromIPC() {
auto ipcLock = gIPC->lockData();
layouts_.clear();
layouts_.reserve(gIPC->keyboardLayoutNames().size());
for (const auto& fullName : gIPC->keyboardLayoutNames()) layouts_.push_back(getLayout(fullName));
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,
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()) + "-" +
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),
bar_(bar),
on_add_(on_add),
on_remove_(on_remove) {}
on_remove_(on_remove),
on_update_(on_update) {}
Host::~Host() {
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_clear_object(&cancellable_);
g_clear_object(&watcher_);
items_.clear();
clearItems();
}
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);
for (auto it = host->items_.begin(); it != host->items_.end(); ++it) {
if ((*it)->bus_name == bus_name && (*it)->object_path == object_path) {
host->on_remove_(*it);
host->items_.erase(it);
host->removeItem(it);
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) {
auto it = service.find('/');
if (it != std::string::npos) {
@@ -132,15 +171,16 @@ std::tuple<std::string, std::string> Host::getBusNameAndObjectPath(const std::st
return {service, "/StatusNotifierItem"};
}
void Host::addRegisteredItem(std::string service) {
void Host::addRegisteredItem(const std::string& service) {
std::string bus_name, object_path;
std::tie(bus_name, object_path) = getBusNameAndObjectPath(service);
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;
});
if (it == items_.end()) {
items_.emplace_back(new Item(bus_name, object_path, config_, bar_));
on_add_(items_.back());
items_.emplace_back(new Item(
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 <spdlog/spdlog.h>
#include <algorithm>
#include <filesystem>
#include <fstream>
#include <map>
@@ -37,13 +38,18 @@ namespace waybar::modules::SNI {
static const Glib::ustring SNI_INTERFACE_NAME = sn_item_interface_info()->name;
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),
object_path(op),
icon_size(16),
effective_icon_size(0),
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()) {
icon_size = config["icon-size"].asUInt();
}
@@ -85,6 +91,8 @@ Item::~Item() {
}
}
bool Item::isReady() const { return ready_; }
bool Item::handleMouseEnter(GdkEventCrossing* const& e) {
event_box.set_state_flags(Gtk::StateFlags::STATE_FLAG_PRELIGHT);
return false;
@@ -112,14 +120,18 @@ void Item::proxyReady(Glib::RefPtr<Gio::AsyncResult>& result) {
if (this->id.empty() || this->category.empty()) {
spdlog::error("Invalid Status Notifier Item: {}, {}", bus_name, object_path);
invalidate();
return;
}
this->updateImage();
setReady();
} catch (const Glib::Error& err) {
spdlog::error("Failed to create DBus Proxy for {} {}: {}", bus_name, object_path, err.what());
invalidate();
} catch (const std::exception& err) {
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") {
overlay_icon_name = get_variant<std::string>(value);
} else if (name == "OverlayIconPixmap") {
// TODO: overlay_icon_pixmap
overlay_icon_pixmap = extractPixBuf(value.gobj());
} else if (name == "AttentionIconName") {
attention_icon_name = get_variant<std::string>(value);
} else if (name == "AttentionIconPixmap") {
// TODO: attention_icon_pixmap
attention_icon_pixmap = extractPixBuf(value.gobj());
} else if (name == "AttentionMovieName") {
attention_movie_name = get_variant<std::string>(value);
} 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) {
Glib::ustring lower = value.lowercase();
event_box.set_visible(show_passive_ || lower.compare("passive") != 0);
status_ = value.lowercase();
event_box.set_visible(show_passive_ || status_.compare("passive") != 0);
auto style = event_box.get_style_context();
for (const auto& class_name : style->list_classes()) {
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
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) {
@@ -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 = {
{"NewTitle", {"Title"}},
{"NewIcon", {"IconName", "IconPixmap"}},
// {"NewAttentionIcon", {"AttentionIconName", "AttentionIconPixmap", "AttentionMovieName"}},
// {"NewOverlayIcon", {"OverlayIconName", "OverlayIconPixmap"}},
{"NewAttentionIcon", {"AttentionIconName", "AttentionIconPixmap", "AttentionMovieName"}},
{"NewOverlayIcon", {"OverlayIconName", "OverlayIconPixmap"}},
{"NewIconThemePath", {"IconThemePath"}},
{"NewToolTip", {"ToolTip"}},
{"NewStatus", {"Status"}},
@@ -336,11 +365,20 @@ Glib::RefPtr<Gdk::Pixbuf> Item::extractPixBuf(GVariant* variant) {
if (array != nullptr) {
g_free(array);
}
#if GLIB_MAJOR_VERSION >= 2 && GLIB_MINOR_VERSION >= 68
array = static_cast<guchar*>(g_memdup2(data, size));
#else
array = static_cast<guchar*>(g_memdup(data, size));
#endif
// We must allocate our own array because the data from GVariant is read-only
// and we need to modify it to convert ARGB to RGBA.
array = static_cast<guchar*>(g_malloc(size));
// 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;
lheight = height;
}
@@ -349,14 +387,6 @@ Glib::RefPtr<Gdk::Pixbuf> Item::extractPixBuf(GVariant* variant) {
}
g_variant_iter_free(it);
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,
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 = overlayPixbufs(pixbuf, getOverlayIconPixbuf());
auto surface =
Gdk::Cairo::create_surface_from_pixbuf(pixbuf, image.get_scale_factor(), image.get_window());
image.set(surface);
}
Glib::RefPtr<Gdk::Pixbuf> Item::getIconPixbuf() {
if (!icon_name.empty()) {
try {
std::ifstream temp(icon_name);
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()));
if (status_ == "needsattention") {
if (auto attention_pixbuf = getAttentionIconPixbuf()) {
return attention_pixbuf;
}
}
// 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) {
return icon_pixmap;
}
@@ -421,9 +439,78 @@ Glib::RefPtr<Gdk::Pixbuf> Item::getIconPixbuf() {
return getIconByName("image-missing", getScaledIconSize());
}
Glib::RefPtr<Gdk::Pixbuf> Item::getIconByName(const std::string& name, int request_size) {
icon_theme->rescan_if_needed();
Glib::RefPtr<Gdk::Pixbuf> Item::getAttentionIconPixbuf() {
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()) {
auto icon_info = icon_theme->lookup_icon(name.c_str(), request_size,
Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE);
@@ -465,6 +552,9 @@ void Item::makeMenu() {
}
bool Item::handleClick(GdkEventButton* const& ev) {
if (!proxy_) {
return false;
}
auto parameters = Glib::VariantContainerBase::create_tuple(
{Glib::Variant<int>::create(ev->x_root + bar_.x_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) {
if (!proxy_) {
return false;
}
int dx = 0, dy = 0;
switch (ev->direction) {
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),
watcher_(SNI::Watcher::getInstance()),
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");
event_box_.add(box_);
if (!id.empty()) {
@@ -33,6 +34,8 @@ Tray::Tray(const std::string& id, const Bar& bar, const Json::Value& config)
dp.emit();
}
void Tray::queueUpdate() { dp.emit(); }
void Tray::onAdd(std::unique_ptr<Item>& item) {
if (config_["reverse-direction"].isBool() && config_["reverse-direction"].asBool()) {
box_.pack_end(item->event_box);

View File

@@ -69,7 +69,7 @@ gboolean Watcher::handleRegisterHost(Watcher* obj, GDBusMethodInvocation* invoca
if (watch != nullptr) {
g_warning("Status Notifier Host with bus name '%s' and object path '%s' is already registered",
bus_name, object_path);
sn_watcher_complete_register_item(obj->watcher_, invocation);
sn_watcher_complete_register_host(obj->watcher_, invocation);
return TRUE;
}
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);
if (watch != nullptr) {
g_warning("Status Notifier Item with bus name '%s' and object path '%s' is already registered",
bus_name, object_path);
spdlog::debug("Ignoring duplicate Status Notifier Item registration for '{}' at '{}'", bus_name,
object_path);
sn_watcher_complete_register_item(obj->watcher_, invocation);
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);
if (watch->watcher->hosts_ == nullptr) {
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) {
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);
g_free(tmp);
}
gfWatchFree(watch);
}
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"}) {
if (const auto& modules = bar_.config.get(section, {}); modules.isArray()) {
for (const auto& module : modules) {

View File

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

View File

@@ -124,7 +124,7 @@ auto Language::update() -> void {
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);
layout_ = layouts_map_[current_layout];
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;
}
if (!marks.empty()) {
marks += ',';
marks.append(",");
}
marks += m.asString();
marks.append(m.asString());
}
}
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
// 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) {
errno = 0;
char* endptr = nullptr;
@@ -487,7 +487,7 @@ std::string Workspaces::getCycleWorkspace(std::vector<Json::Value>::iterator it,
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(':');
if (found != std::string::npos) {
return name.substr(found + 1);

View File

@@ -1,10 +1,15 @@
#include "modules/systemd_failed_units.hpp"
#include <fmt/format.h>
#include <giomm/dbusproxy.h>
#include <glibmm/markup.h>
#include <glibmm/variant.h>
#include <spdlog/spdlog.h>
#include <cstdint>
#include <exception>
#include <stdexcept>
#include <tuple>
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)
: ALabel(config, "systemd-failed-units", id, "{nr_failed} failed", 1),
hide_on_ok(true),
update_pending(false),
nr_failed_system(0),
nr_failed_user(0),
nr_failed(0),
last_status() {
hide_on_ok_(true),
tooltip_format_(
"System: {system_state}\nUser: {user_state}\nFailed units ({nr_failed}):\n"
"{failed_units_list}"),
tooltip_format_ok_("System: {system_state}\nUser: {user_state}"),
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()) {
hide_on_ok = config["hide-on-ok"].asBool();
hide_on_ok_ = config["hide-on-ok"].asBool();
}
if (config["format-ok"].isString()) {
format_ok = config["format-ok"].asString();
format_ok_ = config["format-ok"].asString();
} 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". */
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",
"/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!");
}
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()) {
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",
"/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!");
}
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();
@@ -52,16 +89,11 @@ SystemdFailedUnits::SystemdFailedUnits(const std::string& id, const Json::Value&
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,
const Glib::ustring& signal_name,
const Glib::VariantContainerBase& arguments) -> void {
if (signal_name == "PropertiesChanged" && !update_pending) {
update_pending = true;
if (signal_name == "PropertiesChanged" && !update_pending_) {
update_pending_ = true;
/* The fail count may fluctuate due to restarting. */
Glib::signal_timeout().connect_once(sigc::mem_fun(*this, &SystemdFailedUnits::updateData),
UPDATE_DEBOUNCE_TIME_MS);
@@ -88,12 +120,12 @@ void SystemdFailedUnits::RequestSystemState() {
return "unknown";
};
system_state = load("systemwide", system_proxy);
user_state = load("user", user_proxy);
if (system_state == "running" && user_state == "running")
overall_state = "ok";
system_state_ = load("systemwide", system_props_proxy_);
user_state_ = load("user", user_props_proxy_);
if (system_state_ == "running" && user_state_ == "running")
overall_state_ = "ok";
else
overall_state = "degraded";
overall_state_ = "degraded";
}
void SystemdFailedUnits::RequestFailedUnits() {
@@ -116,46 +148,153 @@ void SystemdFailedUnits::RequestFailedUnits() {
return 0;
};
nr_failed_system = load("systemwide", system_proxy);
nr_failed_user = load("user", user_proxy);
nr_failed = nr_failed_system + nr_failed_user;
nr_failed_system_ = load("systemwide", system_props_proxy_);
nr_failed_user_ = load("user", user_props_proxy_);
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() {
update_pending = false;
update_pending_ = false;
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();
}
auto SystemdFailedUnits::update() -> void {
if (last_status == overall_state) return;
// Hide if needed.
if (overall_state == "ok" && hide_on_ok) {
if (overall_state_ == "ok" && hide_on_ok_) {
event_box_.set_visible(false);
last_status_ = overall_state_;
return;
}
event_box_.set_visible(true);
// Set state class.
if (!last_status.empty() && label_.get_style_context()->has_class(last_status)) {
label_.get_style_context()->remove_class(last_status);
if (!last_status_.empty() && label_.get_style_context()->has_class(last_status_)) {
label_.get_style_context()->remove_class(last_status_);
}
if (!label_.get_style_context()->has_class(overall_state)) {
label_.get_style_context()->add_class(overall_state);
if (!label_.get_style_context()->has_class(overall_state_)) {
label_.get_style_context()->add_class(overall_state_);
}
last_status = overall_state;
last_status_ = overall_state_;
label_.set_markup(fmt::format(
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("system_state", system_state), fmt::arg("user_state", user_state),
fmt::arg("overall_state", overall_state)));
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("system_state", system_state_), fmt::arg("user_state", user_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();
}

View File

@@ -95,7 +95,7 @@ UPower::~UPower() {
removeDevices();
}
static const std::string getDeviceStatus(UpDeviceState& state) {
static std::string_view getDeviceStatus(UpDeviceState& state) {
switch (state) {
case UP_DEVICE_STATE_CHARGING:
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) {
case UP_DEVICE_KIND_LINE_POWER:
return "ac-adapter-symbolic";
@@ -212,7 +212,8 @@ auto UPower::update() -> void {
// Remove last status if it exists
if (!lastStatus_.empty() && box_.get_style_context()->has_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;
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 {
uint32_t len = buf.size();
if constexpr (std::endian::native != std::endian::little) len = byteswap(len);
(void)write(sock.fd, &len, 4);
(void)write(sock.fd, buf.data(), buf.size());
(void)write(sock, &len, 4);
(void)write(sock, buf.data(), buf.size());
}
auto read_exact(Sock& sock, size_t n) -> std::string {
auto buf = std::string(n, 0);
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) {
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"};
}
auto sock = socket(AF_UNIX, SOCK_STREAM, 0);
util::ScopedFd sock(socket(AF_UNIX, SOCK_STREAM, 0));
if (sock == -1) {
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;
if (::connect(sock, (const sockaddr*)&addr, sizeof(addr)) == -1) {
close(sock);
throw std::runtime_error{"Wayfire IPC: connect() failed"};
}
return {sock};
return sock;
}
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::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_);
g_clear_pointer(&apis_, g_ptr_array_unref);
g_clear_object(&om_);
@@ -528,6 +537,7 @@ bool waybar::modules::Wireplumber::handleScroll(GdkEventScroll* e) {
GVariant* variant = g_variant_new_double(newVol);
gboolean ret;
g_signal_emit_by_name(mixer_api_, "set-volume", node_id_, variant, &ret);
g_variant_unref(variant);
}
return true;
}

View File

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

View File

@@ -40,12 +40,16 @@ AudioBackend::AudioBackend(std::function<void()> on_updated_cb, private_construc
}
AudioBackend::~AudioBackend() {
if (context_ != nullptr) {
pa_context_disconnect(context_);
}
if (mainloop_ != nullptr) {
mainloop_api_->quit(mainloop_api_, 0);
// 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) {
pa_context_disconnect(context_);
pa_context_unref(context_);
context_ = nullptr;
}
pa_threaded_mainloop_unlock(mainloop_);
pa_threaded_mainloop_stop(mainloop_);
pa_threaded_mainloop_free(mainloop_);
}
@@ -73,7 +77,14 @@ void AudioBackend::contextStateCb(pa_context* c, void* data) {
auto* backend = static_cast<AudioBackend*>(data);
switch (pa_context_get_state(c)) {
case PA_CONTEXT_TERMINATED:
backend->mainloop_api_->quit(backend->mainloop_api_, 0);
// 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);
}
break;
case PA_CONTEXT_READY:
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.
if (backend->context_ != nullptr) {
pa_context_disconnect(backend->context_);
pa_context_unref(backend->context_);
backend->context_ = nullptr;
}
backend->connectContext();
break;

View File

@@ -21,7 +21,6 @@ Glib::RefPtr<Gdk::Pixbuf> DefaultGtkIconThemeWrapper::load_icon(
const std::lock_guard<std::mutex> lock(default_theme_mutex);
auto default_theme = Gtk::IconTheme::get_default();
default_theme->rescan_if_needed();
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
[wrap-file]
directory = cava-0.10.7-beta
source_url = https://github.com/LukashonakV/cava/archive/v0.10.7-beta.tar.gz
directory = cava-0.10.7
source_url = https://github.com/LukashonakV/cava/archive/0.10.7.tar.gz
source_filename = cava-0.10.7.tar.gz
source_hash = 8915d7214f2046554c158fe6f2ae518881dfb573e421ea848727be11a5dfa8c4
source_hash = 50cc6413e9c96c503657f814744a2baf429a24ff9fed31a8343e0ed285269eff
[provide]
libcava = cava_dep

View File

@@ -4,56 +4,132 @@
#include <catch2/catch.hpp>
#endif
#include "fixtures/IPCTestFixture.hpp"
#include <system_error>
#include "modules/hyprland/backend.hpp"
namespace fs = std::filesystem;
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
// 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::create_directories(tempDir / "hypr" / instanceSig);
fs::create_directories(expectedPath);
setenv("XDG_RUNTIME_DIR", tempDir.c_str(), 1);
IPCTestHelper::resetSocketFolder();
// Act
fs::path actualPath = getSocketFolder(instanceSig);
fs::path actualPath = hyprland::IPC::getSocketFolder(instanceSig);
// Assert expected result
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
// Arrange
constexpr auto instanceSig = "instance_sig";
unsetenv("XDG_RUNTIME_DIR");
fs::path expectedPath = fs::path("/tmp") / "hypr" / instanceSig;
IPCTestHelper::resetSocketFolder();
// Act
fs::path actualPath = getSocketFolder(instanceSig);
fs::path actualPath = hyprland::IPC::getSocketFolder(instanceSig);
// Assert expected result
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
// Arrange
constexpr auto instanceSig = "instance_sig";
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);
setenv("XDG_RUNTIME_DIR", tempDir.c_str(), 1);
fs::path expectedPath = fs::path("/tmp") / "hypr" / instanceSig;
IPCTestHelper::resetSocketFolder();
// Act
fs::path actualPath = getSocketFolder(instanceSig);
fs::path actualPath = hyprland::IPC::getSocketFolder(instanceSig);
// Assert expected result
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";
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
#include <thread>
#include <type_traits>
#include <vector>
#include "fixtures/GlibTestsFixture.hpp"
@@ -141,3 +142,33 @@ TEST_CASE_METHOD(GlibTestsFixture, "SafeSignal copy/move counter", "[signal][thr
producer.join();
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',
'JsonParser.cpp',
'SafeSignal.cpp',
'sleeper_thread.cpp',
'command.cpp',
'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);
}