Merge remote-tracking branch 'upstream/master'
This commit is contained in:
Binary file not shown.
+1
-1
@@ -48,7 +48,7 @@ class AModule : public IModule {
|
||||
virtual bool handleMouseLeave(GdkEventCrossing* const& ev);
|
||||
virtual bool handleScroll(GdkEventScroll*);
|
||||
virtual bool handleRelease(GdkEventButton* const& ev);
|
||||
GObject* menu_;
|
||||
GObject* menu_ = nullptr;
|
||||
|
||||
private:
|
||||
bool handleUserEvent(GdkEventButton* const& ev);
|
||||
|
||||
@@ -30,6 +30,7 @@ class Group : public AModule {
|
||||
bool handleMouseEnter(GdkEventCrossing* const& ev) override;
|
||||
bool handleMouseLeave(GdkEventCrossing* const& ev) override;
|
||||
bool handleToggle(GdkEventButton* const& ev) override;
|
||||
bool handleScroll(GdkEventScroll* e) override;
|
||||
void show_group();
|
||||
void hide_group();
|
||||
};
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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; }
|
||||
auto windowRewriteGroupThreshold() const -> int { return m_windowRewriteGroupThreshold; }
|
||||
auto const& getWindowRewriteGroupFormat() const { return m_windowRewriteGroupFormat; }
|
||||
@@ -212,6 +213,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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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&);
|
||||
|
||||
@@ -78,6 +78,7 @@ class Mpris : public ALabel {
|
||||
|
||||
PlayerctlPlayerManager* manager;
|
||||
PlayerctlPlayer* player;
|
||||
PlayerctlPlayer* last_active_player_ = nullptr;
|
||||
std::string lastStatus;
|
||||
std::string lastPlayer;
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ class EventHandler {
|
||||
|
||||
class IPC {
|
||||
public:
|
||||
IPC() { startIPC(); }
|
||||
IPC();
|
||||
|
||||
void registerForIPC(const std::string& ev, EventHandler* ev_handler);
|
||||
void unregisterForIPC(EventHandler* handler);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -12,8 +12,9 @@
|
||||
#include <string>
|
||||
|
||||
#include "ipc.hpp"
|
||||
#include "util/sleeper_thread.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_;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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,25 +73,9 @@ struct State {
|
||||
auto update_view(const Json::Value& view) -> void;
|
||||
};
|
||||
|
||||
struct Sock {
|
||||
int fd;
|
||||
using Sock = util::ScopedFd;
|
||||
|
||||
Sock(int fd) : fd{fd} {}
|
||||
~Sock() { close(fd); }
|
||||
Sock(const Sock&) = delete;
|
||||
auto operator=(const Sock&) = delete;
|
||||
Sock(Sock&& rhs) noexcept {
|
||||
fd = rhs.fd;
|
||||
rhs.fd = -1;
|
||||
}
|
||||
auto& operator=(Sock&& rhs) noexcept {
|
||||
fd = rhs.fd;
|
||||
rhs.fd = -1;
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
class IPC {
|
||||
class IPC : public std::enable_shared_from_this<IPC> {
|
||||
static std::weak_ptr<IPC> instance;
|
||||
Json::CharReaderBuilder reader_builder;
|
||||
Json::StreamWriterBuilder writer_builder;
|
||||
@@ -98,7 +84,7 @@ class IPC {
|
||||
State state;
|
||||
std::mutex state_mutex;
|
||||
|
||||
IPC() { start(); }
|
||||
IPC() = default;
|
||||
|
||||
static auto connect() -> Sock;
|
||||
auto receive(Sock& sock) -> Json::Value;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
@@ -59,6 +61,7 @@ inline int close(FILE* fp, pid_t pid) {
|
||||
spdlog::debug("Cmd continued");
|
||||
} else if (ret == -1) {
|
||||
spdlog::debug("waitpid failed: {}", strerror(errno));
|
||||
break;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
@@ -113,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]);
|
||||
}
|
||||
@@ -161,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);
|
||||
|
||||
@@ -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
|
||||
@@ -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_;
|
||||
};
|
||||
|
||||
|
||||
@@ -182,6 +182,7 @@ Every entry in the *events* object consists of a *<event-name>* (typeof: *string
|
||||
|
||||
- *on-<status>-<state>*
|
||||
- *on-<status>-<capacity>*
|
||||
- *on-<status>*
|
||||
|
||||
Where:
|
||||
|
||||
@@ -203,7 +204,9 @@ Where:
|
||||
"events": {
|
||||
"on-discharging-warning": "notify-send -u normal 'Low Battery'",
|
||||
"on-discharging-critical": "notify-send -u critical 'Very Low Battery'",
|
||||
"on-charging-100": "notify-send -u normal 'Battery Full!'"
|
||||
"on-charging-100": "notify-send -u normal 'Battery Full!'",
|
||||
"on-discharging": "notify-send -u normal 'Power Switch' Discharging",
|
||||
"on-charging": "notify-send -u normal 'Power Switch' Charging'"
|
||||
},
|
||||
"format": "{capacity}% {icon}",
|
||||
"format-icons": ["", "", "", "", ""],
|
||||
|
||||
+13
-8
@@ -151,19 +151,24 @@ void AAppIconLabel::updateAppIconName(const std::string& app_identifier,
|
||||
}
|
||||
|
||||
void AAppIconLabel::updateAppIcon() {
|
||||
if (update_app_icon_) {
|
||||
if (update_app_icon_ || (!iconEnabled() && image_.get_visible())) {
|
||||
update_app_icon_ = false;
|
||||
if (app_icon_name_.empty()) {
|
||||
image_.set_visible(false);
|
||||
} else if (app_icon_name_.front() == '/') {
|
||||
auto pixbuf = Gdk::Pixbuf::create_from_file(app_icon_name_);
|
||||
int scaled_icon_size = app_icon_size_ * image_.get_scale_factor();
|
||||
pixbuf = Gdk::Pixbuf::create_from_file(app_icon_name_, scaled_icon_size, scaled_icon_size);
|
||||
try {
|
||||
int scaled_icon_size = app_icon_size_ * image_.get_scale_factor();
|
||||
auto pixbuf =
|
||||
Gdk::Pixbuf::create_from_file(app_icon_name_, scaled_icon_size, scaled_icon_size);
|
||||
|
||||
auto surface = Gdk::Cairo::create_surface_from_pixbuf(pixbuf, image_.get_scale_factor(),
|
||||
image_.get_window());
|
||||
image_.set(surface);
|
||||
image_.set_visible(true);
|
||||
auto surface = Gdk::Cairo::create_surface_from_pixbuf(pixbuf, image_.get_scale_factor(),
|
||||
image_.get_window());
|
||||
image_.set(surface);
|
||||
image_.set_visible(true);
|
||||
} catch (const Glib::Exception& e) {
|
||||
spdlog::warn("Failed to load app icon {}: {}", app_icon_name_, std::string(e.what()));
|
||||
image_.set_visible(false);
|
||||
}
|
||||
} else {
|
||||
image_.set_from_icon_name(app_icon_name_, Gtk::ICON_SIZE_INVALID);
|
||||
image_.set_visible(true);
|
||||
|
||||
+16
-4
@@ -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);
|
||||
@@ -91,13 +91,17 @@ ALabel::ALabel(const Json::Value& config, const std::string& name, const std::st
|
||||
|
||||
// Make the GtkBuilder and check for errors in his parsing
|
||||
if (gtk_builder_add_from_string(builder, fileContent.str().c_str(), -1, nullptr) == 0U) {
|
||||
g_object_unref(builder);
|
||||
throw std::runtime_error("Error found in the file " + menuFile);
|
||||
}
|
||||
|
||||
menu_ = gtk_builder_get_object(builder, "menu");
|
||||
if (menu_ == nullptr) {
|
||||
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>();
|
||||
|
||||
@@ -105,11 +109,17 @@ ALabel::ALabel(const Json::Value& config, const std::string& name, const std::st
|
||||
for (Json::Value::const_iterator it = config_["menu-actions"].begin();
|
||||
it != config_["menu-actions"].end(); ++it) {
|
||||
std::string key = it.key().asString();
|
||||
submenus_[key] = GTK_MENU_ITEM(gtk_builder_get_object(builder, key.c_str()));
|
||||
auto* item = gtk_builder_get_object(builder, key.c_str());
|
||||
if (item == nullptr) {
|
||||
spdlog::warn("Menu item '{}' not found in builder file", key);
|
||||
continue;
|
||||
}
|
||||
submenus_[key] = GTK_MENU_ITEM(item);
|
||||
menuActionsMap_[key] = it->asString();
|
||||
g_signal_connect(submenus_[key], "activate", G_CALLBACK(handleGtkMenuEvent),
|
||||
(gpointer)menuActionsMap_[key].c_str());
|
||||
}
|
||||
g_object_unref(builder);
|
||||
} catch (std::runtime_error& e) {
|
||||
spdlog::warn("Error while creating the menu : {}. Menu popup not activated.", e.what());
|
||||
}
|
||||
@@ -141,7 +151,8 @@ std::string ALabel::getIcon(uint16_t percentage, const std::string& alt, uint16_
|
||||
if (format_icons.isArray()) {
|
||||
auto size = format_icons.size();
|
||||
if (size != 0U) {
|
||||
auto idx = std::clamp(percentage / ((max == 0 ? 100 : max) / size), 0U, size - 1);
|
||||
auto divisor = std::max(1U, (max == 0 ? 100U : static_cast<unsigned>(max)) / size);
|
||||
auto idx = std::clamp(percentage / divisor, 0U, size - 1);
|
||||
format_icons = format_icons[idx];
|
||||
}
|
||||
}
|
||||
@@ -167,7 +178,8 @@ std::string ALabel::getIcon(uint16_t percentage, const std::vector<std::string>&
|
||||
if (format_icons.isArray()) {
|
||||
auto size = format_icons.size();
|
||||
if (size != 0U) {
|
||||
auto idx = std::clamp(percentage / ((max == 0 ? 100 : max) / size), 0U, size - 1);
|
||||
auto divisor = std::max(1U, (max == 0 ? 100U : static_cast<unsigned>(max)) / size);
|
||||
auto idx = std::clamp(percentage / divisor, 0U, size - 1);
|
||||
format_icons = format_icons[idx];
|
||||
}
|
||||
}
|
||||
|
||||
+6
-2
@@ -88,6 +88,10 @@ AModule::~AModule() {
|
||||
killpg(pid, SIGTERM);
|
||||
}
|
||||
}
|
||||
if (menu_ != nullptr) {
|
||||
g_object_unref(menu_);
|
||||
menu_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
auto AModule::update() -> void {
|
||||
@@ -166,9 +170,9 @@ bool AModule::handleUserEvent(GdkEventButton* const& e) {
|
||||
}
|
||||
|
||||
// Check that a menu has been configured
|
||||
if (config_["menu"].isString()) {
|
||||
if (rec != eventMap_.cend() && config_["menu"].isString()) {
|
||||
// Check if the event is the one specified for the "menu" option
|
||||
if (rec->second == config_["menu"].asString()) {
|
||||
if (rec->second == config_["menu"].asString() && menu_ != nullptr) {
|
||||
// Popup the menu
|
||||
gtk_widget_show_all(GTK_WIDGET(menu_));
|
||||
gtk_menu_popup_at_pointer(GTK_MENU(menu_), reinterpret_cast<GdkEvent*>(e));
|
||||
|
||||
@@ -124,6 +124,11 @@ auto Group::update() -> void {
|
||||
// noop
|
||||
}
|
||||
|
||||
bool Group::handleScroll(GdkEventScroll* e) {
|
||||
// no scroll.
|
||||
return true;
|
||||
}
|
||||
|
||||
Gtk::Box& Group::getBox() { return is_drawer ? (is_first_widget ? box : revealer_box) : box; }
|
||||
|
||||
void Group::addWidget(Gtk::Widget& widget) {
|
||||
|
||||
+9
-5
@@ -33,7 +33,10 @@ static void writeSignalToPipe(int signum) {
|
||||
// to `signal_handler`.
|
||||
static void catchSignals(waybar::SafeSignal<int>& signal_handler) {
|
||||
int fd[2];
|
||||
pipe(fd);
|
||||
if (pipe(fd) != 0) {
|
||||
spdlog::error("Failed to create signal pipe: {}", strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
int signal_pipe_read_fd = fd[0];
|
||||
signal_pipe_write_fd = fd[1];
|
||||
@@ -137,15 +140,16 @@ static void handleSignalMainThread(int signum, bool& reload) {
|
||||
break;
|
||||
case SIGCHLD:
|
||||
spdlog::debug("Received SIGCHLD in signalThread");
|
||||
if (!reap.empty()) {
|
||||
reap_mtx.lock();
|
||||
for (auto it = reap.begin(); it != reap.end(); ++it) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(reap_mtx);
|
||||
for (auto it = reap.begin(); it != reap.end();) {
|
||||
if (waitpid(*it, nullptr, WNOHANG) == *it) {
|
||||
spdlog::debug("Reaped child with PID: {}", *it);
|
||||
it = reap.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
reap_mtx.unlock();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
||||
+14
-9
@@ -139,7 +139,9 @@ void waybar::modules::Battery::refreshBatteries() {
|
||||
auto event_path = (node.path() / "uevent");
|
||||
auto wd = inotify_add_watch(battery_watch_fd_, event_path.c_str(), IN_ACCESS);
|
||||
if (wd < 0) {
|
||||
throw std::runtime_error("Could not watch events for " + node.path().string());
|
||||
spdlog::warn("Could not watch events for {} (device may have been removed)",
|
||||
node.path().string());
|
||||
continue;
|
||||
}
|
||||
batteries_[node.path()] = wd;
|
||||
}
|
||||
@@ -790,16 +792,19 @@ void waybar::modules::Battery::processEvents(std::string& state, std::string& st
|
||||
if (!events.isObject() || events.empty()) {
|
||||
return;
|
||||
}
|
||||
std::string event_name = fmt::format("on-{}-{}", status == "discharging" ? status : "charging",
|
||||
state.empty() ? std::to_string(capacity) : state);
|
||||
auto exec = [](Json::Value const& event) {
|
||||
if (!event.isString()) return;
|
||||
if (auto command = event.asString(); !command.empty()) {
|
||||
util::command::exec(command, "");
|
||||
}
|
||||
};
|
||||
std::string status_name = status == "discharging" ? "on-discharging" : "on-charging";
|
||||
std::string event_name = status_name + '-' + (state.empty() ? std::to_string(capacity) : state);
|
||||
if (last_event_ != event_name) {
|
||||
spdlog::debug("battery: triggering event {}", event_name);
|
||||
if (events[event_name].isString()) {
|
||||
std::string exec = events[event_name].asString();
|
||||
// Execute the command if it is not empty
|
||||
if (!exec.empty()) {
|
||||
util::command::exec(exec, "");
|
||||
}
|
||||
exec(events[event_name]);
|
||||
if (!last_event_.empty() && last_event_[3] != event_name[3]) {
|
||||
exec(events[status_name]);
|
||||
}
|
||||
last_event_ = event_name;
|
||||
}
|
||||
|
||||
@@ -111,13 +111,16 @@ waybar::modules::Bluetooth::Bluetooth(const std::string& id, const Json::Value&
|
||||
findConnectedDevices(cur_controller_->path, connected_devices_);
|
||||
}
|
||||
|
||||
g_signal_connect(manager_.get(), "object-added", G_CALLBACK(onObjectAdded), this);
|
||||
g_signal_connect(manager_.get(), "object-removed", G_CALLBACK(onObjectRemoved), this);
|
||||
g_signal_connect(manager_.get(), "interface-proxy-properties-changed",
|
||||
G_CALLBACK(onInterfaceProxyPropertiesChanged), this);
|
||||
g_signal_connect(manager_.get(), "interface-added", G_CALLBACK(onInterfaceAddedOrRemoved), this);
|
||||
g_signal_connect(manager_.get(), "interface-removed", G_CALLBACK(onInterfaceAddedOrRemoved),
|
||||
this);
|
||||
if (manager_) {
|
||||
g_signal_connect(manager_.get(), "object-added", G_CALLBACK(onObjectAdded), this);
|
||||
g_signal_connect(manager_.get(), "object-removed", G_CALLBACK(onObjectRemoved), this);
|
||||
g_signal_connect(manager_.get(), "interface-proxy-properties-changed",
|
||||
G_CALLBACK(onInterfaceProxyPropertiesChanged), this);
|
||||
g_signal_connect(manager_.get(), "interface-added", G_CALLBACK(onInterfaceAddedOrRemoved),
|
||||
this);
|
||||
g_signal_connect(manager_.get(), "interface-removed", G_CALLBACK(onInterfaceAddedOrRemoved),
|
||||
this);
|
||||
}
|
||||
|
||||
#ifdef WANT_RFKILL
|
||||
rfkill_.on_update.connect(sigc::hide(sigc::mem_fun(*this, &Bluetooth::update)));
|
||||
@@ -446,6 +449,10 @@ auto waybar::modules::Bluetooth::getControllerProperties(GDBusObject* object,
|
||||
auto waybar::modules::Bluetooth::findCurController() -> std::optional<ControllerInfo> {
|
||||
std::optional<ControllerInfo> controller_info;
|
||||
|
||||
if (!manager_) {
|
||||
return controller_info;
|
||||
}
|
||||
|
||||
GList* objects = g_dbus_object_manager_get_objects(manager_.get());
|
||||
for (GList* l = objects; l != NULL; l = l->next) {
|
||||
GDBusObject* object = G_DBUS_OBJECT(l->data);
|
||||
@@ -465,6 +472,9 @@ auto waybar::modules::Bluetooth::findCurController() -> std::optional<Controller
|
||||
auto waybar::modules::Bluetooth::findConnectedDevices(const std::string& cur_controller_path,
|
||||
std::vector<DeviceInfo>& connected_devices)
|
||||
-> void {
|
||||
if (!manager_) {
|
||||
return;
|
||||
}
|
||||
GList* objects = g_dbus_object_manager_get_objects(manager_.get());
|
||||
for (GList* l = objects; l != NULL; l = l->next) {
|
||||
GDBusObject* object = G_DBUS_OBJECT(l->data);
|
||||
|
||||
@@ -26,6 +26,7 @@ void waybar::modules::cava::Cava::pause_resume() { backend_->doPauseResume(); }
|
||||
auto waybar::modules::cava::Cava::onUpdate(const std::string& input) -> void {
|
||||
Glib::signal_idle().connect_once([this, input]() {
|
||||
if (silence_) {
|
||||
silence_ = false;
|
||||
label_.get_style_context()->remove_class("silent");
|
||||
if (!label_.get_style_context()->has_class("updated"))
|
||||
label_.get_style_context()->add_class("updated");
|
||||
@@ -38,7 +39,6 @@ auto waybar::modules::cava::Cava::onUpdate(const std::string& input) -> void {
|
||||
label_.show();
|
||||
ALabel::update();
|
||||
});
|
||||
silence_ = false;
|
||||
}
|
||||
|
||||
auto waybar::modules::cava::Cava::onSilence() -> void {
|
||||
|
||||
@@ -218,7 +218,7 @@ void waybar::modules::cava::CavaBackend::loadConfig() {
|
||||
prm_.input = ::cava::input_method_by_name(config_["method"].asString().c_str());
|
||||
if (config_["source"].isString()) {
|
||||
if (prm_.audio_source) free(prm_.audio_source);
|
||||
prm_.audio_source = config_["source"].asString().data();
|
||||
prm_.audio_source = strdup(config_["source"].asString().c_str());
|
||||
}
|
||||
if (config_["sample_rate"].isNumeric()) prm_.samplerate = config_["sample_rate"].asLargestInt();
|
||||
if (config_["sample_bits"].isInt()) prm_.samplebits = config_["sample_bits"].asInt();
|
||||
|
||||
@@ -35,7 +35,7 @@ CFFI::CFFI(const std::string& name, const std::string& id, const Json::Value& co
|
||||
throw std::runtime_error{std::string{"Missing wbcffi_init function: "} + dlerror()};
|
||||
}
|
||||
hooks_.deinit = reinterpret_cast<DenitFn*>(dlsym(handle, "wbcffi_deinit"));
|
||||
if (!hooks_.init) {
|
||||
if (!hooks_.deinit) {
|
||||
throw std::runtime_error{std::string{"Missing wbcffi_deinit function: "} + dlerror()};
|
||||
}
|
||||
// Optional functions
|
||||
@@ -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()});
|
||||
}
|
||||
|
||||
+16
-7
@@ -26,9 +26,7 @@ auto waybar::modules::Cpu::update() -> void {
|
||||
auto [load1, load5, load15] = Load::getLoad();
|
||||
auto [cpu_usage, tooltip] = CpuUsage::getCpuUsage(prev_times_);
|
||||
auto [max_frequency, min_frequency, avg_frequency] = CpuFrequency::getCpuFrequency();
|
||||
if (tooltipEnabled()) {
|
||||
label_.set_tooltip_markup(tooltip);
|
||||
}
|
||||
|
||||
auto format = format_;
|
||||
auto total_usage = cpu_usage.empty() ? 0 : cpu_usage[0];
|
||||
auto state = getState(total_usage);
|
||||
@@ -48,14 +46,25 @@ auto waybar::modules::Cpu::update() -> void {
|
||||
store.push_back(fmt::arg("max_frequency", max_frequency));
|
||||
store.push_back(fmt::arg("min_frequency", min_frequency));
|
||||
store.push_back(fmt::arg("avg_frequency", avg_frequency));
|
||||
std::vector<std::string> arg_names;
|
||||
arg_names.reserve(cpu_usage.size() * 2);
|
||||
for (size_t i = 1; i < cpu_usage.size(); ++i) {
|
||||
auto core_i = i - 1;
|
||||
auto core_format = fmt::format("usage{}", core_i);
|
||||
store.push_back(fmt::arg(core_format.c_str(), cpu_usage[i]));
|
||||
auto icon_format = fmt::format("icon{}", core_i);
|
||||
store.push_back(fmt::arg(icon_format.c_str(), getIcon(cpu_usage[i], icons)));
|
||||
arg_names.push_back(fmt::format("usage{}", core_i));
|
||||
store.push_back(fmt::arg(arg_names.back().c_str(), cpu_usage[i]));
|
||||
arg_names.push_back(fmt::format("icon{}", core_i));
|
||||
store.push_back(fmt::arg(arg_names.back().c_str(), getIcon(cpu_usage[i], icons)));
|
||||
}
|
||||
label_.set_markup(fmt::vformat(format, store));
|
||||
|
||||
if (tooltipEnabled()) {
|
||||
if (config_["tooltip-format"].isString()) {
|
||||
tooltip = config_["tooltip-format"].asString();
|
||||
label_.set_tooltip_markup(fmt::vformat(tooltip, store));
|
||||
} else {
|
||||
label_.set_tooltip_markup(tooltip);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Call parent update
|
||||
|
||||
@@ -20,12 +20,7 @@ waybar::modules::CpuFrequency::CpuFrequency(const std::string& id, const Json::V
|
||||
auto waybar::modules::CpuFrequency::update() -> void {
|
||||
// TODO: as creating dynamic fmt::arg arrays is buggy we have to calc both
|
||||
auto [max_frequency, min_frequency, avg_frequency] = CpuFrequency::getCpuFrequency();
|
||||
if (tooltipEnabled()) {
|
||||
auto tooltip =
|
||||
fmt::format("Minimum frequency: {}\nAverage frequency: {}\nMaximum frequency: {}\n",
|
||||
min_frequency, avg_frequency, max_frequency);
|
||||
label_.set_tooltip_markup(tooltip);
|
||||
}
|
||||
|
||||
auto format = format_;
|
||||
auto state = getState(avg_frequency);
|
||||
if (!state.empty() && config_["format-" + state].isString()) {
|
||||
@@ -43,6 +38,18 @@ auto waybar::modules::CpuFrequency::update() -> void {
|
||||
store.push_back(fmt::arg("min_frequency", min_frequency));
|
||||
store.push_back(fmt::arg("avg_frequency", avg_frequency));
|
||||
label_.set_markup(fmt::vformat(format, store));
|
||||
|
||||
if (tooltipEnabled()) {
|
||||
std::string tooltip;
|
||||
if (config_["tooltip-format"].isString()) {
|
||||
tooltip = config_["tooltip-format"].asString();
|
||||
label_.set_tooltip_markup(fmt::vformat(tooltip, store));
|
||||
} else {
|
||||
tooltip = "Minimum frequency: {}\nAverage frequency: {}\nMaximum frequency: {}\n";
|
||||
label_.set_tooltip_markup(
|
||||
fmt::format(fmt::runtime(tooltip), min_frequency, avg_frequency, max_frequency));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Call parent update
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -20,9 +20,7 @@ waybar::modules::CpuUsage::CpuUsage(const std::string& id, const Json::Value& co
|
||||
auto waybar::modules::CpuUsage::update() -> void {
|
||||
// TODO: as creating dynamic fmt::arg arrays is buggy we have to calc both
|
||||
auto [cpu_usage, tooltip] = CpuUsage::getCpuUsage(prev_times_);
|
||||
if (tooltipEnabled()) {
|
||||
label_.set_tooltip_markup(tooltip);
|
||||
}
|
||||
|
||||
auto format = format_;
|
||||
auto total_usage = cpu_usage.empty() ? 0 : cpu_usage[0];
|
||||
auto state = getState(total_usage);
|
||||
@@ -38,14 +36,25 @@ auto waybar::modules::CpuUsage::update() -> void {
|
||||
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||
store.push_back(fmt::arg("usage", total_usage));
|
||||
store.push_back(fmt::arg("icon", getIcon(total_usage, icons)));
|
||||
std::vector<std::string> arg_names;
|
||||
arg_names.reserve(cpu_usage.size() * 2);
|
||||
for (size_t i = 1; i < cpu_usage.size(); ++i) {
|
||||
auto core_i = i - 1;
|
||||
auto core_format = fmt::format("usage{}", core_i);
|
||||
store.push_back(fmt::arg(core_format.c_str(), cpu_usage[i]));
|
||||
auto icon_format = fmt::format("icon{}", core_i);
|
||||
store.push_back(fmt::arg(icon_format.c_str(), getIcon(cpu_usage[i], icons)));
|
||||
arg_names.push_back(fmt::format("usage{}", core_i));
|
||||
store.push_back(fmt::arg(arg_names.back().c_str(), cpu_usage[i]));
|
||||
arg_names.push_back(fmt::format("icon{}", core_i));
|
||||
store.push_back(fmt::arg(arg_names.back().c_str(), getIcon(cpu_usage[i], icons)));
|
||||
}
|
||||
label_.set_markup(fmt::vformat(format, store));
|
||||
|
||||
if (tooltipEnabled()) {
|
||||
if (config_["tooltip-format"].isString()) {
|
||||
tooltip = config_["tooltip-format"].asString();
|
||||
label_.set_tooltip_markup(fmt::vformat(tooltip, store));
|
||||
} else {
|
||||
label_.set_tooltip_markup(tooltip);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Call parent update
|
||||
@@ -71,7 +80,8 @@ std::tuple<std::vector<uint16_t>, std::string> waybar::modules::CpuUsage::getCpu
|
||||
auto [prev_idle, prev_total] = prev_times[0];
|
||||
const float delta_idle = curr_idle - prev_idle;
|
||||
const float delta_total = curr_total - prev_total;
|
||||
uint16_t tmp = 100 * (1 - delta_idle / delta_total);
|
||||
uint16_t tmp =
|
||||
(delta_total > 0) ? static_cast<uint16_t>(100 * (1 - delta_idle / delta_total)) : 0;
|
||||
tooltip = fmt::format("Total: {}%\nCores: (pending)", tmp);
|
||||
usage.push_back(tmp);
|
||||
} else {
|
||||
@@ -93,7 +103,8 @@ std::tuple<std::vector<uint16_t>, std::string> waybar::modules::CpuUsage::getCpu
|
||||
}
|
||||
const float delta_idle = curr_idle - prev_idle;
|
||||
const float delta_total = curr_total - prev_total;
|
||||
uint16_t tmp = 100 * (1 - delta_idle / delta_total);
|
||||
uint16_t tmp =
|
||||
(delta_total > 0) ? static_cast<uint16_t>(100 * (1 - delta_idle / delta_total)) : 0;
|
||||
if (i == 0) {
|
||||
tooltip = fmt::format("Total: {}%", tmp);
|
||||
} else {
|
||||
|
||||
+14
-6
@@ -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,16 +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("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_) {
|
||||
label_.set_tooltip_markup(str);
|
||||
tooltip_markup = str;
|
||||
} else {
|
||||
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();
|
||||
|
||||
@@ -41,7 +41,7 @@ auto waybar::modules::Disk::update() -> void {
|
||||
fs_used - File system used space
|
||||
*/
|
||||
|
||||
if (err != 0) {
|
||||
if (err != 0 || stats.f_blocks == 0) {
|
||||
event_box_.hide();
|
||||
return;
|
||||
}
|
||||
@@ -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") {
|
||||
|
||||
@@ -179,6 +179,7 @@ bool Tags::handle_button_press(GdkEventButton* event_button, uint32_t tag) {
|
||||
}
|
||||
|
||||
void Tags::handle_view_tags(uint32_t tag, uint32_t state, uint32_t clients, uint32_t focused) {
|
||||
if (tag >= buttons_.size()) return;
|
||||
// First clear all occupied state
|
||||
auto& button = buttons_[tag];
|
||||
if (clients) {
|
||||
|
||||
@@ -143,7 +143,9 @@ void WorkspaceManager::handle_finished() {
|
||||
ext_manager_ = nullptr;
|
||||
}
|
||||
|
||||
void WorkspaceManager::commit() const { ext_workspace_manager_v1_commit(ext_manager_); }
|
||||
void WorkspaceManager::commit() const {
|
||||
if (ext_manager_) ext_workspace_manager_v1_commit(ext_manager_);
|
||||
}
|
||||
|
||||
void WorkspaceManager::update() {
|
||||
spdlog::debug("[ext/workspaces]: Updating state");
|
||||
|
||||
@@ -128,9 +128,9 @@ void Gamemode::getData() {
|
||||
Glib::VariantContainerBase data = gamemode_proxy->call_sync("Get", parameters);
|
||||
if (data && data.is_of_type(Glib::VariantType("(v)"))) {
|
||||
Glib::VariantBase variant;
|
||||
g_variant_get(data.gobj_copy(), "(v)", &variant);
|
||||
g_variant_get(const_cast<GVariant*>(data.gobj()), "(v)", &variant);
|
||||
if (variant && variant.is_of_type(Glib::VARIANT_TYPE_INT32)) {
|
||||
g_variant_get(variant.gobj_copy(), "i", &gameCount);
|
||||
g_variant_get(const_cast<GVariant*>(variant.gobj()), "i", &gameCount);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -158,7 +158,7 @@ void Gamemode::prepareForSleep_cb(const Glib::RefPtr<Gio::DBus::Connection>& con
|
||||
const Glib::VariantContainerBase& parameters) {
|
||||
if (parameters.is_of_type(Glib::VariantType("(b)"))) {
|
||||
gboolean sleeping;
|
||||
g_variant_get(parameters.gobj_copy(), "(b)", &sleeping);
|
||||
g_variant_get(const_cast<GVariant*>(parameters.gobj()), "(b)", &sleeping);
|
||||
if (!sleeping) {
|
||||
getData();
|
||||
dp.emit();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -32,7 +32,6 @@ Window::Window(const std::string& id, const Bar& bar, const Json::Value& config)
|
||||
|
||||
windowIpcUniqueLock.unlock();
|
||||
|
||||
queryActiveWorkspace();
|
||||
update();
|
||||
dp.emit();
|
||||
}
|
||||
@@ -124,7 +123,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 +138,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 +176,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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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)),
|
||||
|
||||
@@ -97,7 +97,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});
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
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);
|
||||
@@ -1011,10 +1026,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;
|
||||
}
|
||||
@@ -1143,7 +1160,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};
|
||||
}
|
||||
@@ -1152,6 +1173,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));
|
||||
@@ -1169,9 +1193,9 @@ std::optional<int> Workspaces::parseWorkspaceId(std::string const& workspaceIdSt
|
||||
}
|
||||
}
|
||||
|
||||
bool Workspaces::handleScroll(GdkEventScroll *e) {
|
||||
bool Workspaces::handleScroll(GdkEventScroll* e) {
|
||||
// Ignore emulated scroll events on window
|
||||
if (gdk_event_get_pointer_emulated((GdkEvent *)e)) {
|
||||
if (gdk_event_get_pointer_emulated((GdkEvent*)e)) {
|
||||
return false;
|
||||
}
|
||||
auto dir = AModule::getScrollDir(e);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ std::string JACK::JACKState() {
|
||||
auto JACK::update() -> void {
|
||||
std::string format;
|
||||
std::string state = JACKState();
|
||||
float latency = 1000 * (float)bufsize_ / (float)samplerate_;
|
||||
float latency = samplerate_ > 0 ? 1000.0f * (float)bufsize_ / (float)samplerate_ : 0.0f;
|
||||
|
||||
if (label_.get_style_context()->has_class("xrun")) {
|
||||
label_.get_style_context()->remove_class("xrun");
|
||||
@@ -91,16 +91,19 @@ auto JACK::update() -> void {
|
||||
}
|
||||
|
||||
int JACK::bufSize(jack_nframes_t size) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
bufsize_ = size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int JACK::sampleRate(jack_nframes_t rate) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
samplerate_ = rate;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int JACK::xrun() {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
xruns_ += 1;
|
||||
state_ = "xrun";
|
||||
return 0;
|
||||
|
||||
@@ -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 "";
|
||||
}
|
||||
@@ -323,6 +324,7 @@ void waybar::modules::MPD::checkErrors(mpd_connection* conn) {
|
||||
case MPD_ERROR_SYSTEM:
|
||||
if (auto ec = mpd_connection_get_system_error(conn); ec != 0) {
|
||||
mpd_connection_clear_error(conn);
|
||||
connection_.reset();
|
||||
throw std::system_error(ec, std::system_category());
|
||||
}
|
||||
G_GNUC_FALLTHROUGH;
|
||||
|
||||
+81
-32
@@ -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());
|
||||
@@ -117,7 +118,7 @@ Mpris::Mpris(const std::string& id, const Json::Value& config)
|
||||
}
|
||||
|
||||
GError* error = nullptr;
|
||||
waybar::util::ScopeGuard error_deleter([error]() {
|
||||
waybar::util::ScopeGuard error_deleter([&error]() {
|
||||
if (error) {
|
||||
g_error_free(error);
|
||||
}
|
||||
@@ -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,8 +178,17 @@ Mpris::Mpris(const std::string& id, const Json::Value& config)
|
||||
}
|
||||
|
||||
Mpris::~Mpris() {
|
||||
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 {
|
||||
@@ -410,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();
|
||||
}
|
||||
@@ -458,10 +470,7 @@ auto Mpris::onPlayerStop(PlayerctlPlayer* player, gpointer data) -> void {
|
||||
if (!mpris) return;
|
||||
|
||||
spdlog::debug("mpris: player-stop callback");
|
||||
|
||||
// hide widget
|
||||
mpris->event_box_.set_visible(false);
|
||||
// update widget
|
||||
// update widget (update() handles visibility)
|
||||
mpris->dp.emit();
|
||||
}
|
||||
|
||||
@@ -480,7 +489,7 @@ auto Mpris::getPlayerInfo() -> std::optional<PlayerInfo> {
|
||||
}
|
||||
|
||||
GError* error = nullptr;
|
||||
waybar::util::ScopeGuard error_deleter([error]() {
|
||||
waybar::util::ScopeGuard error_deleter([&error]() {
|
||||
if (error) {
|
||||
g_error_free(error);
|
||||
}
|
||||
@@ -488,7 +497,12 @@ auto Mpris::getPlayerInfo() -> std::optional<PlayerInfo> {
|
||||
|
||||
char* player_status = nullptr;
|
||||
auto player_playback_status = PLAYERCTL_PLAYBACK_STATUS_STOPPED;
|
||||
g_object_get(player, "status", &player_status, "playback-status", &player_playback_status, NULL);
|
||||
|
||||
// Clean up previous fallback player
|
||||
if (last_active_player_ && last_active_player_ != player) {
|
||||
g_object_unref(last_active_player_);
|
||||
last_active_player_ = nullptr;
|
||||
}
|
||||
|
||||
std::string player_name = player_;
|
||||
if (player_name == "playerctld") {
|
||||
@@ -498,19 +512,52 @@ auto Mpris::getPlayerInfo() -> std::optional<PlayerInfo> {
|
||||
}
|
||||
// > get the list of players [..] in order of activity
|
||||
// https://github.com/altdesktop/playerctl/blob/b19a71cb9dba635df68d271bd2b3f6a99336a223/playerctl/playerctl-common.c#L248-L249
|
||||
players = g_list_first(players);
|
||||
if (players)
|
||||
player_name = static_cast<PlayerctlPlayerName*>(players->data)->name;
|
||||
else
|
||||
return std::nullopt; // no players found, hide the widget
|
||||
}
|
||||
|
||||
if (std::any_of(ignored_players_.begin(), ignored_players_.end(),
|
||||
[&](const std::string& pn) { return player_name == pn; })) {
|
||||
PlayerctlPlayer* first_valid_player = nullptr;
|
||||
std::string first_valid_name;
|
||||
for (auto* p = g_list_first(players); p != nullptr; p = p->next) {
|
||||
auto* pn = static_cast<PlayerctlPlayerName*>(p->data);
|
||||
std::string name = pn->name;
|
||||
if (std::any_of(ignored_players_.begin(), ignored_players_.end(),
|
||||
[&](const std::string& ignored) { return name == ignored; })) {
|
||||
spdlog::warn("mpris[{}]: ignoring player update", name);
|
||||
continue;
|
||||
}
|
||||
auto* tmp = playerctl_player_new_from_name(pn, &error);
|
||||
if (error || !tmp) continue;
|
||||
if (!first_valid_player) {
|
||||
first_valid_player = tmp;
|
||||
first_valid_name = name;
|
||||
}
|
||||
PlayerctlPlaybackStatus status;
|
||||
g_object_get(tmp, "playback-status", &status, NULL);
|
||||
if (status == PLAYERCTL_PLAYBACK_STATUS_PLAYING) {
|
||||
if (tmp != first_valid_player) g_object_unref(first_valid_player);
|
||||
last_active_player_ = tmp;
|
||||
player_name = name;
|
||||
break;
|
||||
}
|
||||
if (tmp != first_valid_player) g_object_unref(tmp);
|
||||
}
|
||||
if (!last_active_player_) {
|
||||
if (!first_valid_player) return std::nullopt;
|
||||
last_active_player_ = first_valid_player;
|
||||
player_name = first_valid_name;
|
||||
}
|
||||
} else if (std::any_of(ignored_players_.begin(), ignored_players_.end(),
|
||||
[&](const std::string& pn) { return player_name == pn; })) {
|
||||
spdlog::warn("mpris[{}]: ignoring player update", player_name);
|
||||
return std::nullopt;
|
||||
} else {
|
||||
last_active_player_ = player;
|
||||
}
|
||||
|
||||
g_object_get(last_active_player_, "status", &player_status, "playback-status",
|
||||
&player_playback_status, NULL);
|
||||
|
||||
if (!player_status) {
|
||||
spdlog::error("mpris: failed to get player status");
|
||||
return std::nullopt;
|
||||
}
|
||||
// make status lowercase
|
||||
player_status[0] = std::tolower(player_status[0]);
|
||||
|
||||
@@ -524,28 +571,29 @@ auto Mpris::getPlayerInfo() -> std::optional<PlayerInfo> {
|
||||
.length = std::nullopt,
|
||||
};
|
||||
|
||||
if (auto* artist_ = playerctl_player_get_artist(player, &error)) {
|
||||
if (auto* artist_ = playerctl_player_get_artist(last_active_player_, &error)) {
|
||||
spdlog::debug("mpris[{}]: artist = {}", info.name, artist_);
|
||||
info.artist = artist_;
|
||||
g_free(artist_);
|
||||
}
|
||||
if (error) goto errorexit;
|
||||
|
||||
if (auto* album_ = playerctl_player_get_album(player, &error)) {
|
||||
if (auto* album_ = playerctl_player_get_album(last_active_player_, &error)) {
|
||||
spdlog::debug("mpris[{}]: album = {}", info.name, album_);
|
||||
info.album = album_;
|
||||
g_free(album_);
|
||||
}
|
||||
if (error) goto errorexit;
|
||||
|
||||
if (auto* title_ = playerctl_player_get_title(player, &error)) {
|
||||
if (auto* title_ = playerctl_player_get_title(last_active_player_, &error)) {
|
||||
spdlog::debug("mpris[{}]: title = {}", info.name, title_);
|
||||
info.title = title_;
|
||||
g_free(title_);
|
||||
}
|
||||
if (error) goto errorexit;
|
||||
|
||||
if (auto* length_ = playerctl_player_print_metadata_prop(player, "mpris:length", &error)) {
|
||||
if (auto* length_ =
|
||||
playerctl_player_print_metadata_prop(last_active_player_, "mpris:length", &error)) {
|
||||
spdlog::debug("mpris[{}]: mpris:length = {}", info.name, length_);
|
||||
auto len = std::chrono::microseconds(std::strtol(length_, nullptr, 10));
|
||||
auto len_h = std::chrono::duration_cast<std::chrono::hours>(len);
|
||||
@@ -557,7 +605,7 @@ auto Mpris::getPlayerInfo() -> std::optional<PlayerInfo> {
|
||||
if (error) goto errorexit;
|
||||
|
||||
{
|
||||
auto position_ = playerctl_player_get_position(player, &error);
|
||||
auto position_ = playerctl_player_get_position(last_active_player_, &error);
|
||||
if (error) {
|
||||
// it's fine to have an error here because not all players report a position
|
||||
g_error_free(error);
|
||||
@@ -609,12 +657,13 @@ bool Mpris::handleToggle(GdkEventButton* const& e) {
|
||||
});
|
||||
|
||||
// Command pattern: encapsulate each button's action
|
||||
auto* target = last_active_player_ ? last_active_player_ : player;
|
||||
const ButtonAction actions[] = {
|
||||
{1, "on-click", [&]() { playerctl_player_play_pause(player, &error); }},
|
||||
{2, "on-click-middle", [&]() { playerctl_player_previous(player, &error); }},
|
||||
{3, "on-click-right", [&]() { playerctl_player_next(player, &error); }},
|
||||
{8, "on-click-backward", [&]() { playerctl_player_previous(player, &error); }},
|
||||
{9, "on-click-forward", [&]() { playerctl_player_next(player, &error); }},
|
||||
{1, "on-click", [&]() { playerctl_player_play_pause(target, &error); }},
|
||||
{2, "on-click-middle", [&]() { playerctl_player_previous(target, &error); }},
|
||||
{3, "on-click-right", [&]() { playerctl_player_next(target, &error); }},
|
||||
{8, "on-click-backward", [&]() { playerctl_player_previous(target, &error); }},
|
||||
{9, "on-click-forward", [&]() { playerctl_player_next(target, &error); }},
|
||||
};
|
||||
|
||||
for (const auto& action : actions) {
|
||||
|
||||
+45
-19
@@ -45,7 +45,8 @@ waybar::modules::Network::readBandwidthUsage() {
|
||||
std::istringstream iss(line);
|
||||
|
||||
std::string ifacename;
|
||||
iss >> ifacename; // ifacename contains "eth0:"
|
||||
iss >> ifacename; // ifacename contains "eth0:"
|
||||
if (ifacename.empty()) continue;
|
||||
ifacename.pop_back(); // remove trailing ':'
|
||||
if (ifacename != ifname_) {
|
||||
continue;
|
||||
@@ -320,6 +321,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_;
|
||||
@@ -372,18 +374,26 @@ auto waybar::modules::Network::update() -> void {
|
||||
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")),
|
||||
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(), "b/s")),
|
||||
fmt::arg("bandwidthDownOctets", pow_format(bandwidth_down / interval_.count(), "o/s")),
|
||||
fmt::arg("bandwidthUpOctets", pow_format(bandwidth_up / interval_.count(), "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")),
|
||||
fmt::arg("bandwidthTotalBytes",
|
||||
pow_format((bandwidth_up + bandwidth_down) / interval_.count(), "B/s")));
|
||||
pow_format((bandwidth_up + bandwidth_down) * 8ull / (interval_.count() / 1000.0),
|
||||
"b/s")),
|
||||
fmt::arg("bandwidthDownOctets",
|
||||
pow_format(bandwidth_down / (interval_.count() / 1000.0), "o/s")),
|
||||
fmt::arg("bandwidthUpOctets",
|
||||
pow_format(bandwidth_up / (interval_.count() / 1000.0), "o/s")),
|
||||
fmt::arg(
|
||||
"bandwidthTotalOctets",
|
||||
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")),
|
||||
fmt::arg(
|
||||
"bandwidthTotalBytes",
|
||||
pow_format((bandwidth_up + bandwidth_down) / (interval_.count() / 1000.0), "B/s")));
|
||||
if (label_.get_tooltip_text() != tooltip_text) {
|
||||
label_.set_tooltip_markup(tooltip_text);
|
||||
}
|
||||
@@ -625,18 +635,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) {
|
||||
@@ -656,7 +679,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();
|
||||
|
||||
@@ -17,19 +17,21 @@
|
||||
#include "giomm/dataoutputstream.h"
|
||||
#include "giomm/unixinputstream.h"
|
||||
#include "giomm/unixoutputstream.h"
|
||||
#include "util/scoped_fd.hpp"
|
||||
|
||||
namespace waybar::modules::niri {
|
||||
|
||||
IPC::IPC() { startIPC(); }
|
||||
|
||||
int IPC::connectToSocket() {
|
||||
const char* socket_path = getenv("NIRI_SOCKET");
|
||||
|
||||
if (socket_path == nullptr) {
|
||||
spdlog::warn("Niri is not running, niri IPC will not be available.");
|
||||
return -1;
|
||||
throw std::runtime_error("Niri IPC: NIRI_SOCKET was not set! (Is Niri running?)");
|
||||
}
|
||||
|
||||
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");
|
||||
@@ -44,26 +46,18 @@ 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() {
|
||||
// will start IPC and relay events to parseIPC
|
||||
|
||||
std::thread([&]() {
|
||||
int socketfd;
|
||||
try {
|
||||
socketfd = connectToSocket();
|
||||
} catch (std::exception& e) {
|
||||
spdlog::error("Niri IPC: failed to start, reason: {}", e.what());
|
||||
return;
|
||||
}
|
||||
if (socketfd == -1) return;
|
||||
int socketfd = connectToSocket();
|
||||
|
||||
std::thread([this, socketfd]() {
|
||||
spdlog::info("Niri IPC starting");
|
||||
|
||||
auto unix_istream = Gio::UnixInputStream::create(socketfd, true);
|
||||
@@ -241,8 +235,7 @@ void IPC::unregisterForIPC(EventHandler* ev_handler) {
|
||||
}
|
||||
|
||||
Json::Value IPC::send(const Json::Value& request) {
|
||||
int socketfd = connectToSocket();
|
||||
if (socketfd == -1) throw std::runtime_error("Niri is not running");
|
||||
util::ScopedFd socketfd(connectToSocket());
|
||||
|
||||
auto unix_istream = Gio::UnixInputStream::create(socketfd, true);
|
||||
auto unix_ostream = Gio::UnixOutputStream::create(socketfd, false);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -174,11 +174,11 @@ std::string Workspaces::getIcon(const std::string& value, const Json::Value& ws)
|
||||
|
||||
if (ws["is_urgent"].asBool() && icons["urgent"]) return icons["urgent"].asString();
|
||||
|
||||
if (ws["active_window_id"].isNull() && icons["empty"]) return icons["empty"].asString();
|
||||
if (ws["is_active"].asBool() && icons["active"]) return icons["active"].asString();
|
||||
|
||||
if (ws["is_focused"].asBool() && icons["focused"]) return icons["focused"].asString();
|
||||
|
||||
if (ws["is_active"].asBool() && icons["active"]) return icons["active"].asString();
|
||||
if (ws["active_window_id"].isNull() && icons["empty"]) return icons["empty"].asString();
|
||||
|
||||
if (ws["name"]) {
|
||||
const auto& name = ws["name"].asString();
|
||||
|
||||
@@ -176,6 +176,10 @@ auto PowerProfilesDaemon::update() -> void {
|
||||
|
||||
bool PowerProfilesDaemon::handleToggle(GdkEventButton* const& e) {
|
||||
if (e->type == GdkEventType::GDK_BUTTON_PRESS && connected_) {
|
||||
if (availableProfiles_.empty()) return true;
|
||||
if (activeProfile_ == availableProfiles_.end()) {
|
||||
activeProfile_ = availableProfiles_.begin();
|
||||
}
|
||||
if (e->button == 1) /* left click */ {
|
||||
activeProfile_++;
|
||||
if (activeProfile_ == availableProfiles_.end()) {
|
||||
|
||||
@@ -118,6 +118,7 @@ Layout::Layout(const std::string& id, const waybar::Bar& bar, const Json::Value&
|
||||
|
||||
if (!seat_) {
|
||||
spdlog::error("wl_seat not advertised");
|
||||
return;
|
||||
}
|
||||
|
||||
label_.hide();
|
||||
|
||||
@@ -77,6 +77,7 @@ Mode::Mode(const std::string& id, const waybar::Bar& bar, const Json::Value& con
|
||||
|
||||
if (!seat_) {
|
||||
spdlog::error("wl_seat not advertised");
|
||||
return;
|
||||
}
|
||||
|
||||
label_.hide();
|
||||
|
||||
@@ -101,10 +101,12 @@ Tags::Tags(const std::string& id, const waybar::Bar& bar, const Json::Value& con
|
||||
|
||||
if (!control_) {
|
||||
spdlog::error("river_control_v1 not advertised");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!seat_) {
|
||||
spdlog::error("wl_seat not advertised");
|
||||
return;
|
||||
}
|
||||
|
||||
box_.set_name("tags");
|
||||
@@ -168,6 +170,7 @@ Tags::~Tags() {
|
||||
}
|
||||
|
||||
void Tags::handle_show() {
|
||||
if (!status_manager_) return;
|
||||
struct wl_output* output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj());
|
||||
output_status_ = zriver_status_manager_v1_get_river_output_status(status_manager_, output);
|
||||
zriver_output_status_v1_add_listener(output_status_, &output_status_listener_impl, this);
|
||||
|
||||
@@ -78,6 +78,7 @@ Window::Window(const std::string& id, const waybar::Bar& bar, const Json::Value&
|
||||
|
||||
if (!seat_) {
|
||||
spdlog::error("wl_seat not advertised");
|
||||
return;
|
||||
}
|
||||
|
||||
label_.hide(); // hide the label until populated
|
||||
|
||||
@@ -102,7 +102,9 @@ Sndio::~Sndio() { sioctl_close(hdl_); }
|
||||
|
||||
auto Sndio::update() -> void {
|
||||
auto format = format_;
|
||||
unsigned int vol = 100. * static_cast<double>(volume_) / static_cast<double>(maxval_);
|
||||
unsigned int vol = (maxval_ > 0) ? static_cast<unsigned int>(100. * static_cast<double>(volume_) /
|
||||
static_cast<double>(maxval_))
|
||||
: 0;
|
||||
|
||||
if (volume_ == 0) {
|
||||
label_.get_style_context()->add_class("muted");
|
||||
|
||||
+50
-10
@@ -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,12 +56,12 @@ 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) {
|
||||
GError* error = nullptr;
|
||||
waybar::util::ScopeGuard error_deleter([error]() {
|
||||
waybar::util::ScopeGuard error_deleter([&error]() {
|
||||
if (error != nullptr) {
|
||||
g_error_free(error);
|
||||
}
|
||||
@@ -81,7 +83,7 @@ void Host::proxyReady(GObject* src, GAsyncResult* res, gpointer data) {
|
||||
|
||||
void Host::registerHost(GObject* src, GAsyncResult* res, gpointer data) {
|
||||
GError* error = nullptr;
|
||||
waybar::util::ScopeGuard error_deleter([error]() {
|
||||
waybar::util::ScopeGuard error_deleter([&error]() {
|
||||
if (error != nullptr) {
|
||||
g_error_free(error);
|
||||
}
|
||||
@@ -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_));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+142
-48
@@ -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);
|
||||
}
|
||||
@@ -365,47 +395,36 @@ Glib::RefPtr<Gdk::Pixbuf> Item::extractPixBuf(GVariant* variant) {
|
||||
|
||||
void Item::updateImage() {
|
||||
auto pixbuf = getIconPixbuf();
|
||||
if (!pixbuf) return;
|
||||
auto scaled_icon_size = getScaledIconSize();
|
||||
|
||||
// If the loaded icon is not square, assume that the icon height should match the
|
||||
// requested icon size, but the width is allowed to be different. As such, if the
|
||||
// height of the image does not match the requested icon size, resize the icon such that
|
||||
// the aspect ratio is maintained, but the height matches the requested icon size.
|
||||
if (pixbuf->get_height() != scaled_icon_size) {
|
||||
if (pixbuf->get_height() > 0 && pixbuf->get_height() != scaled_icon_size) {
|
||||
int width = scaled_icon_size * pixbuf->get_width() / pixbuf->get_height();
|
||||
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;
|
||||
}
|
||||
@@ -420,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);
|
||||
@@ -464,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)});
|
||||
@@ -491,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:
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -31,7 +31,7 @@ Watcher::~Watcher() {
|
||||
|
||||
void Watcher::busAcquired(const Glib::RefPtr<Gio::DBus::Connection>& conn, Glib::ustring name) {
|
||||
GError* error = nullptr;
|
||||
waybar::util::ScopeGuard error_deleter([error]() {
|
||||
waybar::util::ScopeGuard error_deleter([&error]() {
|
||||
if (error) {
|
||||
g_error_free(error);
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -77,9 +77,9 @@ void SystemdFailedUnits::RequestSystemState() {
|
||||
Glib::VariantContainerBase data = proxy->call_sync("Get", parameters);
|
||||
if (data && data.is_of_type(Glib::VariantType("(v)"))) {
|
||||
Glib::VariantBase variant;
|
||||
g_variant_get(data.gobj_copy(), "(v)", &variant);
|
||||
g_variant_get(const_cast<GVariant*>(data.gobj()), "(v)", &variant);
|
||||
if (variant && variant.is_of_type(Glib::VARIANT_TYPE_STRING)) {
|
||||
return g_variant_get_string(variant.gobj_copy(), NULL);
|
||||
return g_variant_get_string(const_cast<GVariant*>(variant.gobj()), NULL);
|
||||
}
|
||||
}
|
||||
} catch (Glib::Error& e) {
|
||||
@@ -105,9 +105,9 @@ void SystemdFailedUnits::RequestFailedUnits() {
|
||||
Glib::VariantContainerBase data = proxy->call_sync("Get", parameters);
|
||||
if (data && data.is_of_type(Glib::VariantType("(v)"))) {
|
||||
Glib::VariantBase variant;
|
||||
g_variant_get(data.gobj_copy(), "(v)", &variant);
|
||||
g_variant_get(const_cast<GVariant*>(data.gobj()), "(v)", &variant);
|
||||
if (variant && variant.is_of_type(Glib::VARIANT_TYPE_UINT32)) {
|
||||
return g_variant_get_uint32(variant.gobj_copy());
|
||||
return g_variant_get_uint32(const_cast<GVariant*>(variant.gobj()));
|
||||
}
|
||||
}
|
||||
} catch (Glib::Error& e) {
|
||||
|
||||
+11
-7
@@ -59,10 +59,13 @@ UPower::UPower(const std::string& id, const Json::Value& config)
|
||||
sigc::mem_fun(*this, &UPower::getConn_cb));
|
||||
|
||||
// Make UPower client
|
||||
GError** gErr = NULL;
|
||||
upClient_ = up_client_new_full(NULL, gErr);
|
||||
if (upClient_ == NULL)
|
||||
spdlog::error("Upower. UPower client connection error. {}", (*gErr)->message);
|
||||
GError* gErr = NULL;
|
||||
upClient_ = up_client_new_full(NULL, &gErr);
|
||||
if (upClient_ == NULL) {
|
||||
spdlog::error("Upower. UPower client connection error. {}",
|
||||
gErr ? gErr->message : "unknown error");
|
||||
if (gErr) g_error_free(gErr);
|
||||
}
|
||||
|
||||
// Subscribe UPower events
|
||||
g_signal_connect(upClient_, "device-added", G_CALLBACK(deviceAdded_cb), this);
|
||||
@@ -92,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:
|
||||
@@ -109,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";
|
||||
@@ -209,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_) {
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <bit>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <exception>
|
||||
#include <ranges>
|
||||
#include <thread>
|
||||
@@ -26,13 +27,19 @@ 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;) i += read(sock.fd, &buf[i], n - i);
|
||||
for (size_t i = 0; i < n;) {
|
||||
auto r = read(sock, &buf[i], n - i);
|
||||
if (r <= 0) {
|
||||
throw std::runtime_error("Wayfire IPC: read failed");
|
||||
}
|
||||
i += static_cast<size_t>(r);
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
@@ -53,6 +60,9 @@ auto State::Wset::locate_ws(const Json::Value& geo) -> Workspace& {
|
||||
}
|
||||
|
||||
auto State::Wset::locate_ws(const Json::Value& geo) const -> const Workspace& {
|
||||
if (!output.has_value()) {
|
||||
throw std::runtime_error("Wayfire IPC: wset has no output assigned");
|
||||
}
|
||||
const auto& out = output.value().get();
|
||||
auto [qx, rx] = std::div(geo["x"].asInt(), out.w);
|
||||
auto [qy, ry] = std::div(geo["y"].asInt(), out.h);
|
||||
@@ -88,7 +98,10 @@ auto State::update_view(const Json::Value& view) -> void {
|
||||
|
||||
auto IPC::get_instance() -> std::shared_ptr<IPC> {
|
||||
auto p = instance.lock();
|
||||
if (!p) instance = p = std::shared_ptr<IPC>(new IPC);
|
||||
if (!p) {
|
||||
instance = p = std::shared_ptr<IPC>(new IPC);
|
||||
p->start();
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
@@ -98,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"};
|
||||
}
|
||||
@@ -108,15 +121,16 @@ 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 {
|
||||
auto len = *reinterpret_cast<uint32_t*>(read_exact(sock, 4).data());
|
||||
auto len_buf = read_exact(sock, 4);
|
||||
uint32_t len;
|
||||
std::memcpy(&len, len_buf.data(), sizeof(len));
|
||||
if constexpr (std::endian::native != std::endian::little) len = byteswap(len);
|
||||
auto buf = read_exact(sock, len);
|
||||
|
||||
@@ -153,15 +167,15 @@ auto IPC::start() -> void {
|
||||
send("window-rules/get-focused-view", {});
|
||||
send("window-rules/get-focused-output", {});
|
||||
|
||||
std::thread([&] {
|
||||
std::thread([self = shared_from_this()] {
|
||||
auto sock = connect();
|
||||
|
||||
{
|
||||
Json::Value json;
|
||||
json["method"] = "window-rules/events/watch";
|
||||
|
||||
pack_and_write(sock, Json::writeString(writer_builder, json));
|
||||
if (receive(sock)["result"] != "ok") {
|
||||
pack_and_write(sock, Json::writeString(self->writer_builder, json));
|
||||
if (self->receive(sock)["result"] != "ok") {
|
||||
spdlog::error(
|
||||
"Wayfire IPC: method \"window-rules/events/watch\""
|
||||
" have failed");
|
||||
@@ -169,10 +183,10 @@ auto IPC::start() -> void {
|
||||
}
|
||||
}
|
||||
|
||||
while (auto json = receive(sock)) {
|
||||
while (auto json = self->receive(sock)) {
|
||||
auto ev = json["event"].asString();
|
||||
spdlog::debug("Wayfire IPC: received event \"{}\"", ev);
|
||||
root_event_handler(ev, json);
|
||||
self->root_event_handler(ev, json);
|
||||
}
|
||||
}).detach();
|
||||
}
|
||||
|
||||
@@ -34,8 +34,12 @@ auto Window::update() -> void {
|
||||
auto Window::update_icon_label() -> void {
|
||||
auto _ = ipc->lock_state();
|
||||
|
||||
const auto& output = ipc->get_outputs().at(bar_.output->name);
|
||||
const auto& wset = ipc->get_wsets().at(output.wset_idx);
|
||||
auto out_it = ipc->get_outputs().find(bar_.output->name);
|
||||
if (out_it == ipc->get_outputs().end()) return;
|
||||
const auto& output = out_it->second;
|
||||
auto wset_it = ipc->get_wsets().find(output.wset_idx);
|
||||
if (wset_it == ipc->get_wsets().end()) return;
|
||||
const auto& wset = wset_it->second;
|
||||
const auto& views = ipc->get_views();
|
||||
auto ctx = bar_.window.get_style_context();
|
||||
|
||||
|
||||
@@ -70,8 +70,12 @@ auto Workspaces::handleScroll(GdkEventScroll* e) -> bool {
|
||||
Json::Value data;
|
||||
{
|
||||
auto _ = ipc->lock_state();
|
||||
const auto& output = ipc->get_outputs().at(bar_.output->name);
|
||||
const auto& wset = ipc->get_wsets().at(output.wset_idx);
|
||||
auto out_it = ipc->get_outputs().find(bar_.output->name);
|
||||
if (out_it == ipc->get_outputs().end()) return true;
|
||||
const auto& output = out_it->second;
|
||||
auto wset_it = ipc->get_wsets().find(output.wset_idx);
|
||||
if (wset_it == ipc->get_wsets().end()) return true;
|
||||
const auto& wset = wset_it->second;
|
||||
auto n = wset.ws_w * wset.ws_h;
|
||||
auto i = (wset.ws_idx() + delta + n) % n;
|
||||
data["x"] = Json::Value((uint64_t)i % wset.ws_w);
|
||||
@@ -92,8 +96,12 @@ auto Workspaces::update_box() -> void {
|
||||
auto _ = ipc->lock_state();
|
||||
|
||||
const auto& output_name = bar_.output->name;
|
||||
const auto& output = ipc->get_outputs().at(output_name);
|
||||
const auto& wset = ipc->get_wsets().at(output.wset_idx);
|
||||
auto out_it = ipc->get_outputs().find(output_name);
|
||||
if (out_it == ipc->get_outputs().end()) return;
|
||||
const auto& output = out_it->second;
|
||||
auto wset_it = ipc->get_wsets().find(output.wset_idx);
|
||||
if (wset_it == ipc->get_wsets().end()) return;
|
||||
const auto& wset = wset_it->second;
|
||||
|
||||
auto output_focused = ipc->get_focused_output_name() == output_name;
|
||||
auto ws_w = wset.ws_w;
|
||||
|
||||
@@ -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_);
|
||||
@@ -81,7 +90,7 @@ void waybar::modules::Wireplumber::updateNodeName(waybar::modules::Wireplumber*
|
||||
if (proxy == nullptr) {
|
||||
auto err = fmt::format("Object '{}' not found\n", id);
|
||||
spdlog::error("[{}]: {}", self->name_, err);
|
||||
throw std::runtime_error(err);
|
||||
return;
|
||||
}
|
||||
|
||||
g_autoptr(WpProperties) properties =
|
||||
@@ -153,7 +162,7 @@ void waybar::modules::Wireplumber::updateVolume(waybar::modules::Wireplumber* se
|
||||
if (variant == nullptr) {
|
||||
auto err = fmt::format("Node {} does not support volume\n", id);
|
||||
spdlog::error("[{}]: {}", self->name_, err);
|
||||
throw std::runtime_error(err);
|
||||
return;
|
||||
}
|
||||
|
||||
g_variant_lookup(variant, "volume", "d", &self->volume_);
|
||||
@@ -287,14 +296,14 @@ void waybar::modules::Wireplumber::onObjectManagerInstalled(waybar::modules::Wir
|
||||
|
||||
if (self->def_nodes_api_ == nullptr) {
|
||||
spdlog::error("[{}]: default nodes api is not loaded.", self->name_);
|
||||
throw std::runtime_error("Default nodes API is not loaded\n");
|
||||
return;
|
||||
}
|
||||
|
||||
self->mixer_api_ = wp_plugin_find(self->wp_core_, "mixer-api");
|
||||
|
||||
if (self->mixer_api_ == nullptr) {
|
||||
spdlog::error("[{}]: mixer api is not loaded.", self->name_);
|
||||
throw std::runtime_error("Mixer api is not loaded\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Get default sink
|
||||
@@ -336,7 +345,7 @@ void waybar::modules::Wireplumber::onPluginActivated(WpObject* p, GAsyncResult*
|
||||
|
||||
if (wp_object_activate_finish(p, res, &error) == 0) {
|
||||
spdlog::error("[{}]: error activating plugin: {}", self->name_, error->message);
|
||||
throw std::runtime_error(error->message);
|
||||
return;
|
||||
}
|
||||
|
||||
if (--self->pending_plugins_ == 0) {
|
||||
@@ -373,7 +382,7 @@ void waybar::modules::Wireplumber::onDefaultNodesApiLoaded(WpObject* p, GAsyncRe
|
||||
|
||||
if (success == FALSE) {
|
||||
spdlog::error("[{}]: default nodes API load failed", self->name_);
|
||||
throw std::runtime_error(error->message);
|
||||
return;
|
||||
}
|
||||
spdlog::debug("[{}]: loaded default nodes api", self->name_);
|
||||
g_ptr_array_add(self->apis_, wp_plugin_find(self->wp_core_, "default-nodes-api"));
|
||||
@@ -392,7 +401,7 @@ void waybar::modules::Wireplumber::onMixerApiLoaded(WpObject* p, GAsyncResult* r
|
||||
|
||||
if (success == FALSE) {
|
||||
spdlog::error("[{}]: mixer API load failed", self->name_);
|
||||
throw std::runtime_error(error->message);
|
||||
return;
|
||||
}
|
||||
|
||||
spdlog::debug("[{}]: loaded mixer API", self->name_);
|
||||
@@ -524,9 +533,11 @@ bool waybar::modules::Wireplumber::handleScroll(GdkEventScroll* e) {
|
||||
}
|
||||
}
|
||||
if (newVol != volume_) {
|
||||
if (mixer_api_ == nullptr) return true;
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -437,7 +437,9 @@ void Task::handle_drag_data_get(const Glib::RefPtr<Gdk::DragContext>& context,
|
||||
void Task::handle_drag_data_received(const Glib::RefPtr<Gdk::DragContext>& context, int x, int y,
|
||||
Gtk::SelectionData selection_data, guint info, guint time) {
|
||||
spdlog::debug("drag_data_received");
|
||||
gpointer handle = *(gpointer*)selection_data.get_data();
|
||||
auto* raw = selection_data.get_data();
|
||||
if (!raw || selection_data.get_length() < static_cast<int>(sizeof(gpointer))) return;
|
||||
gpointer handle = *(gpointer*)raw;
|
||||
auto dragged_button = (Gtk::Button*)handle;
|
||||
|
||||
if (dragged_button == &this->button) return;
|
||||
|
||||
+28
-10
@@ -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;
|
||||
@@ -237,9 +250,10 @@ void AudioBackend::sourceInfoCb(pa_context* /*context*/, const pa_source_info* i
|
||||
*/
|
||||
void AudioBackend::serverInfoCb(pa_context* context, const pa_server_info* i, void* data) {
|
||||
auto* backend = static_cast<AudioBackend*>(data);
|
||||
backend->current_sink_name_ = i->default_sink_name;
|
||||
backend->default_sink_name = i->default_sink_name;
|
||||
backend->default_source_name_ = i->default_source_name;
|
||||
if (i == nullptr) return;
|
||||
backend->current_sink_name_ = i->default_sink_name ? i->default_sink_name : "";
|
||||
backend->default_sink_name = i->default_sink_name ? i->default_sink_name : "";
|
||||
backend->default_source_name_ = i->default_source_name ? i->default_source_name : "";
|
||||
|
||||
pa_context_get_sink_info_list(context, sinkInfoCb, data);
|
||||
pa_context_get_source_info_list(context, sourceInfoCb, data);
|
||||
@@ -355,6 +369,7 @@ void AudioBackend::changeVolume(ChangeType change_type, double step, uint16_t ma
|
||||
}
|
||||
|
||||
void AudioBackend::toggleSinkMute() {
|
||||
if (context_ == nullptr || pa_context_get_state(context_) != PA_CONTEXT_READY) return;
|
||||
muted_ = !muted_;
|
||||
pa_threaded_mainloop_lock(mainloop_);
|
||||
pa_context_set_sink_mute_by_index(context_, sink_idx_, static_cast<int>(muted_), nullptr,
|
||||
@@ -363,6 +378,7 @@ void AudioBackend::toggleSinkMute() {
|
||||
}
|
||||
|
||||
void AudioBackend::toggleSinkMute(bool mute) {
|
||||
if (context_ == nullptr || pa_context_get_state(context_) != PA_CONTEXT_READY) return;
|
||||
muted_ = mute;
|
||||
pa_threaded_mainloop_lock(mainloop_);
|
||||
pa_context_set_sink_mute_by_index(context_, sink_idx_, static_cast<int>(muted_), nullptr,
|
||||
@@ -371,7 +387,8 @@ void AudioBackend::toggleSinkMute(bool mute) {
|
||||
}
|
||||
|
||||
void AudioBackend::toggleSourceMute() {
|
||||
source_muted_ = !muted_;
|
||||
if (context_ == nullptr || pa_context_get_state(context_) != PA_CONTEXT_READY) return;
|
||||
source_muted_ = !source_muted_;
|
||||
pa_threaded_mainloop_lock(mainloop_);
|
||||
pa_context_set_source_mute_by_index(context_, source_idx_, static_cast<int>(source_muted_),
|
||||
nullptr, nullptr);
|
||||
@@ -379,6 +396,7 @@ void AudioBackend::toggleSourceMute() {
|
||||
}
|
||||
|
||||
void AudioBackend::toggleSourceMute(bool mute) {
|
||||
if (context_ == nullptr || pa_context_get_state(context_) != PA_CONTEXT_READY) return;
|
||||
source_muted_ = mute;
|
||||
pa_threaded_mainloop_lock(mainloop_);
|
||||
pa_context_set_source_mute_by_index(context_, source_idx_, static_cast<int>(source_muted_),
|
||||
|
||||
@@ -79,18 +79,38 @@ static void upsert_device(std::vector<BacklightDevice>& devices, udev_device* de
|
||||
});
|
||||
if (found != devices.end()) {
|
||||
if (actual != nullptr) {
|
||||
found->set_actual(std::stoi(actual));
|
||||
try {
|
||||
found->set_actual(std::stoi(actual));
|
||||
} catch (const std::exception&) {
|
||||
}
|
||||
}
|
||||
if (max != nullptr) {
|
||||
found->set_max(std::stoi(max));
|
||||
try {
|
||||
found->set_max(std::stoi(max));
|
||||
} catch (const std::exception&) {
|
||||
}
|
||||
}
|
||||
if (power != nullptr) {
|
||||
found->set_powered(std::stoi(power) == 0);
|
||||
try {
|
||||
found->set_powered(std::stoi(power) == 0);
|
||||
} catch (const std::exception&) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const int actual_int = actual == nullptr ? 0 : std::stoi(actual);
|
||||
const int max_int = max == nullptr ? 0 : std::stoi(max);
|
||||
const bool power_bool = power == nullptr ? true : std::stoi(power) == 0;
|
||||
int actual_int = 0, max_int = 0;
|
||||
bool power_bool = true;
|
||||
try {
|
||||
if (actual != nullptr) actual_int = std::stoi(actual);
|
||||
} catch (const std::exception&) {
|
||||
}
|
||||
try {
|
||||
if (max != nullptr) max_int = std::stoi(max);
|
||||
} catch (const std::exception&) {
|
||||
}
|
||||
try {
|
||||
if (power != nullptr) power_bool = std::stoi(power) == 0;
|
||||
} catch (const std::exception&) {
|
||||
}
|
||||
devices.emplace_back(name, actual_int, max_int, power_bool);
|
||||
}
|
||||
}
|
||||
@@ -261,6 +281,11 @@ void BacklightBackend::set_brightness(const std::string& preferred_device, Chang
|
||||
|
||||
void BacklightBackend::set_brightness_internal(const std::string& device_name, int brightness,
|
||||
int max_brightness) {
|
||||
if (!login_proxy_) {
|
||||
spdlog::error("Login proxy not available, cannot set brightness");
|
||||
return;
|
||||
}
|
||||
|
||||
brightness = std::clamp(brightness, 0, max_brightness);
|
||||
|
||||
auto call_args = Glib::VariantContainerBase(
|
||||
@@ -273,6 +298,7 @@ int BacklightBackend::get_scaled_brightness(const std::string& preferred_device)
|
||||
GET_BEST_DEVICE(best, (*this), preferred_device);
|
||||
|
||||
if (best != nullptr) {
|
||||
if (best->get_max() == 0) return 0;
|
||||
return static_cast<int>(std::round(best->get_actual() * 100.0F / best->get_max()));
|
||||
}
|
||||
|
||||
|
||||
@@ -115,11 +115,15 @@ std::vector<std::string> waybar::CssReloadHelper::parseImports(const std::string
|
||||
auto maxIterations = 100U;
|
||||
do {
|
||||
previousSize = imports.size();
|
||||
std::vector<std::string> to_parse;
|
||||
for (const auto& [file, parsed] : imports) {
|
||||
if (!parsed) {
|
||||
parseImports(file, imports);
|
||||
to_parse.push_back(file);
|
||||
}
|
||||
}
|
||||
for (const auto& file : to_parse) {
|
||||
parseImports(file, imports);
|
||||
}
|
||||
|
||||
} while (imports.size() > previousSize && maxIterations-- > 0);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -5,8 +5,11 @@
|
||||
std::vector<std::string> IconLoader::search_prefix() {
|
||||
std::vector<std::string> prefixes = {""};
|
||||
|
||||
std::string home_dir = std::getenv("HOME");
|
||||
prefixes.push_back(home_dir + "/.local/share/");
|
||||
const char* home_env = std::getenv("HOME");
|
||||
std::string home_dir = home_env ? home_env : "";
|
||||
if (!home_dir.empty()) {
|
||||
prefixes.push_back(home_dir + "/.local/share/");
|
||||
}
|
||||
|
||||
auto xdg_data_dirs = std::getenv("XDG_DATA_DIRS");
|
||||
if (!xdg_data_dirs) {
|
||||
@@ -139,7 +142,7 @@ bool IconLoader::image_load_icon(Gtk::Image& image, const Glib::RefPtr<Gtk::Icon
|
||||
}
|
||||
|
||||
if (pixbuf) {
|
||||
if (pixbuf->get_width() != scaled_icon_size) {
|
||||
if (pixbuf->get_width() != scaled_icon_size && pixbuf->get_height() > 0) {
|
||||
int width = scaled_icon_size * pixbuf->get_width() / pixbuf->get_height();
|
||||
pixbuf = pixbuf->scale_simple(width, scaled_icon_size, Gdk::InterpType::INTERP_BILINEAR);
|
||||
}
|
||||
|
||||
@@ -54,11 +54,17 @@ PipewireBackend::PipewireBackend(PrivateConstructorTag tag)
|
||||
context_ = pw_context_new(pw_thread_loop_get_loop(mainloop_), nullptr, 0);
|
||||
if (context_ == nullptr) {
|
||||
pw_thread_loop_unlock(mainloop_);
|
||||
pw_thread_loop_destroy(mainloop_);
|
||||
mainloop_ = nullptr;
|
||||
throw std::runtime_error("pa_context_new() failed.");
|
||||
}
|
||||
core_ = pw_context_connect(context_, nullptr, 0);
|
||||
if (core_ == nullptr) {
|
||||
pw_thread_loop_unlock(mainloop_);
|
||||
pw_context_destroy(context_);
|
||||
context_ = nullptr;
|
||||
pw_thread_loop_destroy(mainloop_);
|
||||
mainloop_ = nullptr;
|
||||
throw std::runtime_error("pw_context_connect() failed");
|
||||
}
|
||||
registry_ = pw_core_get_registry(core_, PW_VERSION_REGISTRY, 0);
|
||||
@@ -136,7 +142,10 @@ void PipewireBackend::handleRegistryEventGlobal(uint32_t id, uint32_t permission
|
||||
|
||||
pw_proxy_add_object_listener(proxy, &pNodeInfo->object_listener, &NODE_EVENTS, pNodeInfo);
|
||||
|
||||
privacy_nodes.insert_or_assign(id, pNodeInfo);
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
privacy_nodes.insert_or_assign(id, pNodeInfo);
|
||||
}
|
||||
}
|
||||
|
||||
void PipewireBackend::handleRegistryEventGlobalRemove(uint32_t id) {
|
||||
|
||||
+19
-13
@@ -58,21 +58,27 @@ void waybar::Portal::refreshAppearance() {
|
||||
// xdg-desktop-portal 1.17 will fix this issue with a new `ReadOne` method,
|
||||
// but this version is not yet released.
|
||||
// TODO(xdg-desktop-portal v1.17): switch to ReadOne
|
||||
auto container = Glib::VariantBase::cast_dynamic<Glib::VariantContainerBase>(response);
|
||||
Glib::VariantBase modev;
|
||||
container.get_child(modev, 0);
|
||||
auto mode =
|
||||
Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::Variant<Glib::Variant<uint32_t>>>>(modev)
|
||||
.get()
|
||||
.get()
|
||||
.get();
|
||||
auto newMode = Appearance(mode);
|
||||
if (newMode == currentMode) {
|
||||
try {
|
||||
auto container = Glib::VariantBase::cast_dynamic<Glib::VariantContainerBase>(response);
|
||||
Glib::VariantBase modev;
|
||||
container.get_child(modev, 0);
|
||||
auto mode =
|
||||
Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::Variant<Glib::Variant<uint32_t>>>>(
|
||||
modev)
|
||||
.get()
|
||||
.get()
|
||||
.get();
|
||||
auto newMode = Appearance(mode);
|
||||
if (newMode == currentMode) {
|
||||
return;
|
||||
}
|
||||
spdlog::info("Discovered appearance '{}'", newMode);
|
||||
currentMode = newMode;
|
||||
m_signal_appearance_changed.emit(currentMode);
|
||||
} catch (const std::bad_cast& e) {
|
||||
spdlog::error("Unexpected appearance variant format: {}", e.what());
|
||||
return;
|
||||
}
|
||||
spdlog::info("Discovered appearance '{}'", newMode);
|
||||
currentMode = newMode;
|
||||
m_signal_appearance_changed.emit(currentMode);
|
||||
}
|
||||
|
||||
waybar::Appearance waybar::Portal::getAppearance() { return currentMode; };
|
||||
|
||||
+87
-11
@@ -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
|
||||
|
||||
@@ -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";
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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',
|
||||
)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user