Merge branch 'master' into feature/workspace-name-css-class
This commit is contained in:
@@ -4,6 +4,8 @@
|
|||||||
#include <gtkmm/label.h>
|
#include <gtkmm/label.h>
|
||||||
#include <json/json.h>
|
#include <json/json.h>
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
#include "AModule.hpp"
|
#include "AModule.hpp"
|
||||||
|
|
||||||
namespace waybar {
|
namespace waybar {
|
||||||
@@ -25,12 +27,19 @@ class ALabel : public AModule {
|
|||||||
bool alt_ = false;
|
bool alt_ = false;
|
||||||
std::string default_format_;
|
std::string default_format_;
|
||||||
|
|
||||||
|
bool setLabelMarkup(const Glib::ustring& markup);
|
||||||
|
bool setTooltipMarkup(const Glib::ustring& markup);
|
||||||
|
|
||||||
bool handleToggle(GdkEventButton* const& e) override;
|
bool handleToggle(GdkEventButton* const& e) override;
|
||||||
virtual std::string getState(uint8_t value, bool lesser = false);
|
virtual std::string getState(uint8_t value, bool lesser = false);
|
||||||
|
|
||||||
std::map<std::string, GtkMenuItem*> submenus_;
|
std::map<std::string, GtkMenuItem*> submenus_;
|
||||||
std::map<std::string, std::string> menuActionsMap_;
|
std::map<std::string, std::string> menuActionsMap_;
|
||||||
static void handleGtkMenuEvent(GtkMenuItem* menuitem, gpointer data);
|
static void handleGtkMenuEvent(GtkMenuItem* menuitem, gpointer data);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::optional<Glib::ustring> last_label_markup_;
|
||||||
|
std::optional<Glib::ustring> last_tooltip_markup_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace waybar
|
} // namespace waybar
|
||||||
|
|||||||
@@ -25,6 +25,10 @@ class AModule : public IModule {
|
|||||||
|
|
||||||
bool expandEnabled() const;
|
bool expandEnabled() const;
|
||||||
|
|
||||||
|
virtual void suspend() {};
|
||||||
|
virtual void resume() {};
|
||||||
|
bool shouldSuspend() const { return disable_on_sleep_; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// Don't need to make an object directly
|
// Don't need to make an object directly
|
||||||
// Derived classes are able to use it
|
// Derived classes are able to use it
|
||||||
@@ -48,6 +52,8 @@ class AModule : public IModule {
|
|||||||
virtual bool handleMouseLeave(GdkEventCrossing* const& ev);
|
virtual bool handleMouseLeave(GdkEventCrossing* const& ev);
|
||||||
virtual bool handleScroll(GdkEventScroll*);
|
virtual bool handleScroll(GdkEventScroll*);
|
||||||
virtual bool handleRelease(GdkEventButton* const& ev);
|
virtual bool handleRelease(GdkEventButton* const& ev);
|
||||||
|
|
||||||
|
bool disable_on_sleep_{false};
|
||||||
GObject* menu_ = nullptr;
|
GObject* menu_ = nullptr;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -57,6 +63,7 @@ class AModule : public IModule {
|
|||||||
bool hasUserEvents_;
|
bool hasUserEvents_;
|
||||||
gdouble distance_scrolled_y_;
|
gdouble distance_scrolled_y_;
|
||||||
gdouble distance_scrolled_x_;
|
gdouble distance_scrolled_x_;
|
||||||
|
sigc::connection cursor_timeout_conn_;
|
||||||
std::map<std::string, std::string> eventActionMap_;
|
std::map<std::string, std::string> eventActionMap_;
|
||||||
static const inline std::map<std::pair<uint, GdkEventType>, std::string> eventMap_{
|
static const inline std::map<std::pair<uint, GdkEventType>, std::string> eventMap_{
|
||||||
{std::make_pair(1, GdkEventType::GDK_BUTTON_PRESS), "on-click"},
|
{std::make_pair(1, GdkEventType::GDK_BUTTON_PRESS), "on-click"},
|
||||||
|
|||||||
@@ -75,6 +75,8 @@ class Bar : public sigc::trackable {
|
|||||||
util::KillSignalAction getOnSigusr1Action();
|
util::KillSignalAction getOnSigusr1Action();
|
||||||
util::KillSignalAction getOnSigusr2Action();
|
util::KillSignalAction getOnSigusr2Action();
|
||||||
|
|
||||||
|
void toggleSuspend(bool suspend);
|
||||||
|
|
||||||
struct waybar_output* output;
|
struct waybar_output* output;
|
||||||
Json::Value config;
|
Json::Value config;
|
||||||
struct wl_surface* surface;
|
struct wl_surface* surface;
|
||||||
@@ -99,6 +101,7 @@ class Bar : public sigc::trackable {
|
|||||||
void setMode(const bar_mode&);
|
void setMode(const bar_mode&);
|
||||||
void setPassThrough(bool passthrough);
|
void setPassThrough(bool passthrough);
|
||||||
void setPosition(Gtk::PositionType position);
|
void setPosition(Gtk::PositionType position);
|
||||||
|
void forceLayerCommit();
|
||||||
void onConfigure(GdkEventConfigure* ev);
|
void onConfigure(GdkEventConfigure* ev);
|
||||||
void configureGlobalOffset(int width, int height);
|
void configureGlobalOffset(int width, int height);
|
||||||
void onOutputGeometryChanged();
|
void onOutputGeometryChanged();
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ class Client {
|
|||||||
const char* interface, uint32_t version);
|
const char* interface, uint32_t version);
|
||||||
static void handleGlobalRemove(void* data, struct wl_registry* registry, uint32_t name);
|
static void handleGlobalRemove(void* data, struct wl_registry* registry, uint32_t name);
|
||||||
static void handleOutputDone(void*, struct zxdg_output_v1*);
|
static void handleOutputDone(void*, struct zxdg_output_v1*);
|
||||||
|
void createBarsBatch();
|
||||||
static void handleOutputName(void*, struct zxdg_output_v1*, const char*);
|
static void handleOutputName(void*, struct zxdg_output_v1*, const char*);
|
||||||
static void handleOutputDescription(void*, struct zxdg_output_v1*, const char*);
|
static void handleOutputDescription(void*, struct zxdg_output_v1*, const char*);
|
||||||
void handleMonitorAdded(Glib::RefPtr<Gdk::Monitor> monitor);
|
void handleMonitorAdded(Glib::RefPtr<Gdk::Monitor> monitor);
|
||||||
@@ -58,6 +59,8 @@ class Client {
|
|||||||
std::string m_cssFile;
|
std::string m_cssFile;
|
||||||
sigc::connection monitor_added_connection_;
|
sigc::connection monitor_added_connection_;
|
||||||
sigc::connection monitor_removed_connection_;
|
sigc::connection monitor_removed_connection_;
|
||||||
|
std::list<waybar_output*> pending_outputs_;
|
||||||
|
bool bars_scheduled_ = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace waybar
|
} // namespace waybar
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <list>
|
#include <list>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
@@ -35,9 +36,22 @@ class IPC {
|
|||||||
Json::Value getSocket1JsonReply(const std::string& rq);
|
Json::Value getSocket1JsonReply(const std::string& rq);
|
||||||
static std::filesystem::path getSocketFolder(const char* instanceSig);
|
static std::filesystem::path getSocketFolder(const char* instanceSig);
|
||||||
|
|
||||||
|
/// Dispatch a Hyprland command. Automatically uses the correct protocol
|
||||||
|
/// (legacy text or Lua-based) depending on the running Hyprland version.
|
||||||
|
static std::string dispatch(const std::string& dispatcher, const std::string& arg);
|
||||||
|
|
||||||
|
/// Build a Lua-format dispatch command string.
|
||||||
|
static std::string buildLuaDispatch(const std::string& dispatcher, const std::string& arg);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
static std::filesystem::path socketFolder_;
|
static std::filesystem::path socketFolder_;
|
||||||
|
|
||||||
|
/// Detect whether the running Hyprland uses the Lua-based IPC protocol.
|
||||||
|
/// Returns true for Hyprland >= 0.54 (Lua config), false for older versions.
|
||||||
|
static bool isLuaProtocol();
|
||||||
|
|
||||||
|
static std::optional<bool> s_luaProtocolDetected_; // cached detection result
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void socketListener();
|
void socketListener();
|
||||||
void parseIPC(const std::string&);
|
void parseIPC(const std::string&);
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ class Workspace {
|
|||||||
public:
|
public:
|
||||||
explicit Workspace(const Json::Value& workspace_data, Workspaces& workspace_manager,
|
explicit Workspace(const Json::Value& workspace_data, Workspaces& workspace_manager,
|
||||||
const Json::Value& clients_data = Json::Value::nullRef);
|
const Json::Value& clients_data = Json::Value::nullRef);
|
||||||
|
~Workspace();
|
||||||
std::string& selectIcon(std::map<std::string, std::string>& icons_map);
|
std::string& selectIcon(std::map<std::string, std::string>& icons_map);
|
||||||
Gtk::Button& button() { return m_button; };
|
Gtk::Button& button() { return m_button; };
|
||||||
|
|
||||||
@@ -45,6 +46,15 @@ class Workspace {
|
|||||||
bool isUrgent() const { return m_isUrgent; };
|
bool isUrgent() const { return m_isUrgent; };
|
||||||
|
|
||||||
bool handleClicked(GdkEventButton* bt) const;
|
bool handleClicked(GdkEventButton* bt) const;
|
||||||
|
|
||||||
|
bool handleEnter(GdkEventCrossing* event);
|
||||||
|
bool handleLeave(GdkEventCrossing* event);
|
||||||
|
|
||||||
|
void startHoverCheck();
|
||||||
|
void stopHoverCheck();
|
||||||
|
bool syncHoverClass();
|
||||||
|
bool pointerInsideButton();
|
||||||
|
|
||||||
void setActive(bool value = true) { m_isActive = value; };
|
void setActive(bool value = true) { m_isActive = value; };
|
||||||
void setPersistentRule(bool value = true) { m_isPersistentRule = value; };
|
void setPersistentRule(bool value = true) { m_isPersistentRule = value; };
|
||||||
void setPersistentConfig(bool value = true) { m_isPersistentConfig = value; };
|
void setPersistentConfig(bool value = true) { m_isPersistentConfig = value; };
|
||||||
@@ -81,6 +91,8 @@ class Workspace {
|
|||||||
bool m_isUrgent = false;
|
bool m_isUrgent = false;
|
||||||
bool m_isVisible = false;
|
bool m_isVisible = false;
|
||||||
|
|
||||||
|
sigc::connection m_hoverCheckConnection;
|
||||||
|
|
||||||
std::vector<WindowRepr> m_windowMap;
|
std::vector<WindowRepr> m_windowMap;
|
||||||
|
|
||||||
Gtk::Button m_button;
|
Gtk::Button m_button;
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ class Memory : public ALabel {
|
|||||||
std::unordered_map<std::string, unsigned long> meminfo_;
|
std::unordered_map<std::string, unsigned long> meminfo_;
|
||||||
|
|
||||||
util::SleeperThread thread_;
|
util::SleeperThread thread_;
|
||||||
|
|
||||||
|
std::string unit_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace waybar::modules
|
} // namespace waybar::modules
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ class MPD : public ALabel {
|
|||||||
detail::unique_status status_;
|
detail::unique_status status_;
|
||||||
mpd_state state_;
|
mpd_state state_;
|
||||||
detail::unique_song song_;
|
detail::unique_song song_;
|
||||||
|
std::string ellipsis_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
MPD(const std::string&, const Json::Value&);
|
MPD(const std::string&, const Json::Value&);
|
||||||
@@ -45,6 +46,10 @@ class MPD : public ALabel {
|
|||||||
void setLabel();
|
void setLabel();
|
||||||
std::string getStateIcon() const;
|
std::string getStateIcon() const;
|
||||||
std::string getOptionIcon(const std::string& optionName, bool activated) const;
|
std::string getOptionIcon(const std::string& optionName, bool activated) const;
|
||||||
|
std::string getArtistStr(bool truncated) const;
|
||||||
|
std::string getAlbumArtistStr(bool truncated) const;
|
||||||
|
std::string getAlbumStr(bool truncated) const;
|
||||||
|
std::string getTitleStr(bool truncated) const;
|
||||||
|
|
||||||
// GUI-side methods
|
// GUI-side methods
|
||||||
bool handlePlayPause(GdkEventButton* const&);
|
bool handlePlayPause(GdkEventButton* const&);
|
||||||
@@ -54,7 +59,6 @@ class MPD : public ALabel {
|
|||||||
void tryConnect();
|
void tryConnect();
|
||||||
void checkErrors(mpd_connection* conn);
|
void checkErrors(mpd_connection* conn);
|
||||||
void fetchState();
|
void fetchState();
|
||||||
void queryMPD();
|
|
||||||
|
|
||||||
inline bool stopped() const { return connection_ && state_ == MPD_STATE_STOP; }
|
inline bool stopped() const { return connection_ && state_ == MPD_STATE_STOP; }
|
||||||
inline bool playing() const { return connection_ && state_ == MPD_STATE_PLAY; }
|
inline bool playing() const { return connection_ && state_ == MPD_STATE_PLAY; }
|
||||||
|
|||||||
@@ -197,7 +197,6 @@ class Context {
|
|||||||
void tryConnect() const;
|
void tryConnect() const;
|
||||||
void checkErrors(mpd_connection*) const;
|
void checkErrors(mpd_connection*) const;
|
||||||
void do_update();
|
void do_update();
|
||||||
void queryMPD() const;
|
|
||||||
void fetchState() const;
|
void fetchState() const;
|
||||||
constexpr mpd_state state() const;
|
constexpr mpd_state state() const;
|
||||||
void emit() const;
|
void emit() const;
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
|
using namespace std::literals::chrono_literals;
|
||||||
|
|
||||||
inline bool Context::is_connected() const { return mpd_module_->connection_ != nullptr; }
|
inline bool Context::is_connected() const { return mpd_module_->connection_ != nullptr; }
|
||||||
inline bool Context::is_playing() const { return mpd_module_->playing(); }
|
inline bool Context::is_playing() const { return mpd_module_->playing(); }
|
||||||
inline bool Context::is_paused() const { return mpd_module_->paused(); }
|
inline bool Context::is_paused() const { return mpd_module_->paused(); }
|
||||||
inline bool Context::is_stopped() const { return mpd_module_->stopped(); }
|
inline bool Context::is_stopped() const { return mpd_module_->stopped(); }
|
||||||
|
|
||||||
constexpr inline std::size_t Context::interval() const { return mpd_module_->interval_.count(); }
|
constexpr inline std::size_t Context::interval() const { return mpd_module_->interval_ / 1s; }
|
||||||
inline void Context::tryConnect() const { mpd_module_->tryConnect(); }
|
inline void Context::tryConnect() const { mpd_module_->tryConnect(); }
|
||||||
inline unique_connection& Context::connection() { return mpd_module_->connection_; }
|
inline unique_connection& Context::connection() { return mpd_module_->connection_; }
|
||||||
constexpr inline mpd_state Context::state() const { return mpd_module_->state_; }
|
constexpr inline mpd_state Context::state() const { return mpd_module_->state_; }
|
||||||
@@ -15,7 +16,6 @@ constexpr inline mpd_state Context::state() const { return mpd_module_->state_;
|
|||||||
inline void Context::do_update() { mpd_module_->setLabel(); }
|
inline void Context::do_update() { mpd_module_->setLabel(); }
|
||||||
|
|
||||||
inline void Context::checkErrors(mpd_connection* conn) const { mpd_module_->checkErrors(conn); }
|
inline void Context::checkErrors(mpd_connection* conn) const { mpd_module_->checkErrors(conn); }
|
||||||
inline void Context::queryMPD() const { mpd_module_->queryMPD(); }
|
|
||||||
inline void Context::fetchState() const { mpd_module_->fetchState(); }
|
inline void Context::fetchState() const { mpd_module_->fetchState(); }
|
||||||
inline void Context::emit() const { mpd_module_->emit(); }
|
inline void Context::emit() const { mpd_module_->emit(); }
|
||||||
|
|
||||||
|
|||||||
@@ -70,6 +70,8 @@ class Network : public ALabel {
|
|||||||
|
|
||||||
unsigned long long bandwidth_down_total_{0};
|
unsigned long long bandwidth_down_total_{0};
|
||||||
unsigned long long bandwidth_up_total_{0};
|
unsigned long long bandwidth_up_total_{0};
|
||||||
|
unsigned long long bandwidth_down_prev_{0};
|
||||||
|
unsigned long long bandwidth_up_prev_{0};
|
||||||
std::chrono::steady_clock::time_point bandwidth_last_sample_time_;
|
std::chrono::steady_clock::time_point bandwidth_last_sample_time_;
|
||||||
|
|
||||||
std::string state_;
|
std::string state_;
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ class Privacy : public AModule {
|
|||||||
|
|
||||||
// Config
|
// Config
|
||||||
Gtk::Box box_;
|
Gtk::Box box_;
|
||||||
|
std::vector<PrivacyItem*> modules_;
|
||||||
uint iconSpacing = 4;
|
uint iconSpacing = 4;
|
||||||
uint iconSize = 20;
|
uint iconSize = 20;
|
||||||
uint transition_duration = 250;
|
uint transition_duration = 250;
|
||||||
|
|||||||
@@ -6,11 +6,6 @@
|
|||||||
#include "util/audio_backend.hpp"
|
#include "util/audio_backend.hpp"
|
||||||
namespace waybar::modules {
|
namespace waybar::modules {
|
||||||
|
|
||||||
enum class PulseaudioSliderTarget {
|
|
||||||
Sink,
|
|
||||||
Source,
|
|
||||||
};
|
|
||||||
|
|
||||||
class PulseaudioSlider : public ASlider {
|
class PulseaudioSlider : public ASlider {
|
||||||
public:
|
public:
|
||||||
PulseaudioSlider(const std::string&, const Json::Value&);
|
PulseaudioSlider(const std::string&, const Json::Value&);
|
||||||
@@ -21,7 +16,15 @@ class PulseaudioSlider : public ASlider {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
std::shared_ptr<util::AudioBackend> backend = nullptr;
|
std::shared_ptr<util::AudioBackend> backend = nullptr;
|
||||||
PulseaudioSliderTarget target = PulseaudioSliderTarget::Sink;
|
util::PulseaudioTarget target = util::PulseaudioTarget::Sink;
|
||||||
|
|
||||||
|
bool zero_on_mute = true;
|
||||||
|
bool unmute_on_volume_change = true;
|
||||||
|
// zero_on_mute and unmute_on_volume_change default to true
|
||||||
|
// in order to maintain previous behaviour when using a
|
||||||
|
// config in which these values are undefined
|
||||||
|
|
||||||
|
bool previously_muted = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace waybar::modules
|
} // namespace waybar::modules
|
||||||
@@ -43,6 +43,8 @@ class Host {
|
|||||||
std::size_t watcher_id_;
|
std::size_t watcher_id_;
|
||||||
GCancellable* cancellable_ = nullptr;
|
GCancellable* cancellable_ = nullptr;
|
||||||
SnWatcher* watcher_ = nullptr;
|
SnWatcher* watcher_ = nullptr;
|
||||||
|
sigc::connection retry_connection_;
|
||||||
|
unsigned retry_count_ = 0;
|
||||||
const Json::Value& config_;
|
const Json::Value& config_;
|
||||||
const Bar& bar_;
|
const Bar& bar_;
|
||||||
const std::function<void(std::unique_ptr<Item>&)> on_add_;
|
const std::function<void(std::unique_ptr<Item>&)> on_add_;
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ class Item : public sigc::trackable {
|
|||||||
std::string title;
|
std::string title;
|
||||||
std::string icon_name;
|
std::string icon_name;
|
||||||
Glib::RefPtr<Gdk::Pixbuf> icon_pixmap;
|
Glib::RefPtr<Gdk::Pixbuf> icon_pixmap;
|
||||||
|
bool has_custom_icon_ = false;
|
||||||
Glib::RefPtr<Gtk::IconTheme> icon_theme;
|
Glib::RefPtr<Gtk::IconTheme> icon_theme;
|
||||||
std::string overlay_icon_name;
|
std::string overlay_icon_name;
|
||||||
Glib::RefPtr<Gdk::Pixbuf> overlay_icon_pixmap;
|
Glib::RefPtr<Gdk::Pixbuf> overlay_icon_pixmap;
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ class Tray : public AModule {
|
|||||||
void queueUpdate();
|
void queueUpdate();
|
||||||
|
|
||||||
static inline std::size_t nb_hosts_ = 0;
|
static inline std::size_t nb_hosts_ = 0;
|
||||||
bool show_passive_ = false;
|
|
||||||
Gtk::Box box_;
|
Gtk::Box box_;
|
||||||
SNI::Watcher::singleton watcher_;
|
SNI::Watcher::singleton watcher_;
|
||||||
SNI::Host host_;
|
SNI::Host host_;
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ class Workspaces : public AModule, public sigc::trackable {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
static constexpr std::string_view workspace_switch_cmd_ = "workspace {} \"{}\"";
|
static constexpr std::string_view workspace_switch_cmd_ = "workspace {} \"{}\"";
|
||||||
|
static constexpr std::string_view workspace_switch_number_cmd_ = "workspace {} number {}";
|
||||||
static constexpr std::string_view persistent_workspace_switch_cmd_ =
|
static constexpr std::string_view persistent_workspace_switch_cmd_ =
|
||||||
R"(workspace {} "{}"; move workspace to output "{}"; workspace {} "{}")";
|
R"(workspace {} "{}"; move workspace to output "{}"; workspace {} "{}")";
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ class Temperature : public ALabel {
|
|||||||
Temperature(const std::string&, const Json::Value&);
|
Temperature(const std::string&, const Json::Value&);
|
||||||
virtual ~Temperature() = default;
|
virtual ~Temperature() = default;
|
||||||
auto update() -> void override;
|
auto update() -> void override;
|
||||||
|
void suspend() override;
|
||||||
|
void resume() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
float getTemperature();
|
float getTemperature();
|
||||||
|
|||||||
@@ -12,11 +12,6 @@
|
|||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#ifdef __OpenBSD__
|
|
||||||
#define SIGRTMIN SIGUSR1 - 1
|
|
||||||
#define SIGRTMAX SIGUSR1 + 1
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace waybar {
|
namespace waybar {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -14,6 +14,11 @@
|
|||||||
|
|
||||||
namespace waybar::util {
|
namespace waybar::util {
|
||||||
|
|
||||||
|
enum class PulseaudioTarget {
|
||||||
|
Sink,
|
||||||
|
Source,
|
||||||
|
};
|
||||||
|
|
||||||
class AudioBackend {
|
class AudioBackend {
|
||||||
private:
|
private:
|
||||||
static void subscribeCb(pa_context*, pa_subscription_event_type_t, uint32_t, void*);
|
static void subscribeCb(pa_context*, pa_subscription_event_type_t, uint32_t, void*);
|
||||||
@@ -92,6 +97,10 @@ class AudioBackend {
|
|||||||
void toggleSourceMute();
|
void toggleSourceMute();
|
||||||
void toggleSourceMute(bool);
|
void toggleSourceMute(bool);
|
||||||
|
|
||||||
|
uint16_t getVolume(PulseaudioTarget) const;
|
||||||
|
bool getMuted(PulseaudioTarget) const;
|
||||||
|
void unmute(PulseaudioTarget);
|
||||||
|
|
||||||
bool isBluetooth();
|
bool isBluetooth();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -14,12 +14,14 @@ struct pollfd;
|
|||||||
namespace waybar {
|
namespace waybar {
|
||||||
class CssReloadHelper {
|
class CssReloadHelper {
|
||||||
public:
|
public:
|
||||||
CssReloadHelper(std::string cssFile, std::function<void()> callback);
|
CssReloadHelper(std::string cssFile, std::function<void(const std::string&)> callback);
|
||||||
|
|
||||||
virtual ~CssReloadHelper() = default;
|
virtual ~CssReloadHelper() = default;
|
||||||
|
|
||||||
virtual void monitorChanges();
|
virtual void monitorChanges();
|
||||||
|
|
||||||
|
virtual void changeCssFile(const std::string& newCssFile);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::vector<std::string> parseImports(const std::string& cssFile);
|
std::vector<std::string> parseImports(const std::string& cssFile);
|
||||||
|
|
||||||
@@ -42,7 +44,7 @@ class CssReloadHelper {
|
|||||||
private:
|
private:
|
||||||
std::string m_cssFile;
|
std::string m_cssFile;
|
||||||
|
|
||||||
std::function<void()> m_callback;
|
std::function<void(const std::string&)> m_callback;
|
||||||
|
|
||||||
std::vector<std::tuple<Glib::RefPtr<Gio::FileMonitor>>> m_fileMonitors;
|
std::vector<std::tuple<Glib::RefPtr<Gio::FileMonitor>>> m_fileMonitors;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include <codecvt>
|
#include <codecvt>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <locale>
|
#include <locale>
|
||||||
|
#include <memory>
|
||||||
#include <regex>
|
#include <regex>
|
||||||
|
|
||||||
#if (FMT_VERSION >= 90000)
|
#if (FMT_VERSION >= 90000)
|
||||||
@@ -26,14 +27,19 @@ class JsonParser {
|
|||||||
Json::Value root;
|
Json::Value root;
|
||||||
|
|
||||||
// replace all occurrences of "\x" with "\u00", because JSON doesn't allow "\x" escape sequences
|
// replace all occurrences of "\x" with "\u00", because JSON doesn't allow "\x" escape sequences
|
||||||
std::string modifiedJsonStr = replaceHexadecimalEscape(jsonStr);
|
std::string modifiedJsonStr;
|
||||||
|
const std::string* json = &jsonStr;
|
||||||
|
if (jsonStr.find("\\x") != std::string::npos) {
|
||||||
|
modifiedJsonStr = replaceHexadecimalEscape(jsonStr);
|
||||||
|
json = &modifiedJsonStr;
|
||||||
|
}
|
||||||
|
|
||||||
std::istringstream jsonStream(modifiedJsonStr);
|
|
||||||
std::string errs;
|
std::string errs;
|
||||||
// Use local CharReaderBuilder for thread safety - the IPC singleton's
|
// Use local CharReaderBuilder for thread safety - the IPC singleton's
|
||||||
// parser can be called concurrently from multiple module threads
|
// parser can be called concurrently from multiple module threads
|
||||||
Json::CharReaderBuilder readerBuilder;
|
Json::CharReaderBuilder readerBuilder;
|
||||||
if (!Json::parseFromStream(readerBuilder, jsonStream, &root, &errs)) {
|
auto reader = std::unique_ptr<Json::CharReader>(readerBuilder.newCharReader());
|
||||||
|
if (!reader->parse(json->data(), json->data() + json->size(), &root, &errs)) {
|
||||||
throw std::runtime_error("Error parsing JSON: " + errs);
|
throw std::runtime_error("Error parsing JSON: " + errs);
|
||||||
}
|
}
|
||||||
return root;
|
return root;
|
||||||
|
|||||||
@@ -79,6 +79,12 @@ class SleeperThread {
|
|||||||
auto sleep_for(std::chrono::system_clock::duration dur) {
|
auto sleep_for(std::chrono::system_clock::duration dur) {
|
||||||
std::unique_lock lk(mutex_);
|
std::unique_lock lk(mutex_);
|
||||||
CancellationGuard cancel_lock;
|
CancellationGuard cancel_lock;
|
||||||
|
|
||||||
|
condvar_.wait(lk, [this] {
|
||||||
|
return !is_paused_ || signal_.load(std::memory_order_relaxed) ||
|
||||||
|
!do_run_.load(std::memory_order_relaxed);
|
||||||
|
});
|
||||||
|
|
||||||
constexpr auto max_time_point = std::chrono::steady_clock::time_point::max();
|
constexpr auto max_time_point = std::chrono::steady_clock::time_point::max();
|
||||||
auto wait_end = max_time_point;
|
auto wait_end = max_time_point;
|
||||||
auto now = std::chrono::steady_clock::now();
|
auto now = std::chrono::steady_clock::now();
|
||||||
@@ -95,6 +101,12 @@ class SleeperThread {
|
|||||||
time_point) {
|
time_point) {
|
||||||
std::unique_lock lk(mutex_);
|
std::unique_lock lk(mutex_);
|
||||||
CancellationGuard cancel_lock;
|
CancellationGuard cancel_lock;
|
||||||
|
|
||||||
|
condvar_.wait(lk, [this] {
|
||||||
|
return !is_paused_ || signal_.load(std::memory_order_relaxed) ||
|
||||||
|
!do_run_.load(std::memory_order_relaxed);
|
||||||
|
});
|
||||||
|
|
||||||
return condvar_.wait_until(lk, time_point, [this] {
|
return condvar_.wait_until(lk, time_point, [this] {
|
||||||
return signal_.load(std::memory_order_relaxed) || !do_run_.load(std::memory_order_relaxed);
|
return signal_.load(std::memory_order_relaxed) || !do_run_.load(std::memory_order_relaxed);
|
||||||
});
|
});
|
||||||
@@ -122,6 +134,17 @@ class SleeperThread {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void pause() {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
is_paused_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void resume() {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
is_paused_ = false;
|
||||||
|
condvar_.notify_all();
|
||||||
|
}
|
||||||
|
|
||||||
~SleeperThread() {
|
~SleeperThread() {
|
||||||
connection_.disconnect();
|
connection_.disconnect();
|
||||||
stop();
|
stop();
|
||||||
@@ -137,6 +160,7 @@ class SleeperThread {
|
|||||||
std::atomic<bool> do_run_ = true;
|
std::atomic<bool> do_run_ = true;
|
||||||
std::atomic<bool> signal_ = false;
|
std::atomic<bool> signal_ = false;
|
||||||
sigc::connection connection_;
|
sigc::connection connection_;
|
||||||
|
bool is_paused_{false};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace waybar::util
|
} // namespace waybar::util
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace waybar::util {
|
||||||
|
size_t utf8_width(const std::string& str);
|
||||||
|
void utf8_truncate(std::string& s, const std::string& ellipsis, size_t max_len);
|
||||||
|
} // namespace waybar::util
|
||||||
@@ -27,6 +27,11 @@ The *battery* module displays the current capacity and state (eg. charging) of y
|
|||||||
default: false ++
|
default: false ++
|
||||||
Option to use the battery design capacity instead of its current maximal capacity.
|
Option to use the battery design capacity instead of its current maximal capacity.
|
||||||
|
|
||||||
|
*full-at-plugged*: ++
|
||||||
|
typeof: bool ++
|
||||||
|
default: false ++
|
||||||
|
When enabled, a battery that is *Full* while the adapter is online is reported with the *Plugged* status instead of *Full* (so you can style/format it separately). Disabled by default to preserve the existing *Full* behaviour.
|
||||||
|
|
||||||
*interval*: ++
|
*interval*: ++
|
||||||
typeof: integer ++
|
typeof: integer ++
|
||||||
default: 60 ++
|
default: 60 ++
|
||||||
|
|||||||
@@ -92,6 +92,12 @@ The *cpu* module displays the current CPU utilization.
|
|||||||
|
|
||||||
*{load}*: Current CPU load.
|
*{load}*: Current CPU load.
|
||||||
|
|
||||||
|
*{load1}*: CPU load average over the last minute.
|
||||||
|
|
||||||
|
*{load5}*: CPU load average over the last 5 minutes.
|
||||||
|
|
||||||
|
*{load15}*: CPU load average over the last 15 minutes.
|
||||||
|
|
||||||
*{usage}*: Current overall CPU usage.
|
*{usage}*: Current overall CPU usage.
|
||||||
|
|
||||||
*{usage*{n}*}*: Current CPU core n usage. Cores are numbered from zero, so first core will be {usage0} and 4th will be {usage3}.
|
*{usage*{n}*}*: Current CPU core n usage. Cores are numbered from zero, so first core will be {usage0} and 4th will be {usage3}.
|
||||||
|
|||||||
@@ -38,6 +38,20 @@ Addressed by *hyprland/language*
|
|||||||
typeof: array ++
|
typeof: array ++
|
||||||
The actions corresponding to the buttons of the menu.
|
The actions corresponding to the buttons of the menu.
|
||||||
|
|
||||||
|
*tooltip*: ++
|
||||||
|
typeof: boolean ++
|
||||||
|
default: true ++
|
||||||
|
Enables or disables the tooltip for the language module. By default, the tooltip is enabled. Set to *false* to disable.
|
||||||
|
|
||||||
|
*tooltip-format*: ++
|
||||||
|
typeof: string ++
|
||||||
|
default: {long} ++
|
||||||
|
Specifies the format of the tooltip when it is enabled. It follows the same format replacement rules as the *format* key.
|
||||||
|
|
||||||
|
*tooltip-format-<lang>*: ++
|
||||||
|
typeof: string ++
|
||||||
|
Allows specifying a different tooltip format for each language. The *<lang>* should be replaced with the language code. This can be used to provide a custom tooltip for each language.
|
||||||
|
|
||||||
*expand*: ++
|
*expand*: ++
|
||||||
typeof: bool ++
|
typeof: bool ++
|
||||||
default: false ++
|
default: false ++
|
||||||
@@ -66,6 +80,28 @@ Addressed by *hyprland/language*
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
"hyprland/language": {
|
||||||
|
"format": "{}",
|
||||||
|
"format-en": "US",
|
||||||
|
"format-es": "ES",
|
||||||
|
"tooltip": true,
|
||||||
|
"tooltip-format": "{long}"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
"hyprland/language": {
|
||||||
|
"format": "{}",
|
||||||
|
"format-en": "US",
|
||||||
|
"format-es": "ES",
|
||||||
|
"tooltip": true,
|
||||||
|
"tooltip-format": "{}",
|
||||||
|
"tooltip-format-es": "{Español}",
|
||||||
|
"tooltip-format-en": "{English (american)}"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
# STYLE
|
# STYLE
|
||||||
|
|
||||||
- *#language*
|
- *#language*
|
||||||
|
|||||||
@@ -135,6 +135,14 @@ This setting is ignored if *workspace-taskbar.enable* is set to true.
|
|||||||
default: false ++
|
default: false ++
|
||||||
If set to false, you can't scroll to cycle throughout workspaces from the entire bar. If set to true this behaviour is enabled.
|
If set to false, you can't scroll to cycle throughout workspaces from the entire bar. If set to true this behaviour is enabled.
|
||||||
|
|
||||||
|
*on-scroll-up*: ++
|
||||||
|
typeof: string ++
|
||||||
|
Command to execute when scrolling up on the module. This replaces the default behaviour of workspace cycling.
|
||||||
|
|
||||||
|
*on-scroll-down*: ++
|
||||||
|
typeof: string ++
|
||||||
|
Command to execute when scrolling down on the module. This replaces the default behaviour of workspace cycling.
|
||||||
|
|
||||||
*ignore-workspaces*: ++
|
*ignore-workspaces*: ++
|
||||||
typeof: array ++
|
typeof: array ++
|
||||||
default: [] ++
|
default: [] ++
|
||||||
|
|||||||
+12
-6
@@ -102,23 +102,29 @@ Addressed by *memory*
|
|||||||
default: false ++
|
default: false ++
|
||||||
Enables this module to consume all left over space dynamically.
|
Enables this module to consume all left over space dynamically.
|
||||||
|
|
||||||
|
*unit*: ++
|
||||||
|
typeof: string ++
|
||||||
|
default: GiB ++
|
||||||
|
Used to specify unit for total, swapTotal, used, swapUsed, avail, swapAvail,
|
||||||
|
and swapState. Accepts B, kB, kiB, MB, MiB, GB, GiB, TB, and TiB.
|
||||||
|
|
||||||
# FORMAT REPLACEMENTS
|
# FORMAT REPLACEMENTS
|
||||||
|
|
||||||
*{percentage}*: Percentage of memory in use.
|
*{percentage}*: Percentage of memory in use.
|
||||||
|
|
||||||
*{swapPercentage}*: Percentage of swap in use.
|
*{swapPercentage}*: Percentage of swap in use.
|
||||||
|
|
||||||
*{total}*: Amount of total memory available in GiB.
|
*{total}*: Amount of total memory available. Defaults to GiB.
|
||||||
|
|
||||||
*{swapTotal}*: Amount of total swap available in GiB.
|
*{swapTotal}*: Amount of total swap available. Defaults to GiB.
|
||||||
|
|
||||||
*{used}*: Amount of used memory in GiB.
|
*{used}*: Amount of used memory. Defaults to GiB.
|
||||||
|
|
||||||
*{swapUsed}*: Amount of used swap in GiB.
|
*{swapUsed}*: Amount of used swap. Defaults to GiB.
|
||||||
|
|
||||||
*{avail}*: Amount of available memory in GiB.
|
*{avail}*: Amount of available memory. Defaults to GiB.
|
||||||
|
|
||||||
*{swapAvail}*: Amount of available swap in GiB.
|
*{swapAvail}*: Amount of available swap. Defaults to GiB.
|
||||||
|
|
||||||
*{swapState}*: Signals if swap is activated or not
|
*{swapState}*: Signals if swap is activated or not
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,17 @@ The volume can be controlled by dragging the slider across the bar or clicking o
|
|||||||
default: false ++
|
default: false ++
|
||||||
Enables this module to consume all left over space dynamically.
|
Enables this module to consume all left over space dynamically.
|
||||||
|
|
||||||
|
*zero-on-mute*: ++
|
||||||
|
typeof: bool ++
|
||||||
|
default: true ++
|
||||||
|
`true` = The slider will be set to `min` when the source/sink is muted. ++
|
||||||
|
`false` = The slider will continue to show the unmuted volume level when the source/sink is muted.
|
||||||
|
|
||||||
|
*unmute-on-volume-change*: ++
|
||||||
|
typeof: bool ++
|
||||||
|
default: true ++
|
||||||
|
Specifies whether to unmute a muted souce/sink when its volume is changed by the user moving the slider.
|
||||||
|
|
||||||
# EXAMPLES
|
# EXAMPLES
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -41,7 +52,9 @@ The volume can be controlled by dragging the slider across the bar or clicking o
|
|||||||
"pulseaudio/slider": {
|
"pulseaudio/slider": {
|
||||||
"min": 0,
|
"min": 0,
|
||||||
"max": 100,
|
"max": 100,
|
||||||
"orientation": "horizontal"
|
"orientation": "horizontal",
|
||||||
|
"zero-on-mute": false,
|
||||||
|
"unmute-on-volume-change": false
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -52,6 +65,9 @@ The slider is a component with multiple CSS Nodes, of which the following are ex
|
|||||||
*#pulseaudio-slider*: ++
|
*#pulseaudio-slider*: ++
|
||||||
Controls the style of the box *around* the slider and bar.
|
Controls the style of the box *around* the slider and bar.
|
||||||
|
|
||||||
|
*#pulseaudio-slider.muted*: ++
|
||||||
|
Controls the style when the audio source/sink is muted.
|
||||||
|
|
||||||
*#pulseaudio-slider slider*: ++
|
*#pulseaudio-slider slider*: ++
|
||||||
Controls the style of the slider handle.
|
Controls the style of the slider handle.
|
||||||
|
|
||||||
@@ -85,4 +101,8 @@ The slider is a component with multiple CSS Nodes, of which the following are ex
|
|||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
background: green;
|
background: green;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#pulseaudio-slider.muted highlight {
|
||||||
|
background-color: orange;
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -8,13 +8,27 @@ waybar-styles - using stylesheets for waybar
|
|||||||
|
|
||||||
Waybar uses Cascading Style Sheets (CSS) to configure its appearance.
|
Waybar uses Cascading Style Sheets (CSS) to configure its appearance.
|
||||||
|
|
||||||
It uses the first file found in this search order:
|
When the system appearance is light, Waybar first looks for *style-light.css*.
|
||||||
|
When the system appearance is dark, Waybar first looks for *style-dark.css*.
|
||||||
|
If no appearance-specific stylesheet is found, it falls back to *style.css*.
|
||||||
|
|
||||||
- *$XDG_CONFIG_HOME/waybar/style.css*
|
Waybar uses the first file found in this search order for each stylesheet name:
|
||||||
- *~/.config/waybar/style.css*
|
|
||||||
- *~/waybar/style.css*
|
- *$XDG_CONFIG_HOME/waybar/<stylesheet>*
|
||||||
- */etc/xdg/waybar/style.css*
|
- *~/.config/waybar/<stylesheet>*
|
||||||
- *@sysconfdir@/xdg/waybar/style.css*
|
- *~/waybar/<stylesheet>*
|
||||||
|
- */etc/xdg/waybar/<stylesheet>*
|
||||||
|
- *@sysconfdir@/xdg/waybar/<stylesheet>*
|
||||||
|
|
||||||
|
For example, a light theme is loaded from the first available file among:
|
||||||
|
|
||||||
|
- *$XDG_CONFIG_HOME/waybar/style-light.css*
|
||||||
|
- *~/.config/waybar/style-light.css*
|
||||||
|
- *~/waybar/style-light.css*
|
||||||
|
- */etc/xdg/waybar/style-light.css*
|
||||||
|
- *@sysconfdir@/xdg/waybar/style-light.css*
|
||||||
|
|
||||||
|
If no file is found there, Waybar repeats the same search using *style.css*.
|
||||||
|
|
||||||
# EXAMPLE
|
# EXAMPLE
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,12 @@ Addressed by *temperature*
|
|||||||
The path of the hwmon-directory of the device, e.g. */sys/devices/pci0000:00/0000:00:18.3/hwmon*. (Note that the subdirectory *hwmon/hwmon#*, where *#* is a number is not part of the path!) Has to be used together with *input-filename*.
|
The path of the hwmon-directory of the device, e.g. */sys/devices/pci0000:00/0000:00:18.3/hwmon*. (Note that the subdirectory *hwmon/hwmon#*, where *#* is a number is not part of the path!) Has to be used together with *input-filename*.
|
||||||
This can also be an array of strings, for which, it just works like *hwmon-path*.
|
This can also be an array of strings, for which, it just works like *hwmon-path*.
|
||||||
|
|
||||||
|
*hwmon-name*: ++
|
||||||
|
typeof: string ++
|
||||||
|
Select a hwmon device by its name (from /sys/class/hwmon/\*/name), e.g. *amdgpu*.
|
||||||
|
Requires *input-filename* to be set.
|
||||||
|
Cannot be used together with *hwmon-path* or *hwmon-path-abs*.
|
||||||
|
|
||||||
*input-filename*: ++
|
*input-filename*: ++
|
||||||
typeof: string ++
|
typeof: string ++
|
||||||
The temperature filename of your *hwmon-path-abs*, e.g. *temp1_input*
|
The temperature filename of your *hwmon-path-abs*, e.g. *temp1_input*
|
||||||
|
|||||||
@@ -57,6 +57,11 @@ Addressed by *wlr/taskbar*
|
|||||||
default: false ++
|
default: false ++
|
||||||
If set to true, group tasks by their app_id. Cannot be used with 'active-first'.
|
If set to true, group tasks by their app_id. Cannot be used with 'active-first'.
|
||||||
|
|
||||||
|
*expand*: ++
|
||||||
|
typeof: bool ++
|
||||||
|
default: false ++
|
||||||
|
If set to true, task buttons stretch to fill the available space in the taskbar and long titles are ellipsized to fit. Only takes effect on a horizontal bar; on a vertical bar the buttons keep their content-based size. If set to false, buttons are sized to their content.
|
||||||
|
|
||||||
*on-click*: ++
|
*on-click*: ++
|
||||||
typeof: string ++
|
typeof: string ++
|
||||||
The action which should be triggered when clicking on the application button with the left mouse button.
|
The action which should be triggered when clicking on the application button with the left mouse button.
|
||||||
|
|||||||
+2
-1
@@ -19,7 +19,7 @@ Valid locations for this file are:
|
|||||||
A good starting point is the default configuration found at https://github.com/Alexays/Waybar/blob/master/resources/config.jsonc
|
A good starting point is the default configuration found at https://github.com/Alexays/Waybar/blob/master/resources/config.jsonc
|
||||||
Also, a minimal example configuration can be found at the bottom of this man page.
|
Also, a minimal example configuration can be found at the bottom of this man page.
|
||||||
|
|
||||||
The visual display elements for waybar use a CSS stylesheet, see *waybar-styles(5)* for details.
|
The visual display elements for waybar use a CSS stylesheet. Waybar supports *style.css* and the appearance-specific *style-light.css* and *style-dark.css* files; see *waybar-styles(5)* for details.
|
||||||
|
|
||||||
# BAR CONFIGURATION
|
# BAR CONFIGURATION
|
||||||
|
|
||||||
@@ -168,6 +168,7 @@ The visual display elements for waybar use a CSS stylesheet, see *waybar-styles(
|
|||||||
# MODULE FORMAT
|
# MODULE FORMAT
|
||||||
|
|
||||||
You can use PangoMarkupFormat (See https://developer.gnome.org/pango/stable/PangoMarkupFormat.html#PangoMarkupFormat).
|
You can use PangoMarkupFormat (See https://developer.gnome.org/pango/stable/PangoMarkupFormat.html#PangoMarkupFormat).
|
||||||
|
Tooltip appearance is generally controlled globally via CSS and cannot be truly scoped per module. Some modules (such as the clock) provide limited workarounds for customization.
|
||||||
|
|
||||||
e.g.
|
e.g.
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -186,7 +186,8 @@ src_files = files(
|
|||||||
'src/util/icon_loader.cpp',
|
'src/util/icon_loader.cpp',
|
||||||
'src/util/regex_collection.cpp',
|
'src/util/regex_collection.cpp',
|
||||||
'src/util/css_reload_helper.cpp',
|
'src/util/css_reload_helper.cpp',
|
||||||
'src/util/transform_8bit_to_rgba.cpp'
|
'src/util/transform_8bit_to_rgba.cpp',
|
||||||
|
'src/util/utf8_string.cpp'
|
||||||
)
|
)
|
||||||
|
|
||||||
man_files = files(
|
man_files = files(
|
||||||
|
|||||||
+1
-1
@@ -12,7 +12,7 @@ option('dbusmenu-gtk', type: 'feature', value: 'auto', description: 'Enable supp
|
|||||||
option('man-pages', type: 'feature', value: 'auto', description: 'Generate and install man pages')
|
option('man-pages', type: 'feature', value: 'auto', description: 'Generate and install man pages')
|
||||||
option('mpd', type: 'feature', value: 'auto', description: 'Enable support for the Music Player Daemon')
|
option('mpd', type: 'feature', value: 'auto', description: 'Enable support for the Music Player Daemon')
|
||||||
option('rfkill', type: 'feature', value: 'auto', description: 'Enable support for RFKILL')
|
option('rfkill', type: 'feature', value: 'auto', description: 'Enable support for RFKILL')
|
||||||
option('sndio', type: 'feature', value: 'auto', description: 'Enable support for sndio')
|
option('sndio', type: 'feature', value: 'auto', description: 'Enable support for volume control via sndio')
|
||||||
option('logind', type: 'feature', value: 'auto', description: 'Enable support for logind')
|
option('logind', type: 'feature', value: 'auto', description: 'Enable support for logind')
|
||||||
option('tests', type: 'feature', value: 'auto', description: 'Enable tests')
|
option('tests', type: 'feature', value: 'auto', description: 'Enable tests')
|
||||||
option('experimental', type : 'boolean', value : false, description: 'Enable experimental features')
|
option('experimental', type : 'boolean', value : false, description: 'Enable experimental features')
|
||||||
|
|||||||
@@ -112,17 +112,19 @@ class PlayerManager:
|
|||||||
logger.debug(f"Metadata changed for player {player.props.player_name}")
|
logger.debug(f"Metadata changed for player {player.props.player_name}")
|
||||||
player_name = player.props.player_name
|
player_name = player.props.player_name
|
||||||
artist = player.get_artist()
|
artist = player.get_artist()
|
||||||
artist = artist.replace("&", "&")
|
artist = artist and artist.replace("&", "&")
|
||||||
title = player.get_title()
|
title = player.get_title()
|
||||||
title = title.replace("&", "&")
|
title = title and title.replace("&", "&")
|
||||||
|
|
||||||
track_info = ""
|
track_info = ""
|
||||||
if player_name == "spotify" and "mpris:trackid" in metadata.keys() and ":ad:" in player.props.metadata["mpris:trackid"]:
|
if player_name == "spotify" and "mpris:trackid" in metadata.keys() and ":ad:" in player.props.metadata["mpris:trackid"]:
|
||||||
track_info = "Advertisement"
|
track_info = "Advertisement"
|
||||||
elif artist is not None and title is not None:
|
elif artist is not None and title is not None:
|
||||||
track_info = f"{artist} - {title}"
|
track_info = f"{artist} - {title}"
|
||||||
else:
|
elif title is not None:
|
||||||
track_info = title
|
track_info = title
|
||||||
|
elif artist is not None:
|
||||||
|
track_info = artist
|
||||||
|
|
||||||
if track_info:
|
if track_info:
|
||||||
if player.props.status == "Playing":
|
if player.props.status == "Playing":
|
||||||
|
|||||||
@@ -33,9 +33,7 @@ std::string toLowerCase(const std::string& input) {
|
|||||||
|
|
||||||
std::optional<std::string> getFileBySuffix(const std::string& dir, const std::string& suffix,
|
std::optional<std::string> getFileBySuffix(const std::string& dir, const std::string& suffix,
|
||||||
bool check_lower_case) {
|
bool check_lower_case) {
|
||||||
if (!std::filesystem::exists(dir)) {
|
try {
|
||||||
return {};
|
|
||||||
}
|
|
||||||
for (const auto& entry : std::filesystem::recursive_directory_iterator(dir)) {
|
for (const auto& entry : std::filesystem::recursive_directory_iterator(dir)) {
|
||||||
if (entry.is_regular_file()) {
|
if (entry.is_regular_file()) {
|
||||||
std::string filename = entry.path().filename().string();
|
std::string filename = entry.path().filename().string();
|
||||||
@@ -49,6 +47,8 @@ std::optional<std::string> getFileBySuffix(const std::string& dir, const std::st
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (const std::filesystem::filesystem_error&) {
|
||||||
|
}
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -139,6 +139,26 @@ ALabel::ALabel(const Json::Value& config, const std::string& name, const std::st
|
|||||||
|
|
||||||
auto ALabel::update() -> void { AModule::update(); }
|
auto ALabel::update() -> void { AModule::update(); }
|
||||||
|
|
||||||
|
bool ALabel::setLabelMarkup(const Glib::ustring& markup) {
|
||||||
|
if (last_label_markup_ == markup) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
label_.set_markup(markup);
|
||||||
|
last_label_markup_ = markup;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ALabel::setTooltipMarkup(const Glib::ustring& markup) {
|
||||||
|
if (last_tooltip_markup_ == markup) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
label_.set_tooltip_markup(markup);
|
||||||
|
last_tooltip_markup_ = markup;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
std::string ALabel::getIcon(uint16_t percentage, const std::string& alt, uint16_t max) {
|
std::string ALabel::getIcon(uint16_t percentage, const std::string& alt, uint16_t max) {
|
||||||
auto format_icons = config_["format-icons"];
|
auto format_icons = config_["format-icons"];
|
||||||
if (format_icons.isObject()) {
|
if (format_icons.isObject()) {
|
||||||
|
|||||||
+14
-3
@@ -17,10 +17,14 @@ AModule::AModule(const Json::Value& config, const std::string& name, const std::
|
|||||||
isTooltip{config_["tooltip"].isBool() ? config_["tooltip"].asBool() : true},
|
isTooltip{config_["tooltip"].isBool() ? config_["tooltip"].asBool() : true},
|
||||||
isExpand{config_["expand"].isBool() ? config_["expand"].asBool() : false},
|
isExpand{config_["expand"].isBool() ? config_["expand"].asBool() : false},
|
||||||
distance_scrolled_y_(0.0),
|
distance_scrolled_y_(0.0),
|
||||||
distance_scrolled_x_(0.0) {
|
distance_scrolled_x_(0.0),
|
||||||
|
cursor_timeout_conn_() {
|
||||||
// Configure module action Map
|
// Configure module action Map
|
||||||
const Json::Value actions{config_["actions"]};
|
const Json::Value actions{config_["actions"]};
|
||||||
|
|
||||||
|
disable_on_sleep_ =
|
||||||
|
config_["disable-on-sleep"].isBool() ? config_["disable-on-sleep"].asBool() : false;
|
||||||
|
|
||||||
for (Json::Value::const_iterator it = actions.begin(); it != actions.end(); ++it) {
|
for (Json::Value::const_iterator it = actions.begin(); it != actions.end(); ++it) {
|
||||||
if (it.key().isString() && it->isString())
|
if (it.key().isString() && it->isString())
|
||||||
if (!eventActionMap_.contains(it.key().asString())) {
|
if (!eventActionMap_.contains(it.key().asString())) {
|
||||||
@@ -72,8 +76,12 @@ AModule::AModule(const Json::Value& config, const std::string& name, const std::
|
|||||||
|
|
||||||
// Respect user configuration of cursor
|
// Respect user configuration of cursor
|
||||||
if (config_.isMember("cursor")) {
|
if (config_.isMember("cursor")) {
|
||||||
if (config_["cursor"].isBool() && config_["cursor"].asBool()) {
|
if (config_["cursor"].isBool()) {
|
||||||
|
if (config_["cursor"].asBool()) {
|
||||||
setCursor(Gdk::HAND2);
|
setCursor(Gdk::HAND2);
|
||||||
|
} else {
|
||||||
|
setCursor(Gdk::ARROW);
|
||||||
|
}
|
||||||
} else if (config_["cursor"].isInt()) {
|
} else if (config_["cursor"].isInt()) {
|
||||||
setCursor(Gdk::CursorType(config_["cursor"].asInt()));
|
setCursor(Gdk::CursorType(config_["cursor"].asInt()));
|
||||||
} else {
|
} else {
|
||||||
@@ -83,6 +91,9 @@ AModule::AModule(const Json::Value& config, const std::string& name, const std::
|
|||||||
}
|
}
|
||||||
|
|
||||||
AModule::~AModule() {
|
AModule::~AModule() {
|
||||||
|
if (cursor_timeout_conn_.connected()) {
|
||||||
|
cursor_timeout_conn_.disconnect();
|
||||||
|
}
|
||||||
for (const auto& pid : pid_children_) {
|
for (const auto& pid : pid_children_) {
|
||||||
if (pid != -1) {
|
if (pid != -1) {
|
||||||
killpg(pid, SIGTERM);
|
killpg(pid, SIGTERM);
|
||||||
@@ -118,7 +129,7 @@ void AModule::setCursor(Gdk::CursorType const& c) {
|
|||||||
} else {
|
} else {
|
||||||
// window may not be accessible yet, in this case,
|
// window may not be accessible yet, in this case,
|
||||||
// schedule another call for setting the cursor in 1 sec
|
// schedule another call for setting the cursor in 1 sec
|
||||||
Glib::signal_timeout().connect_seconds(
|
cursor_timeout_conn_ = Glib::signal_timeout().connect_seconds(
|
||||||
[this, c]() {
|
[this, c]() {
|
||||||
setCursor(c);
|
setCursor(c);
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
+70
-4
@@ -172,6 +172,10 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config)
|
|||||||
right_.set_spacing(spacing);
|
right_.set_spacing(spacing);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (config.isMember("height") && !config["height"].isUInt()) {
|
||||||
|
spdlog::warn("Invalid type for 'height', expected unsigned integer");
|
||||||
|
}
|
||||||
|
|
||||||
height_ = config["height"].isUInt() ? config["height"].asUInt() : 0;
|
height_ = config["height"].isUInt() ? config["height"].asUInt() : 0;
|
||||||
width_ = config["width"].isUInt() ? config["width"].asUInt() : 0;
|
width_ = config["width"].isUInt() ? config["width"].asUInt() : 0;
|
||||||
|
|
||||||
@@ -263,6 +267,16 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config)
|
|||||||
|
|
||||||
window.signal_map_event().connect_notify(sigc::mem_fun(*this, &Bar::onMap));
|
window.signal_map_event().connect_notify(sigc::mem_fun(*this, &Bar::onMap));
|
||||||
|
|
||||||
|
window.signal_unmap().connect([this]() {
|
||||||
|
spdlog::debug("Output {} unmapped (DPMS off), suspending modules", output->name);
|
||||||
|
toggleSuspend(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
window.signal_map().connect([this]() {
|
||||||
|
spdlog::debug("Output {} mapped (DPMS on), resuming modules", output->name);
|
||||||
|
toggleSuspend(false);
|
||||||
|
});
|
||||||
|
|
||||||
#if HAVE_SWAY
|
#if HAVE_SWAY
|
||||||
if (auto ipc = config["ipc"]; ipc.isBool() && ipc.asBool()) {
|
if (auto ipc = config["ipc"]; ipc.isBool() && ipc.asBool()) {
|
||||||
bar_id = Client::inst()->bar_id;
|
bar_id = Client::inst()->bar_id;
|
||||||
@@ -309,6 +323,19 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config)
|
|||||||
setupWidgets();
|
setupWidgets();
|
||||||
window.show_all();
|
window.show_all();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If gtk-layer-shell's synchronous wait for the initial configure timed out, show_all() can
|
||||||
|
* return with a configured but not-yet-presented surface. Kick GTK/layer-shell once control has
|
||||||
|
* returned to the main loop, when any late initial configure has been dispatched and widgets have
|
||||||
|
* had a chance to allocate/draw.
|
||||||
|
*/
|
||||||
|
Glib::signal_idle().connect(sigc::track_obj([this] {
|
||||||
|
window.queue_resize();
|
||||||
|
window.queue_draw();
|
||||||
|
forceLayerCommit();
|
||||||
|
return false;
|
||||||
|
}, *this));
|
||||||
|
|
||||||
if (spdlog::should_log(spdlog::level::debug)) {
|
if (spdlog::should_log(spdlog::level::debug)) {
|
||||||
// Unfortunately, this function isn't in the C++ bindings, so we have to call the C version.
|
// Unfortunately, this function isn't in the C++ bindings, so we have to call the C version.
|
||||||
char* gtk_tree = gtk_style_context_to_string(
|
char* gtk_tree = gtk_style_context_to_string(
|
||||||
@@ -374,7 +401,14 @@ void waybar::Bar::setMode(const struct bar_mode& mode) {
|
|||||||
* gtk-layer-shell schedules a commit on the next frame event in GTK, but this could fail in
|
* gtk-layer-shell schedules a commit on the next frame event in GTK, but this could fail in
|
||||||
* certain scenarios, such as fully occluded bar.
|
* certain scenarios, such as fully occluded bar.
|
||||||
*/
|
*/
|
||||||
|
forceLayerCommit();
|
||||||
|
}
|
||||||
|
|
||||||
|
void waybar::Bar::forceLayerCommit() {
|
||||||
|
auto* gtk_window = window.gobj();
|
||||||
|
if (gtk_window != nullptr && gtk_widget_get_realized(GTK_WIDGET(gtk_window))) {
|
||||||
gtk_layer_try_force_commit(gtk_window);
|
gtk_layer_try_force_commit(gtk_window);
|
||||||
|
}
|
||||||
wl_display_flush(Client::inst()->wl_display);
|
wl_display_flush(Client::inst()->wl_display);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -541,13 +575,15 @@ void waybar::Bar::getModules(const Factory& factory, const std::string& pos,
|
|||||||
auto vertical = (group != nullptr ? group->getBox().get_orientation()
|
auto vertical = (group != nullptr ? group->getBox().get_orientation()
|
||||||
: box_.get_orientation()) == Gtk::ORIENTATION_VERTICAL;
|
: box_.get_orientation()) == Gtk::ORIENTATION_VERTICAL;
|
||||||
|
|
||||||
auto group_config = config[ref];
|
const Json::Value& group_config = config[ref];
|
||||||
if (group_config["modules"].isNull()) {
|
if (group_config["modules"].isNull()) {
|
||||||
spdlog::warn("Group definition '{}' has not been found, group will be hidden", ref);
|
spdlog::warn("Group definition '{}' has not been found, group will be hidden", ref);
|
||||||
}
|
}
|
||||||
auto* group_module = new waybar::Group(id_name, class_name, group_config, vertical);
|
auto group_module =
|
||||||
getModules(factory, ref, group_module);
|
std::make_unique<waybar::Group>(id_name, class_name, group_config, vertical);
|
||||||
module = group_module;
|
|
||||||
|
getModules(factory, ref, group_module.get());
|
||||||
|
module = group_module.release();
|
||||||
} else {
|
} else {
|
||||||
module = factory.makeModule(ref, pos);
|
module = factory.makeModule(ref, pos);
|
||||||
}
|
}
|
||||||
@@ -650,6 +686,17 @@ void waybar::Bar::onConfigure(GdkEventConfigure* ev) {
|
|||||||
|
|
||||||
configureGlobalOffset(ev->width, ev->height);
|
configureGlobalOffset(ev->width, ev->height);
|
||||||
spdlog::info(BAR_SIZE_MSG, ev->width, ev->height, output->name);
|
spdlog::info(BAR_SIZE_MSG, ev->width, ev->height, output->name);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* gtk-layer-shell waits for the compositor's initial configure while realizing the window. On a
|
||||||
|
* busy compositor (common during session startup) that wait can time out even though the initial
|
||||||
|
* configure arrives shortly afterwards. In that case GTK may not schedule the frame commit that
|
||||||
|
* presents the first layer-surface buffer, leaving an otherwise configured bar invisible. Force a
|
||||||
|
* commit after every configure so late initial configures, and later compositor-driven resizes,
|
||||||
|
* always result in a submitted surface state.
|
||||||
|
*/
|
||||||
|
window.queue_draw();
|
||||||
|
forceLayerCommit();
|
||||||
}
|
}
|
||||||
|
|
||||||
void waybar::Bar::configureGlobalOffset(int width, int height) {
|
void waybar::Bar::configureGlobalOffset(int width, int height) {
|
||||||
@@ -694,3 +741,22 @@ void waybar::Bar::configureGlobalOffset(int width, int height) {
|
|||||||
void waybar::Bar::onOutputGeometryChanged() {
|
void waybar::Bar::onOutputGeometryChanged() {
|
||||||
configureGlobalOffset(window.get_width(), window.get_height());
|
configureGlobalOffset(window.get_width(), window.get_height());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void waybar::Bar::toggleSuspend(bool suspend) {
|
||||||
|
auto process_modules = [suspend](Gtk::Box& module_box) {
|
||||||
|
for (auto* widget : module_box.get_children()) {
|
||||||
|
auto* module = dynamic_cast<waybar::AModule*>(widget);
|
||||||
|
if (module && module->shouldSuspend()) {
|
||||||
|
if (suspend) {
|
||||||
|
module->suspend();
|
||||||
|
} else {
|
||||||
|
module->resume();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
process_modules(left_);
|
||||||
|
process_modules(center_);
|
||||||
|
process_modules(right_);
|
||||||
|
}
|
||||||
|
|||||||
+29
-6
@@ -91,11 +91,14 @@ void waybar::Client::handleOutputDone(void* data, struct zxdg_output_v1* /*xdg_o
|
|||||||
output.xdg_output.reset();
|
output.xdg_output.reset();
|
||||||
spdlog::debug("Output detection done: {} ({})", output.name, output.identifier);
|
spdlog::debug("Output detection done: {} ({})", output.name, output.identifier);
|
||||||
|
|
||||||
auto configs = client->getOutputConfigs(output);
|
client->pending_outputs_.push_back(&output);
|
||||||
if (!configs.empty()) {
|
|
||||||
for (const auto& config : configs) {
|
if (!client->bars_scheduled_) {
|
||||||
client->bars.emplace_back(std::make_unique<Bar>(&output, config));
|
client->bars_scheduled_ = true;
|
||||||
}
|
|
||||||
|
Glib::signal_idle().connect_once([client]() {
|
||||||
|
client->createBarsBatch();
|
||||||
|
}, Glib::PRIORITY_HIGH_IDLE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
@@ -103,6 +106,25 @@ void waybar::Client::handleOutputDone(void* data, struct zxdg_output_v1* /*xdg_o
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void waybar::Client::createBarsBatch() {
|
||||||
|
pending_outputs_.remove_if([this](auto* output) { return std::none_of(outputs_.begin(), outputs_.end(), [&output](const auto& o) { return &o == output; }); });
|
||||||
|
for (auto* output : pending_outputs_) {
|
||||||
|
try {
|
||||||
|
auto configs = getOutputConfigs(*output);
|
||||||
|
if (!configs.empty()) {
|
||||||
|
for (const auto& config : configs) {
|
||||||
|
bars.emplace_back(std::make_unique<Bar>(output, config));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
spdlog::warn("Error creating bar: {}", e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pending_outputs_.clear();
|
||||||
|
bars_scheduled_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
void waybar::Client::handleOutputName(void* data, struct zxdg_output_v1* /*xdg_output*/,
|
void waybar::Client::handleOutputName(void* data, struct zxdg_output_v1* /*xdg_output*/,
|
||||||
const char* name) {
|
const char* name) {
|
||||||
auto* client = waybar::Client::inst();
|
auto* client = waybar::Client::inst();
|
||||||
@@ -306,9 +328,10 @@ int waybar::Client::main(int argc, char* argv[]) {
|
|||||||
}
|
}
|
||||||
m_cssFile = getStyle(style_opt);
|
m_cssFile = getStyle(style_opt);
|
||||||
setupCss(m_cssFile);
|
setupCss(m_cssFile);
|
||||||
m_cssReloadHelper = std::make_unique<CssReloadHelper>(m_cssFile, [&]() { setupCss(m_cssFile); });
|
m_cssReloadHelper = std::make_unique<CssReloadHelper>(m_cssFile, [&](const std::string& css_file) { setupCss(css_file); });
|
||||||
portal->signal_appearance_changed().connect([&](waybar::Appearance appearance) {
|
portal->signal_appearance_changed().connect([&](waybar::Appearance appearance) {
|
||||||
auto css_file = getStyle(style_opt, appearance);
|
auto css_file = getStyle(style_opt, appearance);
|
||||||
|
m_cssReloadHelper->changeCssFile(css_file);
|
||||||
setupCss(css_file);
|
setupCss(css_file);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -55,9 +55,11 @@ static void catchSignals(waybar::SafeSignal<int>& signal_handler) {
|
|||||||
std::signal(SIGINT, writeSignalToPipe);
|
std::signal(SIGINT, writeSignalToPipe);
|
||||||
std::signal(SIGCHLD, writeSignalToPipe);
|
std::signal(SIGCHLD, writeSignalToPipe);
|
||||||
|
|
||||||
|
#ifdef SIGRTMIN
|
||||||
for (int sig = SIGRTMIN + 1; sig <= SIGRTMAX; ++sig) {
|
for (int sig = SIGRTMIN + 1; sig <= SIGRTMAX; ++sig) {
|
||||||
std::signal(sig, writeSignalToPipe);
|
std::signal(sig, writeSignalToPipe);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
int signum;
|
int signum;
|
||||||
@@ -119,12 +121,14 @@ void handleUserSignal(int signal, bool& reload) {
|
|||||||
// If this signal should restart or close the bar, this function will write
|
// If this signal should restart or close the bar, this function will write
|
||||||
// `true` or `false`, respectively, into `reload`.
|
// `true` or `false`, respectively, into `reload`.
|
||||||
static void handleSignalMainThread(int signum, bool& reload) {
|
static void handleSignalMainThread(int signum, bool& reload) {
|
||||||
|
#ifdef SIGRTMIN
|
||||||
if (signum >= SIGRTMIN + 1 && signum <= SIGRTMAX) {
|
if (signum >= SIGRTMIN + 1 && signum <= SIGRTMAX) {
|
||||||
for (auto& bar : waybar::Client::inst()->bars) {
|
for (auto& bar : waybar::Client::inst()->bars) {
|
||||||
bar->handleSignal(signum);
|
bar->handleSignal(signum);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
switch (signum) {
|
switch (signum) {
|
||||||
case SIGUSR1:
|
case SIGUSR1:
|
||||||
|
|||||||
+68
-47
@@ -117,17 +117,16 @@ void waybar::modules::Battery::refreshBatteries() {
|
|||||||
if (((bat_defined && dir_name == config_["bat"].asString()) || !bat_defined) &&
|
if (((bat_defined && dir_name == config_["bat"].asString()) || !bat_defined) &&
|
||||||
(fs::exists(node.path() / "capacity") || fs::exists(node.path() / "charge_now")) &&
|
(fs::exists(node.path() / "capacity") || fs::exists(node.path() / "charge_now")) &&
|
||||||
fs::exists(node.path() / "uevent") &&
|
fs::exists(node.path() / "uevent") &&
|
||||||
(fs::exists(node.path() / "status") || bat_compatibility) &&
|
(fs::exists(node.path() / "status") || bat_compatibility)) {
|
||||||
fs::exists(node.path() / "type")) {
|
|
||||||
std::string type;
|
std::string type;
|
||||||
std::ifstream(node.path() / "type") >> type;
|
if (std::ifstream{node.path() / "type"} >> type && !type.compare("Battery")) {
|
||||||
|
|
||||||
if (!type.compare("Battery")) {
|
|
||||||
// Ignore non-system power supplies unless explicitly requested
|
// Ignore non-system power supplies unless explicitly requested
|
||||||
if (!bat_defined && fs::exists(node.path() / "scope")) {
|
if (!bat_defined) {
|
||||||
std::string scope;
|
std::string scope;
|
||||||
std::ifstream(node.path() / "scope") >> scope;
|
// for hotplug-in device, access it is always unstable because you may remove the
|
||||||
if (g_ascii_strcasecmp(scope.data(), "device") == 0) {
|
// device anytime so just allow failure happen and do nothing
|
||||||
|
if (std::ifstream{node.path() / "scope"} >> scope &&
|
||||||
|
g_ascii_strcasecmp(scope.data(), "device") == 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -154,7 +153,7 @@ void waybar::modules::Battery::refreshBatteries() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (fs::filesystem_error& e) {
|
} catch (fs::filesystem_error& e) {
|
||||||
throw std::runtime_error(e.what());
|
spdlog::warn("Battery directory tracking failed: {}", e.what());
|
||||||
}
|
}
|
||||||
if (warnFirstTime_ && batteries_.empty()) {
|
if (warnFirstTime_ && batteries_.empty()) {
|
||||||
if (config_["bat"].isString()) {
|
if (config_["bat"].isString()) {
|
||||||
@@ -275,11 +274,11 @@ waybar::modules::Battery::getInfos() {
|
|||||||
auto bat = item.first;
|
auto bat = item.first;
|
||||||
std::string _status;
|
std::string _status;
|
||||||
|
|
||||||
/* Check for adapter status if battery is not available */
|
{
|
||||||
if (!std::ifstream(bat / "status")) {
|
std::ifstream f{bat / "status"};
|
||||||
|
if (!std::getline(f, _status)) {
|
||||||
std::getline(std::ifstream(adapter_ / "status"), _status);
|
std::getline(std::ifstream(adapter_ / "status"), _status);
|
||||||
} else {
|
}
|
||||||
std::getline(std::ifstream(bat / "status"), _status);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Some battery will report current and charge in μA/μAh.
|
// Some battery will report current and charge in μA/μAh.
|
||||||
@@ -288,64 +287,74 @@ waybar::modules::Battery::getInfos() {
|
|||||||
uint32_t current_now = 0;
|
uint32_t current_now = 0;
|
||||||
int32_t _current_now_int = 0;
|
int32_t _current_now_int = 0;
|
||||||
bool current_now_exists = false;
|
bool current_now_exists = false;
|
||||||
if (fs::exists(bat / "current_now")) {
|
if (std::ifstream current_now_f{bat / "current_now"}) {
|
||||||
|
if (current_now_f >> _current_now_int) {
|
||||||
current_now_exists = true;
|
current_now_exists = true;
|
||||||
std::ifstream(bat / "current_now") >> _current_now_int;
|
}
|
||||||
} else if (fs::exists(bat / "current_avg")) {
|
} else if (std::ifstream current_avg_f{bat / "current_avg"}) {
|
||||||
|
if (current_avg_f >> _current_now_int) {
|
||||||
current_now_exists = true;
|
current_now_exists = true;
|
||||||
std::ifstream(bat / "current_avg") >> _current_now_int;
|
}
|
||||||
}
|
}
|
||||||
// Documentation ABI allows a negative value when discharging, positive
|
// Documentation ABI allows a negative value when discharging, positive
|
||||||
// value when charging.
|
// value when charging.
|
||||||
current_now = std::abs(_current_now_int);
|
current_now = std::abs(_current_now_int);
|
||||||
|
|
||||||
if (fs::exists(bat / "time_to_empty_now")) {
|
if (std::ifstream f{bat / "time_to_empty_now"}) {
|
||||||
|
if (f >> time_to_empty_now) {
|
||||||
time_to_empty_now_exists = true;
|
time_to_empty_now_exists = true;
|
||||||
std::ifstream(bat / "time_to_empty_now") >> time_to_empty_now;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fs::exists(bat / "time_to_full_now")) {
|
if (std::ifstream f{bat / "time_to_full_now"}) {
|
||||||
|
if (f >> time_to_full_now) {
|
||||||
time_to_full_now_exists = true;
|
time_to_full_now_exists = true;
|
||||||
std::ifstream(bat / "time_to_full_now") >> time_to_full_now;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t voltage_now = 0;
|
uint32_t voltage_now = 0;
|
||||||
bool voltage_now_exists = false;
|
bool voltage_now_exists = false;
|
||||||
if (fs::exists(bat / "voltage_now")) {
|
if (std::ifstream voltage_now_f{bat / "voltage_now"}) {
|
||||||
|
if (voltage_now_f >> voltage_now) {
|
||||||
voltage_now_exists = true;
|
voltage_now_exists = true;
|
||||||
std::ifstream(bat / "voltage_now") >> voltage_now;
|
}
|
||||||
} else if (fs::exists(bat / "voltage_avg")) {
|
} else if (std::ifstream voltage_avg_f{bat / "voltage_avg"}) {
|
||||||
|
if (voltage_avg_f >> voltage_now) {
|
||||||
voltage_now_exists = true;
|
voltage_now_exists = true;
|
||||||
std::ifstream(bat / "voltage_avg") >> voltage_now;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t charge_full = 0;
|
uint32_t charge_full = 0;
|
||||||
bool charge_full_exists = false;
|
bool charge_full_exists = false;
|
||||||
if (fs::exists(bat / "charge_full")) {
|
if (std::ifstream f{bat / "charge_full"}) {
|
||||||
|
if (f >> charge_full) {
|
||||||
charge_full_exists = true;
|
charge_full_exists = true;
|
||||||
std::ifstream(bat / "charge_full") >> charge_full;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t charge_full_design = 0;
|
uint32_t charge_full_design = 0;
|
||||||
bool charge_full_design_exists = false;
|
bool charge_full_design_exists = false;
|
||||||
if (fs::exists(bat / "charge_full_design")) {
|
if (std::ifstream f{bat / "charge_full_design"}) {
|
||||||
|
if (f >> charge_full_design) {
|
||||||
charge_full_design_exists = true;
|
charge_full_design_exists = true;
|
||||||
std::ifstream(bat / "charge_full_design") >> charge_full_design;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t charge_now = 0;
|
uint32_t charge_now = 0;
|
||||||
bool charge_now_exists = false;
|
bool charge_now_exists = false;
|
||||||
if (fs::exists(bat / "charge_now")) {
|
if (std::ifstream f{bat / "charge_now"}) {
|
||||||
|
if (f >> charge_now) {
|
||||||
charge_now_exists = true;
|
charge_now_exists = true;
|
||||||
std::ifstream(bat / "charge_now") >> charge_now;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t power_now = 0;
|
uint32_t power_now = 0;
|
||||||
int32_t _power_now_int = 0;
|
int32_t _power_now_int = 0;
|
||||||
bool power_now_exists = false;
|
bool power_now_exists = false;
|
||||||
if (fs::exists(bat / "power_now")) {
|
if (std::ifstream f{bat / "power_now"}) {
|
||||||
|
if (f >> _power_now_int) {
|
||||||
power_now_exists = true;
|
power_now_exists = true;
|
||||||
std::ifstream(bat / "power_now") >> _power_now_int;
|
}
|
||||||
}
|
}
|
||||||
// Some drivers (example: Qualcomm) exposes use a negative value when
|
// Some drivers (example: Qualcomm) exposes use a negative value when
|
||||||
// discharging, positive value when charging.
|
// discharging, positive value when charging.
|
||||||
@@ -353,28 +362,31 @@ waybar::modules::Battery::getInfos() {
|
|||||||
|
|
||||||
uint32_t energy_now = 0;
|
uint32_t energy_now = 0;
|
||||||
bool energy_now_exists = false;
|
bool energy_now_exists = false;
|
||||||
if (fs::exists(bat / "energy_now")) {
|
if (std::ifstream f{bat / "energy_now"}) {
|
||||||
|
if (f >> energy_now) {
|
||||||
energy_now_exists = true;
|
energy_now_exists = true;
|
||||||
std::ifstream(bat / "energy_now") >> energy_now;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t energy_full = 0;
|
uint32_t energy_full = 0;
|
||||||
bool energy_full_exists = false;
|
bool energy_full_exists = false;
|
||||||
if (fs::exists(bat / "energy_full")) {
|
if (std::ifstream f{bat / "energy_full"}) {
|
||||||
|
if (f >> energy_full) {
|
||||||
energy_full_exists = true;
|
energy_full_exists = true;
|
||||||
std::ifstream(bat / "energy_full") >> energy_full;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t energy_full_design = 0;
|
uint32_t energy_full_design = 0;
|
||||||
bool energy_full_design_exists = false;
|
bool energy_full_design_exists = false;
|
||||||
if (fs::exists(bat / "energy_full_design")) {
|
if (std::ifstream f{bat / "energy_full_design"}) {
|
||||||
|
if (f >> energy_full_design) {
|
||||||
energy_full_design_exists = true;
|
energy_full_design_exists = true;
|
||||||
std::ifstream(bat / "energy_full_design") >> energy_full_design;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t cycleCount = 0;
|
uint16_t cycleCount = 0;
|
||||||
if (fs::exists(bat / "cycle_count")) {
|
if (std::ifstream f{bat / "cycle_count"}) {
|
||||||
std::ifstream(bat / "cycle_count") >> cycleCount;
|
f >> cycleCount;
|
||||||
}
|
}
|
||||||
if (charge_full_design >= largestDesignCapacity) {
|
if (charge_full_design >= largestDesignCapacity) {
|
||||||
largestDesignCapacity = charge_full_design;
|
largestDesignCapacity = charge_full_design;
|
||||||
@@ -404,9 +416,10 @@ waybar::modules::Battery::getInfos() {
|
|||||||
} else if (energy_now_exists && energy_full_exists && energy_full != 0) {
|
} else if (energy_now_exists && energy_full_exists && energy_full != 0) {
|
||||||
capacity_exists = true;
|
capacity_exists = true;
|
||||||
capacity = 100 * (uint64_t)energy_now / (uint64_t)energy_full;
|
capacity = 100 * (uint64_t)energy_now / (uint64_t)energy_full;
|
||||||
} else if (fs::exists(bat / "capacity")) {
|
} else if (std::ifstream f{bat / "capacity"}) {
|
||||||
|
if (f >> capacity) {
|
||||||
capacity_exists = true;
|
capacity_exists = true;
|
||||||
std::ifstream(bat / "capacity") >> capacity;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!voltage_now_exists) {
|
if (!voltage_now_exists) {
|
||||||
@@ -586,13 +599,14 @@ waybar::modules::Battery::getInfos() {
|
|||||||
|
|
||||||
float calculated_capacity{0.0f};
|
float calculated_capacity{0.0f};
|
||||||
if (total_capacity_exists) {
|
if (total_capacity_exists) {
|
||||||
if (total_capacity > 0.0f)
|
if (total_capacity > 0.0f) {
|
||||||
calculated_capacity = (float)total_capacity / batteries_.size();
|
calculated_capacity = (float)total_capacity / batteries_.size();
|
||||||
else if (total_energy_full_exists && total_energy_exists) {
|
} else if (total_energy_full_exists && total_energy_exists) {
|
||||||
if (total_energy_full > 0.0f)
|
if (total_energy_full > 0.0f) {
|
||||||
calculated_capacity = ((float)total_energy * 100.0f / (float)total_energy_full);
|
calculated_capacity = ((float)total_energy * 100.0f / (float)total_energy_full);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Handle weighted-average
|
// Handle weighted-average
|
||||||
if (weightedAverage_ && total_energy_exists && total_energy_full_exists) {
|
if (weightedAverage_ && total_energy_exists && total_energy_full_exists) {
|
||||||
@@ -687,13 +701,20 @@ auto waybar::modules::Battery::update() -> void {
|
|||||||
if (status == "Unknown") {
|
if (status == "Unknown") {
|
||||||
status = getAdapterStatus(capacity);
|
status = getAdapterStatus(capacity);
|
||||||
}
|
}
|
||||||
|
auto state = getState(capacity, true);
|
||||||
|
bool adapter_online = false;
|
||||||
|
if (!adapter_.empty()) {
|
||||||
|
std::ifstream(adapter_ / "online") >> adapter_online;
|
||||||
|
}
|
||||||
|
if (config_["full-at-plugged"].asBool() && status == "Full" && adapter_online) {
|
||||||
|
status = "Plugged";
|
||||||
|
}
|
||||||
auto status_pretty = status;
|
auto status_pretty = status;
|
||||||
|
|
||||||
// Transform to lowercase and replace space with dash
|
// Transform to lowercase and replace space with dash
|
||||||
std::ranges::transform(status.begin(), status.end(), status.begin(),
|
std::ranges::transform(status.begin(), status.end(), status.begin(),
|
||||||
[](char ch) { return ch == ' ' ? '-' : std::tolower(ch); });
|
[](char ch) { return ch == ' ' ? '-' : std::tolower(ch); });
|
||||||
auto format = format_;
|
auto format = format_;
|
||||||
auto state = getState(capacity, true);
|
|
||||||
processEvents(state, status, capacity);
|
processEvents(state, status, capacity);
|
||||||
setBarClass(state);
|
setBarClass(state);
|
||||||
auto time_remaining_formatted = formatTimeRemaining(time_remaining);
|
auto time_remaining_formatted = formatTimeRemaining(time_remaining);
|
||||||
|
|||||||
@@ -402,8 +402,13 @@ auto waybar::modules::Clock::get_calendar(const year_month_day& today, const yea
|
|||||||
data = g_utf8_find_next_char(data, end);
|
data = g_utf8_find_next_char(data, end);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Note: the stream's default fill character is already a space (L' ' on
|
||||||
|
// libstdc++'s wide FormatStream, ' ' on libc++'s narrow one), so no
|
||||||
|
// std::setfill is needed. Passing std::setfill(' ')/std::setfill(L' ')
|
||||||
|
// here is not portable because the fill char type must match the
|
||||||
|
// FormatStream's char type, which differs between standard libraries.
|
||||||
os << Glib::ustring::format(
|
os << Glib::ustring::format(
|
||||||
(cldWPos_ != WS::LEFT || line == 0) ? std::left : std::right, std::setfill(L' '),
|
(cldWPos_ != WS::LEFT || line == 0) ? std::left : std::right,
|
||||||
std::setw(cldMonColLen_ + ((line < 2) ? cldWnLen_ - wideCharCount : 0)),
|
std::setw(cldMonColLen_ + ((line < 2) ? cldWnLen_ - wideCharCount : 0)),
|
||||||
calendarLine);
|
calendarLine);
|
||||||
|
|
||||||
|
|||||||
+11
-3
@@ -41,6 +41,9 @@ auto waybar::modules::Cpu::update() -> void {
|
|||||||
auto icons = std::vector<std::string>{state};
|
auto icons = std::vector<std::string>{state};
|
||||||
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||||
store.push_back(fmt::arg("load", load1));
|
store.push_back(fmt::arg("load", load1));
|
||||||
|
store.push_back(fmt::arg("load1", load1));
|
||||||
|
store.push_back(fmt::arg("load5", load5));
|
||||||
|
store.push_back(fmt::arg("load15", load15));
|
||||||
store.push_back(fmt::arg("usage", total_usage));
|
store.push_back(fmt::arg("usage", total_usage));
|
||||||
store.push_back(fmt::arg("icon", getIcon(total_usage, icons)));
|
store.push_back(fmt::arg("icon", getIcon(total_usage, icons)));
|
||||||
store.push_back(fmt::arg("max_frequency", max_frequency));
|
store.push_back(fmt::arg("max_frequency", max_frequency));
|
||||||
@@ -58,9 +61,14 @@ auto waybar::modules::Cpu::update() -> void {
|
|||||||
label_.set_markup(fmt::vformat(format, store));
|
label_.set_markup(fmt::vformat(format, store));
|
||||||
|
|
||||||
if (tooltipEnabled()) {
|
if (tooltipEnabled()) {
|
||||||
if (config_["tooltip-format"].isString()) {
|
std::string tooltip_format;
|
||||||
tooltip = config_["tooltip-format"].asString();
|
if (!state.empty() && config_["tooltip-format-" + state].isString()) {
|
||||||
label_.set_tooltip_markup(fmt::vformat(tooltip, store));
|
tooltip_format = config_["tooltip-format-" + state].asString();
|
||||||
|
} else if (config_["tooltip-format"].isString()) {
|
||||||
|
tooltip_format = config_["tooltip-format"].asString();
|
||||||
|
}
|
||||||
|
if (!tooltip_format.empty()) {
|
||||||
|
label_.set_tooltip_markup(fmt::vformat(tooltip_format, store));
|
||||||
} else {
|
} else {
|
||||||
label_.set_tooltip_markup(tooltip);
|
label_.set_tooltip_markup(tooltip);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,23 +23,21 @@ std::vector<float> waybar::modules::CpuFrequency::parseCpuFrequencies() {
|
|||||||
|
|
||||||
if (frequencies.size() <= 0) {
|
if (frequencies.size() <= 0) {
|
||||||
std::string cpufreq_dir = "/sys/devices/system/cpu/cpufreq";
|
std::string cpufreq_dir = "/sys/devices/system/cpu/cpufreq";
|
||||||
if (std::filesystem::exists(cpufreq_dir)) {
|
try {
|
||||||
std::vector<std::string> frequency_files = {"/cpuinfo_min_freq", "/cpuinfo_max_freq"};
|
std::vector<std::string> frequency_files = {"/cpuinfo_min_freq", "/cpuinfo_max_freq"};
|
||||||
for (auto& p : std::filesystem::directory_iterator(cpufreq_dir)) {
|
for (auto& p : std::filesystem::directory_iterator(cpufreq_dir)) {
|
||||||
for (const auto& freq_file : frequency_files) {
|
for (const auto& freq_file : frequency_files) {
|
||||||
std::string freq_file_path = p.path().string() + freq_file;
|
std::string freq_file_path = p.path().string() + freq_file;
|
||||||
if (std::filesystem::exists(freq_file_path)) {
|
|
||||||
std::string freq_value;
|
std::string freq_value;
|
||||||
std::ifstream freq(freq_file_path);
|
std::ifstream freq(freq_file_path);
|
||||||
if (freq.is_open()) {
|
if (freq.is_open()) {
|
||||||
getline(freq, freq_value);
|
getline(freq, freq_value);
|
||||||
float frequency = std::strtol(freq_value.c_str(), nullptr, 10);
|
float frequency = std::strtol(freq_value.c_str(), nullptr, 10);
|
||||||
frequencies.push_back(frequency / 1000);
|
frequencies.push_back(frequency / 1000);
|
||||||
freq.close();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (const std::filesystem::filesystem_error&) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,11 @@ waybar::modules::Custom::~Custom() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void waybar::modules::Custom::delayWorker() {
|
void waybar::modules::Custom::delayWorker() {
|
||||||
|
if (!config_["exec"].isString() && !config_["exec-if"].isString()) {
|
||||||
|
dp.emit();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
thread_ = [this] {
|
thread_ = [this] {
|
||||||
for (int i : this->pid_children_) {
|
for (int i : this->pid_children_) {
|
||||||
int status;
|
int status;
|
||||||
@@ -138,9 +143,11 @@ void waybar::modules::Custom::waitingWorker() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void waybar::modules::Custom::refresh(int sig) {
|
void waybar::modules::Custom::refresh(int sig) {
|
||||||
|
#ifdef SIGRTMIN
|
||||||
if (config_["signal"].isInt() && sig == SIGRTMIN + config_["signal"].asInt()) {
|
if (config_["signal"].isInt() && sig == SIGRTMIN + config_["signal"].asInt()) {
|
||||||
thread_.wake_up();
|
thread_.wake_up();
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void waybar::modules::Custom::handleEvent() {
|
void waybar::modules::Custom::handleEvent() {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
#include <cerrno>
|
#include <cerrno>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "util/scoped_fd.hpp"
|
#include "util/scoped_fd.hpp"
|
||||||
@@ -20,6 +21,7 @@
|
|||||||
namespace waybar::modules::hyprland {
|
namespace waybar::modules::hyprland {
|
||||||
|
|
||||||
std::filesystem::path IPC::socketFolder_;
|
std::filesystem::path IPC::socketFolder_;
|
||||||
|
std::optional<bool> IPC::s_luaProtocolDetected_;
|
||||||
|
|
||||||
std::filesystem::path IPC::getSocketFolder(const char* instanceSig) {
|
std::filesystem::path IPC::getSocketFolder(const char* instanceSig) {
|
||||||
static std::mutex folderMutex;
|
static std::mutex folderMutex;
|
||||||
@@ -290,4 +292,69 @@ Json::Value IPC::getSocket1JsonReply(const std::string& rq) {
|
|||||||
return parser_.parse(reply);
|
return parser_.parse(reply);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IPC::isLuaProtocol() {
|
||||||
|
if (s_luaProtocolDetected_.has_value()) {
|
||||||
|
return *s_luaProtocolDetected_;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Probe: send a harmless old-style dispatch and check the error.
|
||||||
|
// In Lua-based Hyprland (>= 0.54) the error contains "hl.dispatch".
|
||||||
|
// In older versions it returns "ok" or a different error.
|
||||||
|
auto reply = getSocket1Reply("dispatch workspace __waybar_probe__");
|
||||||
|
bool luaProto = reply.find("hl.dispatch") != std::string::npos;
|
||||||
|
|
||||||
|
if (luaProto) {
|
||||||
|
spdlog::info("Hyprland IPC: detected Lua-based dispatch protocol (Hyprland >= 0.54)");
|
||||||
|
} else {
|
||||||
|
spdlog::info("Hyprland IPC: detected legacy dispatch protocol");
|
||||||
|
}
|
||||||
|
|
||||||
|
s_luaProtocolDetected_ = luaProto;
|
||||||
|
return luaProto;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string IPC::buildLuaDispatch(const std::string& dispatcher, const std::string& arg) {
|
||||||
|
// Map old-style dispatchers to the new Lua hl.dsp API.
|
||||||
|
//
|
||||||
|
// Old format: dispatch workspace 1
|
||||||
|
// New format: /dispatch hl.dsp.focus({ workspace = "1" })
|
||||||
|
//
|
||||||
|
// Old format: dispatch focusworkspaceoncurrentmonitor 2
|
||||||
|
// New format: /dispatch hl.dsp.focus({ workspace = "2", on_current_monitor = true })
|
||||||
|
//
|
||||||
|
// Old format: dispatch togglespecialworkspace name
|
||||||
|
// New format: /dispatch hl.dsp.workspace.toggle_special("name")
|
||||||
|
|
||||||
|
if (dispatcher == "workspace") {
|
||||||
|
return "/dispatch hl.dsp.focus({ workspace = \"" + arg + "\" })";
|
||||||
|
}
|
||||||
|
if (dispatcher == "focusworkspaceoncurrentmonitor") {
|
||||||
|
return "/dispatch hl.dsp.focus({ workspace = \"" + arg + "\", on_current_monitor = true })";
|
||||||
|
}
|
||||||
|
if (dispatcher == "togglespecialworkspace") {
|
||||||
|
if (arg.empty()) {
|
||||||
|
return "/dispatch hl.dsp.workspace.toggle_special()";
|
||||||
|
}
|
||||||
|
return "/dispatch hl.dsp.workspace.toggle_special(\"" + arg + "\")";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback for any other dispatcher: try the old format wrapped in dispatch().
|
||||||
|
// This may not work for all dispatchers, but it's a reasonable default.
|
||||||
|
spdlog::warn("Hyprland IPC: unknown dispatcher '{}' in Lua mode, attempting generic format",
|
||||||
|
dispatcher);
|
||||||
|
return "/dispatch hl.dsp." + dispatcher + "(\"" + arg + "\")";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string IPC::dispatch(const std::string& dispatcher, const std::string& arg) {
|
||||||
|
if (isLuaProtocol()) {
|
||||||
|
return getSocket1Reply(buildLuaDispatch(dispatcher, arg));
|
||||||
|
}
|
||||||
|
// Legacy format: "dispatch <dispatcher> <arg>"
|
||||||
|
std::string cmd = "dispatch " + dispatcher;
|
||||||
|
if (!arg.empty()) {
|
||||||
|
cmd += " " + arg;
|
||||||
|
}
|
||||||
|
return getSocket1Reply(cmd);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace waybar::modules::hyprland
|
} // namespace waybar::modules::hyprland
|
||||||
|
|||||||
@@ -48,12 +48,39 @@ auto Language::update() -> void {
|
|||||||
fmt::arg("shortDescription", layout_.short_description),
|
fmt::arg("shortDescription", layout_.short_description),
|
||||||
fmt::arg("variant", layout_.variant)));
|
fmt::arg("variant", layout_.variant)));
|
||||||
}
|
}
|
||||||
|
|
||||||
spdlog::debug("hyprland language formatted layout name {}", layoutName);
|
spdlog::debug("hyprland language formatted layout name {}", layoutName);
|
||||||
|
|
||||||
|
std::string tooltipContent = std::string{};
|
||||||
|
bool tooltip_enabled = tooltipEnabled();
|
||||||
|
if (tooltip_enabled) {
|
||||||
|
if (config_.isMember("tooltip-format")) {
|
||||||
|
auto tooltip_format = config_["tooltip-format"].asString();
|
||||||
|
if (config_.isMember("tooltip-format-" + layout_.short_description + "-" + layout_.variant)) {
|
||||||
|
const auto propName = "tooltip-format-" + layout_.short_description + "-" + layout_.variant;
|
||||||
|
tooltipContent = fmt::format(fmt::runtime(tooltip_format), config_[propName].asString());
|
||||||
|
} else if (config_.isMember("tooltip-format-" + layout_.short_description)) {
|
||||||
|
const auto propName = "tooltip-format-" + layout_.short_description;
|
||||||
|
tooltipContent = fmt::format(fmt::runtime(tooltip_format), config_[propName].asString());
|
||||||
|
} else {
|
||||||
|
tooltipContent =
|
||||||
|
trim(fmt::format(fmt::runtime(tooltip_format), fmt::arg("long", layout_.full_name),
|
||||||
|
fmt::arg("short", layout_.short_name),
|
||||||
|
fmt::arg("shortDescription", layout_.short_description),
|
||||||
|
fmt::arg("variant", layout_.variant)));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// if no tooltip format is provided, use the same text as the module
|
||||||
|
tooltipContent = layoutName;
|
||||||
|
}
|
||||||
|
spdlog::debug("hyprland language formatted tooltip content {}", tooltipContent);
|
||||||
|
}
|
||||||
|
|
||||||
if (!format_.empty()) {
|
if (!format_.empty()) {
|
||||||
label_.show();
|
label_.show();
|
||||||
label_.set_markup(layoutName);
|
label_.set_markup(layoutName);
|
||||||
|
if (tooltip_enabled) {
|
||||||
|
label_.set_tooltip_markup(tooltipContent);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
label_.hide();
|
label_.hide();
|
||||||
}
|
}
|
||||||
@@ -74,8 +101,6 @@ void Language::onEvent(const std::string& ev) {
|
|||||||
spdlog::warn("hyprland language received malformed event payload: {}", ev);
|
spdlog::warn("hyprland language received malformed event payload: {}", ev);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
std::string kbName = payload.substr(0, kbSeparator);
|
|
||||||
|
|
||||||
// Last comma before variants parenthesis, eg:
|
// Last comma before variants parenthesis, eg:
|
||||||
// activelayout>>micro-star-int'l-co.,-ltd.-msi-gk50-elite-gaming-keyboard,English (US, intl.,
|
// activelayout>>micro-star-int'l-co.,-ltd.-msi-gk50-elite-gaming-keyboard,English (US, intl.,
|
||||||
// with dead keys)
|
// with dead keys)
|
||||||
@@ -93,8 +118,15 @@ void Language::onEvent(const std::string& ev) {
|
|||||||
}
|
}
|
||||||
auto layoutName = payload.substr(layoutSeparator + 1);
|
auto layoutName = payload.substr(layoutSeparator + 1);
|
||||||
|
|
||||||
if (config_.isMember("keyboard-name") && kbName != config_["keyboard-name"].asString())
|
if (config_.isMember("keyboard-name")) {
|
||||||
|
const auto keyboardName = config_["keyboard-name"].asString();
|
||||||
|
// The keyboard name itself can contain commas, so match it as a full prefix
|
||||||
|
// (followed by the ',' separator) rather than comparing against the substring
|
||||||
|
// before the first comma, which would truncate such names and drop the event.
|
||||||
|
if (payload.size() <= keyboardName.size() || payload[keyboardName.size()] != ',' ||
|
||||||
|
payload.compare(0, keyboardName.size(), keyboardName) != 0)
|
||||||
return; // ignore
|
return; // ignore
|
||||||
|
}
|
||||||
|
|
||||||
layoutName = waybar::util::sanitize_string(layoutName);
|
layoutName = waybar::util::sanitize_string(layoutName);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "modules/hyprland/window.hpp"
|
#include "modules/hyprland/window.hpp"
|
||||||
|
|
||||||
|
#include <fmt/format.h>
|
||||||
#include <glibmm/fileutils.h>
|
#include <glibmm/fileutils.h>
|
||||||
#include <glibmm/keyfile.h>
|
#include <glibmm/keyfile.h>
|
||||||
#include <glibmm/miscutils.h>
|
#include <glibmm/miscutils.h>
|
||||||
@@ -53,8 +54,15 @@ auto Window::update() -> void {
|
|||||||
std::string label_text;
|
std::string label_text;
|
||||||
if (!format_.empty()) {
|
if (!format_.empty()) {
|
||||||
label_.show();
|
label_.show();
|
||||||
|
|
||||||
|
// If the focused window name is empty and fallback is configured, use fallback text
|
||||||
|
std::string displayTitle = windowName;
|
||||||
|
if (displayTitle.empty() && config_["fallback"].isString()) {
|
||||||
|
displayTitle = config_["fallback"].asString();
|
||||||
|
}
|
||||||
|
|
||||||
label_text = waybar::util::rewriteString(
|
label_text = waybar::util::rewriteString(
|
||||||
fmt::format(fmt::runtime(format_), fmt::arg("title", windowName),
|
fmt::format(fmt::runtime(format_), fmt::arg("title", displayTitle),
|
||||||
fmt::arg("initialTitle", windowData_.initial_title),
|
fmt::arg("initialTitle", windowData_.initial_title),
|
||||||
fmt::arg("class", windowData_.class_name),
|
fmt::arg("class", windowData_.class_name),
|
||||||
fmt::arg("initialClass", windowData_.initial_class_name)),
|
fmt::arg("initialClass", windowData_.initial_class_name)),
|
||||||
@@ -108,21 +116,14 @@ auto Window::update() -> void {
|
|||||||
AAppIconLabel::update();
|
AAppIconLabel::update();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Window::getActiveWorkspace() -> Workspace {
|
auto Window::getActiveWorkspace() -> Workspace { return getActiveWorkspace(""); }
|
||||||
const auto workspace = IPC::inst().getSocket1JsonReply("activeworkspace");
|
|
||||||
|
|
||||||
if (workspace.isObject()) {
|
|
||||||
return Workspace::parse(workspace);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Window::getActiveWorkspace(const std::string& monitorName) -> Workspace {
|
auto Window::getActiveWorkspace(const std::string& monitorName) -> Workspace {
|
||||||
const auto monitors = IPC::inst().getSocket1JsonReply("monitors");
|
const auto monitors = IPC::inst().getSocket1JsonReply("monitors");
|
||||||
if (monitors.isArray()) {
|
if (monitors.isArray()) {
|
||||||
auto monitor = std::ranges::find_if(
|
auto monitor = std::ranges::find_if(monitors, [&](const Json::Value& monitor) {
|
||||||
monitors, [&](const Json::Value& monitor) { return monitor["name"] == monitorName; });
|
return monitorName.empty() ? monitor["focused"].asBool() : monitor["name"] == monitorName;
|
||||||
|
});
|
||||||
if (monitor == std::end(monitors)) {
|
if (monitor == std::end(monitors)) {
|
||||||
spdlog::warn("Monitor not found: {}", monitorName);
|
spdlog::warn("Monitor not found: {}", monitorName);
|
||||||
return Workspace{
|
return Workspace{
|
||||||
@@ -132,7 +133,8 @@ auto Window::getActiveWorkspace(const std::string& monitorName) -> Workspace {
|
|||||||
.last_window_title = "",
|
.last_window_title = "",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const int id = (*monitor)["activeWorkspace"]["id"].asInt();
|
const int special_id = (*monitor)["specialWorkspace"]["id"].asInt();
|
||||||
|
const int id = special_id != 0 ? special_id : (*monitor)["activeWorkspace"]["id"].asInt();
|
||||||
|
|
||||||
const auto workspaces = IPC::inst().getSocket1JsonReply("workspaces");
|
const auto workspaces = IPC::inst().getSocket1JsonReply("workspaces");
|
||||||
if (workspaces.isArray()) {
|
if (workspaces.isArray()) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include <json/value.h>
|
#include <json/value.h>
|
||||||
#include <spdlog/spdlog.h>
|
#include <spdlog/spdlog.h>
|
||||||
|
#include <glibmm/main.h>
|
||||||
|
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
@@ -58,6 +59,11 @@ Workspace::Workspace(const Json::Value& workspace_data, Workspaces& workspace_ma
|
|||||||
}
|
}
|
||||||
|
|
||||||
m_button.add_events(Gdk::BUTTON_PRESS_MASK);
|
m_button.add_events(Gdk::BUTTON_PRESS_MASK);
|
||||||
|
m_button.add_events(Gdk::ENTER_NOTIFY_MASK | Gdk::LEAVE_NOTIFY_MASK);
|
||||||
|
|
||||||
|
m_button.signal_enter_notify_event().connect(sigc::mem_fun(*this, &Workspace::handleEnter));
|
||||||
|
m_button.signal_leave_notify_event().connect(sigc::mem_fun(*this, &Workspace::handleLeave));
|
||||||
|
|
||||||
m_button.signal_button_press_event().connect(sigc::mem_fun(*this, &Workspace::handleClicked),
|
m_button.signal_button_press_event().connect(sigc::mem_fun(*this, &Workspace::handleClicked),
|
||||||
false);
|
false);
|
||||||
|
|
||||||
@@ -73,6 +79,12 @@ Workspace::Workspace(const Json::Value& workspace_data, Workspaces& workspace_ma
|
|||||||
initializeWindowMap(clients_data);
|
initializeWindowMap(clients_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Workspace::~Workspace() {
|
||||||
|
// Disconnect the hover-check timeout so it can't fire on this destroyed
|
||||||
|
// instance (Workspaces are removed at runtime while a check may be armed).
|
||||||
|
stopHoverCheck();
|
||||||
|
}
|
||||||
|
|
||||||
void addOrRemoveClass(const Glib::RefPtr<Gtk::StyleContext>& context, bool condition,
|
void addOrRemoveClass(const Glib::RefPtr<Gtk::StyleContext>& context, bool condition,
|
||||||
const std::string& class_name) {
|
const std::string& class_name) {
|
||||||
if (condition) {
|
if (condition) {
|
||||||
@@ -94,25 +106,121 @@ std::optional<WindowRepr> Workspace::closeWindow(WindowAddress const& addr) {
|
|||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Workspace::pointerInsideButton() {
|
||||||
|
auto display = Gdk::Display::get_default();
|
||||||
|
if (!display) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto seat = display->get_default_seat();
|
||||||
|
if (!seat) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto pointer = seat->get_pointer();
|
||||||
|
if (!pointer) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Glib::RefPtr<Gdk::Screen> screen;
|
||||||
|
int pointerRootX = 0;
|
||||||
|
int pointerRootY = 0;
|
||||||
|
|
||||||
|
pointer->get_position(screen, pointerRootX, pointerRootY);
|
||||||
|
|
||||||
|
Gtk::Widget* toplevel = m_button.get_toplevel();
|
||||||
|
if (toplevel == nullptr || !toplevel->get_window()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int buttonX = 0;
|
||||||
|
int buttonY = 0;
|
||||||
|
|
||||||
|
if (!m_button.translate_coordinates(*toplevel, 0, 0, buttonX, buttonY)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int windowRootX = 0;
|
||||||
|
int windowRootY = 0;
|
||||||
|
toplevel->get_window()->get_root_origin(windowRootX, windowRootY);
|
||||||
|
|
||||||
|
const auto allocation = m_button.get_allocation();
|
||||||
|
|
||||||
|
const int buttonRootX = windowRootX + buttonX;
|
||||||
|
const int buttonRootY = windowRootY + buttonY;
|
||||||
|
const int buttonWidth = allocation.get_width();
|
||||||
|
const int buttonHeight = allocation.get_height();
|
||||||
|
|
||||||
|
return pointerRootX >= buttonRootX && pointerRootY >= buttonRootY &&
|
||||||
|
pointerRootX < buttonRootX + buttonWidth &&
|
||||||
|
pointerRootY < buttonRootY + buttonHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Workspace::syncHoverClass() {
|
||||||
|
auto styleContext = m_button.get_style_context();
|
||||||
|
|
||||||
|
if (pointerInsideButton()) {
|
||||||
|
styleContext->add_class("workspace-hover");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
styleContext->remove_class("workspace-hover");
|
||||||
|
stopHoverCheck();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Workspace::startHoverCheck() {
|
||||||
|
if (m_hoverCheckConnection.connected()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_hoverCheckConnection = Glib::signal_timeout().connect(
|
||||||
|
sigc::mem_fun(*this, &Workspace::syncHoverClass),
|
||||||
|
50);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Workspace::stopHoverCheck() {
|
||||||
|
if (m_hoverCheckConnection.connected()) {
|
||||||
|
m_hoverCheckConnection.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Workspace::handleEnter(GdkEventCrossing* /*event*/) {
|
||||||
|
m_button.get_style_context()->add_class("workspace-hover");
|
||||||
|
startHoverCheck();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Workspace::handleLeave(GdkEventCrossing* /*event*/) {
|
||||||
|
/*
|
||||||
|
* Do not remove immediately.
|
||||||
|
* Workspace taskbar children can fire misleading leave events while the
|
||||||
|
* pointer is still visually inside the workspace button.
|
||||||
|
*
|
||||||
|
* The polling check will remove the class once the pointer really leaves.
|
||||||
|
*/
|
||||||
|
startHoverCheck();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
bool Workspace::handleClicked(GdkEventButton* bt) const {
|
bool Workspace::handleClicked(GdkEventButton* bt) const {
|
||||||
if (bt->type == GDK_BUTTON_PRESS) {
|
if (bt->type == GDK_BUTTON_PRESS) {
|
||||||
try {
|
try {
|
||||||
if (id() > 0) { // normal
|
if (id() > 0) { // normal
|
||||||
if (m_workspaceManager.moveToMonitor()) {
|
if (m_workspaceManager.moveToMonitor()) {
|
||||||
m_ipc.getSocket1Reply("dispatch focusworkspaceoncurrentmonitor " + std::to_string(id()));
|
IPC::dispatch("focusworkspaceoncurrentmonitor", std::to_string(id()));
|
||||||
} else {
|
} else {
|
||||||
m_ipc.getSocket1Reply("dispatch workspace " + std::to_string(id()));
|
IPC::dispatch("workspace", std::to_string(id()));
|
||||||
}
|
}
|
||||||
} else if (!isSpecial()) { // named (this includes persistent)
|
} else if (!isSpecial()) { // named (this includes persistent)
|
||||||
if (m_workspaceManager.moveToMonitor()) {
|
if (m_workspaceManager.moveToMonitor()) {
|
||||||
m_ipc.getSocket1Reply("dispatch focusworkspaceoncurrentmonitor name:" + name());
|
IPC::dispatch("focusworkspaceoncurrentmonitor", "name:" + name());
|
||||||
} else {
|
} else {
|
||||||
m_ipc.getSocket1Reply("dispatch workspace name:" + name());
|
IPC::dispatch("workspace", "name:" + name());
|
||||||
}
|
}
|
||||||
} else if (id() != -99) { // named special
|
} else if (id() != -99) { // named special
|
||||||
m_ipc.getSocket1Reply("dispatch togglespecialworkspace " + name());
|
IPC::dispatch("togglespecialworkspace", name());
|
||||||
} else { // special
|
} else { // special
|
||||||
m_ipc.getSocket1Reply("dispatch togglespecialworkspace");
|
IPC::dispatch("togglespecialworkspace", "");
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
@@ -143,7 +251,11 @@ void Workspace::setActiveWindow(WindowAddress const& addr) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto activeWindowPos = m_workspaceManager.activeWindowPosition();
|
auto activeWindowPos = m_workspaceManager.activeWindowPosition();
|
||||||
if (activeIdx.has_value() && activeWindowPos != Workspaces::ActiveWindowPosition::NONE) {
|
const bool has_active_window =
|
||||||
|
activeIdx.has_value() &&
|
||||||
|
activeWindowPos != Workspaces::ActiveWindowPosition::NONE;
|
||||||
|
|
||||||
|
if (has_active_window) {
|
||||||
auto window = std::move(m_windowMap[*activeIdx]);
|
auto window = std::move(m_windowMap[*activeIdx]);
|
||||||
m_windowMap.erase(m_windowMap.begin() + *activeIdx);
|
m_windowMap.erase(m_windowMap.begin() + *activeIdx);
|
||||||
if (activeWindowPos == Workspaces::ActiveWindowPosition::FIRST) {
|
if (activeWindowPos == Workspaces::ActiveWindowPosition::FIRST) {
|
||||||
@@ -158,7 +270,10 @@ void Workspace::insertWindow(WindowCreationPayload create_window_payload) {
|
|||||||
if (!create_window_payload.isEmpty(m_workspaceManager)) {
|
if (!create_window_payload.isEmpty(m_workspaceManager)) {
|
||||||
auto repr = create_window_payload.repr(m_workspaceManager);
|
auto repr = create_window_payload.repr(m_workspaceManager);
|
||||||
|
|
||||||
if (!repr.empty() || m_workspaceManager.enableTaskbar()) {
|
const bool should_display =
|
||||||
|
!repr.empty() || m_workspaceManager.enableTaskbar();
|
||||||
|
|
||||||
|
if (should_display) {
|
||||||
auto addr = create_window_payload.getAddress();
|
auto addr = create_window_payload.getAddress();
|
||||||
auto it = std::ranges::find_if(
|
auto it = std::ranges::find_if(
|
||||||
m_windowMap, [&addr](const auto& window) { return window.address == addr; });
|
m_windowMap, [&addr](const auto& window) { return window.address == addr; });
|
||||||
@@ -170,7 +285,7 @@ void Workspace::insertWindow(WindowCreationPayload create_window_payload) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
bool Workspace::onWindowOpened(WindowCreationPayload const& create_window_payload) {
|
bool Workspace::onWindowOpened(WindowCreationPayload const& create_window_payload) {
|
||||||
if (create_window_payload.getWorkspaceName() == name()) {
|
if (create_window_payload.getWorkspaceName() == name()) {
|
||||||
@@ -189,6 +304,17 @@ std::string& Workspace::selectIcon(std::map<std::string, std::string>& icons_map
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isActive() && isSpecial()) {
|
||||||
|
auto activeIconIt = icons_map.find("active:" + name());
|
||||||
|
if (activeIconIt != icons_map.end()) {
|
||||||
|
return activeIconIt->second;
|
||||||
|
}
|
||||||
|
auto namedIconIt = icons_map.find(name());
|
||||||
|
if (namedIconIt != icons_map.end()) {
|
||||||
|
return namedIconIt->second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (isActive()) {
|
if (isActive()) {
|
||||||
auto activeIconIt = icons_map.find("active");
|
auto activeIconIt = icons_map.find("active");
|
||||||
if (activeIconIt != icons_map.end()) {
|
if (activeIconIt != icons_map.end()) {
|
||||||
@@ -263,6 +389,7 @@ void Workspace::update(const std::string& workspace_icon) {
|
|||||||
auto styleContext = m_button.get_style_context();
|
auto styleContext = m_button.get_style_context();
|
||||||
addOrRemoveClass(styleContext, isActive(), "active");
|
addOrRemoveClass(styleContext, isActive(), "active");
|
||||||
addOrRemoveClass(styleContext, isSpecial(), "special");
|
addOrRemoveClass(styleContext, isSpecial(), "special");
|
||||||
|
addOrRemoveClass(styleContext, isSpecial(), name());
|
||||||
addOrRemoveClass(styleContext, isEmpty(), "empty");
|
addOrRemoveClass(styleContext, isEmpty(), "empty");
|
||||||
addOrRemoveClass(styleContext, isPersistent(), "persistent");
|
addOrRemoveClass(styleContext, isPersistent(), "persistent");
|
||||||
addOrRemoveClass(styleContext, isUrgent(), "urgent");
|
addOrRemoveClass(styleContext, isUrgent(), "urgent");
|
||||||
@@ -310,7 +437,9 @@ void Workspace::update(const std::string& workspace_icon) {
|
|||||||
|
|
||||||
bool Workspace::isEmpty() const {
|
bool Workspace::isEmpty() const {
|
||||||
auto ignore_list = m_workspaceManager.getIgnoredWindows();
|
auto ignore_list = m_workspaceManager.getIgnoredWindows();
|
||||||
if (ignore_list.empty()) {
|
const bool no_ignore_rules = ignore_list.empty();
|
||||||
|
|
||||||
|
if (no_ignore_rules) {
|
||||||
return m_windows == 0;
|
return m_windows == 0;
|
||||||
}
|
}
|
||||||
// If there are windows but they are all ignored, consider the workspace empty
|
// If there are windows but they are all ignored, consider the workspace empty
|
||||||
@@ -341,15 +470,18 @@ void Workspace::updateTaskbar(const std::string& workspace_icon) {
|
|||||||
|
|
||||||
auto window_box = Gtk::make_managed<Gtk::Box>(Gtk::ORIENTATION_HORIZONTAL);
|
auto window_box = Gtk::make_managed<Gtk::Box>(Gtk::ORIENTATION_HORIZONTAL);
|
||||||
window_box->set_tooltip_markup(window_repr.window_title);
|
window_box->set_tooltip_markup(window_repr.window_title);
|
||||||
window_box->get_style_context()->add_class("taskbar-window");
|
|
||||||
|
auto button = Gtk::manage(new Gtk::Button());
|
||||||
|
button->set_relief(Gtk::RELIEF_NONE);
|
||||||
|
button->add(*window_box);
|
||||||
|
button->get_style_context()->add_class("taskbar-window");
|
||||||
if (window_repr.isActive) {
|
if (window_repr.isActive) {
|
||||||
window_box->get_style_context()->add_class("active");
|
button->get_style_context()->add_class("active");
|
||||||
}
|
}
|
||||||
auto event_box = Gtk::manage(new Gtk::EventBox());
|
|
||||||
event_box->add(*window_box);
|
|
||||||
if (m_workspaceManager.onClickWindow() != "") {
|
if (m_workspaceManager.onClickWindow() != "") {
|
||||||
event_box->signal_button_press_event().connect(
|
button->signal_button_press_event().connect(
|
||||||
sigc::bind(sigc::mem_fun(*this, &Workspace::handleClick), window_repr.address));
|
sigc::bind(sigc::mem_fun(*this, &Workspace::handleClick), window_repr.address),
|
||||||
|
false);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto text_before = fmt::format(fmt::runtime(m_workspaceManager.taskbarFormatBefore()),
|
auto text_before = fmt::format(fmt::runtime(m_workspaceManager.taskbarFormatBefore()),
|
||||||
@@ -374,8 +506,8 @@ void Workspace::updateTaskbar(const std::string& workspace_icon) {
|
|||||||
window_box->pack_start(*window_label_after, true, true);
|
window_box->pack_start(*window_label_after, true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
m_content.pack_start(*event_box, true, false);
|
m_content.pack_start(*button, true, false);
|
||||||
event_box->show_all();
|
button->show_all();
|
||||||
};
|
};
|
||||||
|
|
||||||
if (m_workspaceManager.taskbarReverseDirection()) {
|
if (m_workspaceManager.taskbarReverseDirection()) {
|
||||||
@@ -389,7 +521,9 @@ void Workspace::updateTaskbar(const std::string& workspace_icon) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto formatAfter = m_workspaceManager.formatAfter();
|
auto formatAfter = m_workspaceManager.formatAfter();
|
||||||
if (!formatAfter.empty()) {
|
const bool has_format_after = !formatAfter.empty();
|
||||||
|
|
||||||
|
if (has_format_after) {
|
||||||
m_labelAfter.set_markup(fmt::format(fmt::runtime(formatAfter), fmt::arg("id", id()),
|
m_labelAfter.set_markup(fmt::format(fmt::runtime(formatAfter), fmt::arg("id", id()),
|
||||||
fmt::arg("name", name()),
|
fmt::arg("name", name()),
|
||||||
fmt::arg("icon", workspace_icon)));
|
fmt::arg("icon", workspace_icon)));
|
||||||
|
|||||||
@@ -50,7 +50,8 @@ void Workspaces::init() {
|
|||||||
if (m_scrollEventConnection_.connected()) {
|
if (m_scrollEventConnection_.connected()) {
|
||||||
m_scrollEventConnection_.disconnect();
|
m_scrollEventConnection_.disconnect();
|
||||||
}
|
}
|
||||||
if (barScroll()) {
|
bool hasScrollConfig = config_["on-scroll-up"].isString() || config_["on-scroll-down"].isString();
|
||||||
|
if (barScroll() || hasScrollConfig) {
|
||||||
auto& window = const_cast<Bar&>(m_bar).window;
|
auto& window = const_cast<Bar&>(m_bar).window;
|
||||||
window.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);
|
window.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);
|
||||||
m_scrollEventConnection_ =
|
m_scrollEventConnection_ =
|
||||||
@@ -326,6 +327,10 @@ void Workspaces::loadPersistentWorkspacesFromWorkspaceRules(const Json::Value& c
|
|||||||
// 2. the rule's monitor is the current monitor
|
// 2. the rule's monitor is the current monitor
|
||||||
// 3. no monitor is specified in the rule => assume it needs to be persistent on every monitor
|
// 3. no monitor is specified in the rule => assume it needs to be persistent on every monitor
|
||||||
if (allOutputs() || m_bar.output->name == monitor || monitor.empty()) {
|
if (allOutputs() || m_bar.output->name == monitor || monitor.empty()) {
|
||||||
|
// => skip ignore-workspaces even if its a persistent
|
||||||
|
if(isWorkspaceIgnored(workspace)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
// => persistent workspace should be shown on this monitor
|
// => persistent workspace should be shown on this monitor
|
||||||
auto workspaceData = createMonitorWorkspaceData(workspace, m_bar.output->name);
|
auto workspaceData = createMonitorWorkspaceData(workspace, m_bar.output->name);
|
||||||
workspaceData["persistent-rule"] = true;
|
workspaceData["persistent-rule"] = true;
|
||||||
@@ -1188,6 +1193,12 @@ bool Workspaces::handleScroll(GdkEventScroll* e) {
|
|||||||
if (gdk_event_get_pointer_emulated((GdkEvent*)e)) {
|
if (gdk_event_get_pointer_emulated((GdkEvent*)e)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for custom scroll commands first; delegate to base class
|
||||||
|
if (config_["on-scroll-up"].isString() || config_["on-scroll-down"].isString()) {
|
||||||
|
return AModule::handleScroll(e);
|
||||||
|
}
|
||||||
|
|
||||||
auto dir = AModule::getScrollDir(e);
|
auto dir = AModule::getScrollDir(e);
|
||||||
if (dir == SCROLL_DIR::NONE) {
|
if (dir == SCROLL_DIR::NONE) {
|
||||||
return true;
|
return true;
|
||||||
@@ -1195,15 +1206,15 @@ bool Workspaces::handleScroll(GdkEventScroll* e) {
|
|||||||
|
|
||||||
if (dir == SCROLL_DIR::DOWN || dir == SCROLL_DIR::RIGHT) {
|
if (dir == SCROLL_DIR::DOWN || dir == SCROLL_DIR::RIGHT) {
|
||||||
if (allOutputs()) {
|
if (allOutputs()) {
|
||||||
m_ipc.getSocket1Reply("dispatch workspace e+1");
|
IPC::dispatch("workspace", "e+1");
|
||||||
} else {
|
} else {
|
||||||
m_ipc.getSocket1Reply("dispatch workspace m+1");
|
IPC::dispatch("workspace", "m+1");
|
||||||
}
|
}
|
||||||
} else if (dir == SCROLL_DIR::UP || dir == SCROLL_DIR::LEFT) {
|
} else if (dir == SCROLL_DIR::UP || dir == SCROLL_DIR::LEFT) {
|
||||||
if (allOutputs()) {
|
if (allOutputs()) {
|
||||||
m_ipc.getSocket1Reply("dispatch workspace e-1");
|
IPC::dispatch("workspace", "e-1");
|
||||||
} else {
|
} else {
|
||||||
m_ipc.getSocket1Reply("dispatch workspace m-1");
|
IPC::dispatch("workspace", "m-1");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,9 +46,11 @@ void waybar::modules::Image::delayWorker() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void waybar::modules::Image::refresh(int sig) {
|
void waybar::modules::Image::refresh(int sig) {
|
||||||
|
#ifdef SIGRTMIN
|
||||||
if (config_["signal"].isInt() && sig == SIGRTMIN + config_["signal"].asInt()) {
|
if (config_["signal"].isInt() && sig == SIGRTMIN + config_["signal"].asInt()) {
|
||||||
thread_.wake_up();
|
thread_.wake_up();
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
auto waybar::modules::Image::update() -> void {
|
auto waybar::modules::Image::update() -> void {
|
||||||
|
|||||||
@@ -1,11 +1,32 @@
|
|||||||
#include "modules/memory.hpp"
|
#include "modules/memory.hpp"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
const std::unordered_map<std::string, float> kUnits = {
|
||||||
|
{"kB", 1.000},
|
||||||
|
{"kiB", 1.024},
|
||||||
|
{"MB", 1.000 * 1000.0},
|
||||||
|
{"MiB", 1.024 * 1024.0},
|
||||||
|
{"GB", 1.000 * 1000.0 * 1000.0},
|
||||||
|
{"GiB", 1.024 * 1024.0 * 1024.0},
|
||||||
|
{"TB", 1.000 * 1000.0 * 1000.0 * 1000.0},
|
||||||
|
{"TiB", 1.024 * 1024.0 * 1024.0 * 1024.0}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
waybar::modules::Memory::Memory(const std::string& id, const Json::Value& config)
|
waybar::modules::Memory::Memory(const std::string& id, const Json::Value& config)
|
||||||
: ALabel(config, "memory", id, "{}%", 30) {
|
: ALabel(config, "memory", id, "{}%", 30) {
|
||||||
thread_ = [this] {
|
thread_ = [this] {
|
||||||
dp.emit();
|
dp.emit();
|
||||||
thread_.sleep_for(interval_);
|
thread_.sleep_for(interval_);
|
||||||
};
|
};
|
||||||
|
if (config["unit"].isString()) {
|
||||||
|
unit_ = config["unit"].asString();
|
||||||
|
if (!kUnits.contains(unit_)) {
|
||||||
|
unit_ = "GiB";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
unit_ = "GiB";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto waybar::modules::Memory::update() -> void {
|
auto waybar::modules::Memory::update() -> void {
|
||||||
@@ -13,15 +34,15 @@ auto waybar::modules::Memory::update() -> void {
|
|||||||
|
|
||||||
unsigned long memtotal = meminfo_["MemTotal"];
|
unsigned long memtotal = meminfo_["MemTotal"];
|
||||||
unsigned long swaptotal = 0;
|
unsigned long swaptotal = 0;
|
||||||
if (meminfo_.count("SwapTotal")) {
|
if (meminfo_.contains("SwapTotal")) {
|
||||||
swaptotal = meminfo_["SwapTotal"];
|
swaptotal = meminfo_["SwapTotal"];
|
||||||
}
|
}
|
||||||
unsigned long memfree;
|
unsigned long memfree;
|
||||||
unsigned long swapfree = 0;
|
unsigned long swapfree = 0;
|
||||||
if (meminfo_.count("SwapFree")) {
|
if (meminfo_.contains("SwapFree")) {
|
||||||
swapfree = meminfo_["SwapFree"];
|
swapfree = meminfo_["SwapFree"];
|
||||||
}
|
}
|
||||||
if (meminfo_.count("MemAvailable")) {
|
if (meminfo_.contains("MemAvailable")) {
|
||||||
// New kernels (3.4+) have an accurate available memory field.
|
// New kernels (3.4+) have an accurate available memory field.
|
||||||
memfree = meminfo_["MemAvailable"] + meminfo_["zfs_size"];
|
memfree = meminfo_["MemAvailable"] + meminfo_["zfs_size"];
|
||||||
} else {
|
} else {
|
||||||
@@ -31,18 +52,19 @@ auto waybar::modules::Memory::update() -> void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (memtotal > 0 && memfree >= 0) {
|
if (memtotal > 0 && memfree >= 0) {
|
||||||
float total_ram_gigabytes =
|
|
||||||
0.01 * round(memtotal / 10485.76); // 100*10485.76 = 2^20 = 1024^2 = GiB/KiB
|
|
||||||
float total_swap_gigabytes = 0.01 * round(swaptotal / 10485.76);
|
|
||||||
int used_ram_percentage = 100 * (memtotal - memfree) / memtotal;
|
int used_ram_percentage = 100 * (memtotal - memfree) / memtotal;
|
||||||
int used_swap_percentage = 0;
|
int used_swap_percentage = 0;
|
||||||
if (swaptotal) {
|
if ((bool) swaptotal) {
|
||||||
used_swap_percentage = 100 * (swaptotal - swapfree) / swaptotal;
|
used_swap_percentage = 100 * (swaptotal - swapfree) / swaptotal;
|
||||||
}
|
}
|
||||||
float used_ram_gigabytes = 0.01 * round((memtotal - memfree) / 10485.76);
|
|
||||||
float used_swap_gigabytes = 0.01 * round((swaptotal - swapfree) / 10485.76);
|
float divisor = kUnits.at(unit_);
|
||||||
float available_ram_gigabytes = 0.01 * round(memfree / 10485.76);
|
float total_ram = memtotal / divisor;
|
||||||
float available_swap_gigabytes = 0.01 * round(swapfree / 10485.76);
|
float total_swap = swaptotal / divisor;
|
||||||
|
float used_ram = (memtotal - memfree) / divisor;
|
||||||
|
float used_swap = (swaptotal - swapfree) / divisor;
|
||||||
|
float available_ram = memfree / divisor;
|
||||||
|
float available_swap = swapfree / divisor;
|
||||||
|
|
||||||
auto format = format_;
|
auto format = format_;
|
||||||
auto state = getState(used_ram_percentage);
|
auto state = getState(used_ram_percentage);
|
||||||
@@ -58,12 +80,12 @@ auto waybar::modules::Memory::update() -> void {
|
|||||||
label_.set_markup(fmt::format(
|
label_.set_markup(fmt::format(
|
||||||
fmt::runtime(format), used_ram_percentage,
|
fmt::runtime(format), used_ram_percentage,
|
||||||
fmt::arg("icon", getIcon(used_ram_percentage, icons)),
|
fmt::arg("icon", getIcon(used_ram_percentage, icons)),
|
||||||
fmt::arg("total", total_ram_gigabytes), fmt::arg("swapTotal", total_swap_gigabytes),
|
fmt::arg("total", total_ram), fmt::arg("swapTotal", total_swap),
|
||||||
fmt::arg("percentage", used_ram_percentage),
|
fmt::arg("percentage", used_ram_percentage),
|
||||||
fmt::arg("swapState", swaptotal == 0 ? "Off" : "On"),
|
fmt::arg("swapState", swaptotal == 0 ? "Off" : "On"),
|
||||||
fmt::arg("swapPercentage", used_swap_percentage), fmt::arg("used", used_ram_gigabytes),
|
fmt::arg("swapPercentage", used_swap_percentage), fmt::arg("used", used_ram),
|
||||||
fmt::arg("swapUsed", used_swap_gigabytes), fmt::arg("avail", available_ram_gigabytes),
|
fmt::arg("swapUsed", used_swap), fmt::arg("avail", available_ram),
|
||||||
fmt::arg("swapAvail", available_swap_gigabytes)));
|
fmt::arg("swapAvail", available_swap)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tooltipEnabled()) {
|
if (tooltipEnabled()) {
|
||||||
@@ -71,14 +93,14 @@ auto waybar::modules::Memory::update() -> void {
|
|||||||
auto tooltip_format = config_["tooltip-format"].asString();
|
auto tooltip_format = config_["tooltip-format"].asString();
|
||||||
label_.set_tooltip_markup(fmt::format(
|
label_.set_tooltip_markup(fmt::format(
|
||||||
fmt::runtime(tooltip_format), used_ram_percentage,
|
fmt::runtime(tooltip_format), used_ram_percentage,
|
||||||
fmt::arg("total", total_ram_gigabytes), fmt::arg("swapTotal", total_swap_gigabytes),
|
fmt::arg("total", total_ram), fmt::arg("swapTotal", total_swap),
|
||||||
fmt::arg("percentage", used_ram_percentage),
|
fmt::arg("percentage", used_ram_percentage),
|
||||||
fmt::arg("swapState", swaptotal == 0 ? "Off" : "On"),
|
fmt::arg("swapState", swaptotal == 0 ? "Off" : "On"),
|
||||||
fmt::arg("swapPercentage", used_swap_percentage), fmt::arg("used", used_ram_gigabytes),
|
fmt::arg("swapPercentage", used_swap_percentage), fmt::arg("used", used_ram),
|
||||||
fmt::arg("swapUsed", used_swap_gigabytes), fmt::arg("avail", available_ram_gigabytes),
|
fmt::arg("swapUsed", used_swap), fmt::arg("avail", available_ram),
|
||||||
fmt::arg("swapAvail", available_swap_gigabytes)));
|
fmt::arg("swapAvail", available_swap)));
|
||||||
} else {
|
} else {
|
||||||
label_.set_tooltip_markup(fmt::format("{:.{}f}GiB used", used_ram_gigabytes, 1));
|
label_.set_tooltip_markup(fmt::format("{:.{}f}{} used", used_ram, 1, unit_));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+51
-40
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
#include <system_error>
|
#include <system_error>
|
||||||
#include <util/sanitize_str.hpp>
|
#include <util/sanitize_str.hpp>
|
||||||
|
#include <util/utf8_string.hpp>
|
||||||
using namespace waybar::util;
|
using namespace waybar::util;
|
||||||
|
|
||||||
#include "modules/mpd/state.hpp"
|
#include "modules/mpd/state.hpp"
|
||||||
@@ -24,7 +25,8 @@ waybar::modules::MPD::MPD(const std::string& id, const Json::Value& config)
|
|||||||
timeout_(config_["timeout"].isUInt() ? config_["timeout"].asUInt() * 1'000 : 30'000),
|
timeout_(config_["timeout"].isUInt() ? config_["timeout"].asUInt() * 1'000 : 30'000),
|
||||||
connection_(nullptr, &mpd_connection_free),
|
connection_(nullptr, &mpd_connection_free),
|
||||||
status_(nullptr, &mpd_status_free),
|
status_(nullptr, &mpd_status_free),
|
||||||
song_(nullptr, &mpd_song_free) {
|
song_(nullptr, &mpd_song_free),
|
||||||
|
ellipsis_(config_["ellipsis"].isString() ? config_["ellipsis"].asString() : "\u2026") {
|
||||||
if (!config_["port"].isNull() && !config_["port"].isUInt()) {
|
if (!config_["port"].isNull() && !config_["port"].isUInt()) {
|
||||||
spdlog::warn("{}: `port` configuration should be an unsigned int", module_name_);
|
spdlog::warn("{}: `port` configuration should be an unsigned int", module_name_);
|
||||||
}
|
}
|
||||||
@@ -51,21 +53,6 @@ auto waybar::modules::MPD::update() -> void {
|
|||||||
ALabel::update();
|
ALabel::update();
|
||||||
}
|
}
|
||||||
|
|
||||||
void waybar::modules::MPD::queryMPD() {
|
|
||||||
if (connection_ != nullptr) {
|
|
||||||
spdlog::trace("{}: fetching state information", module_name_);
|
|
||||||
try {
|
|
||||||
fetchState();
|
|
||||||
spdlog::trace("{}: fetch complete", module_name_);
|
|
||||||
} catch (std::exception const& e) {
|
|
||||||
spdlog::error("{}: {}", module_name_, e.what());
|
|
||||||
state_ = MPD_STATE_UNKNOWN;
|
|
||||||
}
|
|
||||||
|
|
||||||
dp.emit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string waybar::modules::MPD::getTag(mpd_tag_type type, unsigned idx) const {
|
std::string waybar::modules::MPD::getTag(mpd_tag_type type, unsigned idx) const {
|
||||||
std::string result =
|
std::string result =
|
||||||
config_["unknown-tag"].isString() ? config_["unknown-tag"].asString() : "N/A";
|
config_["unknown-tag"].isString() ? config_["unknown-tag"].asString() : "N/A";
|
||||||
@@ -98,7 +85,7 @@ void waybar::modules::MPD::setLabel() {
|
|||||||
? config_["format-disconnected"].asString()
|
? config_["format-disconnected"].asString()
|
||||||
: "disconnected";
|
: "disconnected";
|
||||||
if (format.empty()) {
|
if (format.empty()) {
|
||||||
label_.set_markup(format);
|
setLabelMarkup(format);
|
||||||
label_.show();
|
label_.show();
|
||||||
} else {
|
} else {
|
||||||
label_.hide();
|
label_.hide();
|
||||||
@@ -110,22 +97,22 @@ void waybar::modules::MPD::setLabel() {
|
|||||||
? config_["tooltip-format-disconnected"].asString()
|
? config_["tooltip-format-disconnected"].asString()
|
||||||
: "MPD (disconnected)";
|
: "MPD (disconnected)";
|
||||||
// Nothing to format
|
// Nothing to format
|
||||||
label_.set_tooltip_markup(tooltip_format);
|
setTooltipMarkup(tooltip_format);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
label_.get_style_context()->remove_class("disconnected");
|
label_.get_style_context()->remove_class("disconnected");
|
||||||
|
|
||||||
auto format = format_;
|
auto format = format_;
|
||||||
Glib::ustring artist, album_artist, album, title;
|
|
||||||
std::string date, filename, uri;
|
std::string date, filename, uri;
|
||||||
int song_pos = 0, queue_length = 0, volume = 0;
|
int song_pos = 0, queue_length = 0, volume = 0;
|
||||||
std::chrono::seconds elapsedTime, totalTime;
|
std::chrono::seconds elapsedTime, totalTime;
|
||||||
|
|
||||||
std::string stateIcon = "";
|
std::string stateIcon = "";
|
||||||
bool no_song = song_.get() == nullptr;
|
bool no_song = song_.get() == nullptr;
|
||||||
if (stopped() || no_song) {
|
bool is_stopped = stopped();
|
||||||
if (no_song) spdlog::warn("Bug in mpd: no current song but state is not stopped.");
|
if (is_stopped || no_song) {
|
||||||
|
if (no_song && !is_stopped) spdlog::warn("mpd: no current song while state is not stopped");
|
||||||
format =
|
format =
|
||||||
config_["format-stopped"].isString() ? config_["format-stopped"].asString() : "stopped";
|
config_["format-stopped"].isString() ? config_["format-stopped"].asString() : "stopped";
|
||||||
label_.get_style_context()->add_class("stopped");
|
label_.get_style_context()->add_class("stopped");
|
||||||
@@ -137,18 +124,15 @@ void waybar::modules::MPD::setLabel() {
|
|||||||
label_.get_style_context()->add_class("playing");
|
label_.get_style_context()->add_class("playing");
|
||||||
label_.get_style_context()->remove_class("paused");
|
label_.get_style_context()->remove_class("paused");
|
||||||
} else if (paused()) {
|
} else if (paused()) {
|
||||||
format = config_["format-paused"].isString() ? config_["format-paused"].asString()
|
if (config_["format-paused"].isString()) {
|
||||||
: config_["format"].asString();
|
format = config_["format-paused"].asString();
|
||||||
|
}
|
||||||
label_.get_style_context()->add_class("paused");
|
label_.get_style_context()->add_class("paused");
|
||||||
label_.get_style_context()->remove_class("playing");
|
label_.get_style_context()->remove_class("playing");
|
||||||
}
|
}
|
||||||
|
|
||||||
stateIcon = getStateIcon();
|
stateIcon = getStateIcon();
|
||||||
|
|
||||||
artist = sanitize_string(getTag(MPD_TAG_ARTIST));
|
|
||||||
album_artist = sanitize_string(getTag(MPD_TAG_ALBUM_ARTIST));
|
|
||||||
album = sanitize_string(getTag(MPD_TAG_ALBUM));
|
|
||||||
title = sanitize_string(getTag(MPD_TAG_TITLE));
|
|
||||||
date = sanitize_string(getTag(MPD_TAG_DATE));
|
date = sanitize_string(getTag(MPD_TAG_DATE));
|
||||||
filename = sanitize_string(getFilename());
|
filename = sanitize_string(getFilename());
|
||||||
uri = mpd_song_get_uri(song_.get());
|
uri = mpd_song_get_uri(song_.get());
|
||||||
@@ -170,17 +154,12 @@ void waybar::modules::MPD::setLabel() {
|
|||||||
std::string repeatIcon = getOptionIcon("repeat", repeatActivated);
|
std::string repeatIcon = getOptionIcon("repeat", repeatActivated);
|
||||||
bool singleActivated = mpd_status_get_single(status_.get());
|
bool singleActivated = mpd_status_get_single(status_.get());
|
||||||
std::string singleIcon = getOptionIcon("single", singleActivated);
|
std::string singleIcon = getOptionIcon("single", singleActivated);
|
||||||
if (config_["artist-len"].isInt()) artist = artist.substr(0, config_["artist-len"].asInt());
|
|
||||||
if (config_["album-artist-len"].isInt())
|
|
||||||
album_artist = album_artist.substr(0, config_["album-artist-len"].asInt());
|
|
||||||
if (config_["album-len"].isInt()) album = album.substr(0, config_["album-len"].asInt());
|
|
||||||
if (config_["title-len"].isInt()) title = title.substr(0, config_["title-len"].asInt());
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
auto text = fmt::format(
|
auto text = fmt::format(
|
||||||
fmt::runtime(format), fmt::arg("artist", artist.raw()),
|
fmt::runtime(format), fmt::arg("artist", getArtistStr(true)),
|
||||||
fmt::arg("albumArtist", album_artist.raw()), fmt::arg("album", album.raw()),
|
fmt::arg("albumArtist", getAlbumArtistStr(true)), fmt::arg("album", getAlbumStr(true)),
|
||||||
fmt::arg("title", title.raw()), fmt::arg("date", date), fmt::arg("volume", volume),
|
fmt::arg("title", getTitleStr(true)), fmt::arg("date", date), fmt::arg("volume", volume),
|
||||||
fmt::arg("elapsedTime", elapsedTime), fmt::arg("totalTime", totalTime),
|
fmt::arg("elapsedTime", elapsedTime), fmt::arg("totalTime", totalTime),
|
||||||
fmt::arg("songPosition", song_pos), fmt::arg("queueLength", queue_length),
|
fmt::arg("songPosition", song_pos), fmt::arg("queueLength", queue_length),
|
||||||
fmt::arg("stateIcon", stateIcon), fmt::arg("consumeIcon", consumeIcon),
|
fmt::arg("stateIcon", stateIcon), fmt::arg("consumeIcon", consumeIcon),
|
||||||
@@ -190,7 +169,7 @@ void waybar::modules::MPD::setLabel() {
|
|||||||
label_.hide();
|
label_.hide();
|
||||||
} else {
|
} else {
|
||||||
label_.show();
|
label_.show();
|
||||||
label_.set_markup(text);
|
setLabelMarkup(text);
|
||||||
}
|
}
|
||||||
} catch (fmt::format_error const& e) {
|
} catch (fmt::format_error const& e) {
|
||||||
spdlog::warn("mpd: format error: {}", e.what());
|
spdlog::warn("mpd: format error: {}", e.what());
|
||||||
@@ -202,15 +181,15 @@ void waybar::modules::MPD::setLabel() {
|
|||||||
: "MPD (connected)";
|
: "MPD (connected)";
|
||||||
try {
|
try {
|
||||||
auto tooltip_text = fmt::format(
|
auto tooltip_text = fmt::format(
|
||||||
fmt::runtime(tooltip_format), fmt::arg("artist", artist.raw()),
|
fmt::runtime(tooltip_format), fmt::arg("artist", getArtistStr(false)),
|
||||||
fmt::arg("albumArtist", album_artist.raw()), fmt::arg("album", album.raw()),
|
fmt::arg("albumArtist", getAlbumArtistStr(false)), fmt::arg("album", getAlbumStr(false)),
|
||||||
fmt::arg("title", title.raw()), fmt::arg("date", date), fmt::arg("volume", volume),
|
fmt::arg("title", getTitleStr(false)), fmt::arg("date", date), fmt::arg("volume", volume),
|
||||||
fmt::arg("elapsedTime", elapsedTime), fmt::arg("totalTime", totalTime),
|
fmt::arg("elapsedTime", elapsedTime), fmt::arg("totalTime", totalTime),
|
||||||
fmt::arg("songPosition", song_pos), fmt::arg("queueLength", queue_length),
|
fmt::arg("songPosition", song_pos), fmt::arg("queueLength", queue_length),
|
||||||
fmt::arg("stateIcon", stateIcon), fmt::arg("consumeIcon", consumeIcon),
|
fmt::arg("stateIcon", stateIcon), fmt::arg("consumeIcon", consumeIcon),
|
||||||
fmt::arg("randomIcon", randomIcon), fmt::arg("repeatIcon", repeatIcon),
|
fmt::arg("randomIcon", randomIcon), fmt::arg("repeatIcon", repeatIcon),
|
||||||
fmt::arg("singleIcon", singleIcon), fmt::arg("filename", filename), fmt::arg("uri", uri));
|
fmt::arg("singleIcon", singleIcon), fmt::arg("filename", filename), fmt::arg("uri", uri));
|
||||||
label_.set_tooltip_markup(tooltip_text);
|
setTooltipMarkup(tooltip_text);
|
||||||
} catch (fmt::format_error const& e) {
|
} catch (fmt::format_error const& e) {
|
||||||
spdlog::warn("mpd: format error (tooltip): {}", e.what());
|
spdlog::warn("mpd: format error (tooltip): {}", e.what());
|
||||||
}
|
}
|
||||||
@@ -257,6 +236,38 @@ std::string waybar::modules::MPD::getOptionIcon(const std::string& optionName,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string waybar::modules::MPD::getArtistStr(bool truncated) const {
|
||||||
|
std::string artist = getTag(MPD_TAG_ARTIST);
|
||||||
|
if (truncated && config_["artist-len"].isInt()) {
|
||||||
|
waybar::util::utf8_truncate(artist, ellipsis_, config_["artist-len"].asInt());
|
||||||
|
}
|
||||||
|
return sanitize_string(artist);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string waybar::modules::MPD::getAlbumArtistStr(bool truncated) const {
|
||||||
|
std::string album_artist = getTag(MPD_TAG_ALBUM_ARTIST);
|
||||||
|
if (truncated && config_["album-artist-len"].isInt()) {
|
||||||
|
waybar::util::utf8_truncate(album_artist, ellipsis_, config_["album-artist-len"].asInt());
|
||||||
|
}
|
||||||
|
return sanitize_string(album_artist);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string waybar::modules::MPD::getAlbumStr(bool truncated) const {
|
||||||
|
std::string album = getTag(MPD_TAG_ALBUM);
|
||||||
|
if (truncated && config_["album-len"].isInt()) {
|
||||||
|
waybar::util::utf8_truncate(album, ellipsis_, config_["album-len"].asInt());
|
||||||
|
}
|
||||||
|
return sanitize_string(album);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string waybar::modules::MPD::getTitleStr(bool truncated) const {
|
||||||
|
std::string title = getTag(MPD_TAG_TITLE);
|
||||||
|
if (truncated && config_["title-len"].isInt()) {
|
||||||
|
waybar::util::utf8_truncate(title, ellipsis_, config_["title-len"].asInt());
|
||||||
|
}
|
||||||
|
return sanitize_string(title);
|
||||||
|
}
|
||||||
|
|
||||||
static bool isServerUnavailable(const std::error_code& ec) {
|
static bool isServerUnavailable(const std::error_code& ec) {
|
||||||
if (ec.category() == std::system_category()) {
|
if (ec.category() == std::system_category()) {
|
||||||
switch (ec.value()) {
|
switch (ec.value()) {
|
||||||
|
|||||||
@@ -152,7 +152,6 @@ bool Playing::on_timer() {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx_->queryMPD();
|
|
||||||
ctx_->emit();
|
ctx_->emit();
|
||||||
} catch (std::exception const& e) {
|
} catch (std::exception const& e) {
|
||||||
spdlog::warn("mpd: Playing: error: {}", e.what());
|
spdlog::warn("mpd: Playing: error: {}", e.what());
|
||||||
|
|||||||
+14
-57
@@ -7,6 +7,7 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "util/scope_guard.hpp"
|
#include "util/scope_guard.hpp"
|
||||||
|
#include "util/utf8_string.hpp"
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include <playerctl/playerctl.h>
|
#include <playerctl/playerctl.h>
|
||||||
@@ -202,69 +203,22 @@ auto Mpris::getIconFromJson(const Json::Value& icons, const std::string& key) ->
|
|||||||
// Wide characters count as two, zero-width characters count as zero
|
// Wide characters count as two, zero-width characters count as zero
|
||||||
// Modifies str in-place (unless width = std::string::npos)
|
// Modifies str in-place (unless width = std::string::npos)
|
||||||
// Returns the total width of the string pre-truncating
|
// Returns the total width of the string pre-truncating
|
||||||
size_t utf8_truncate(std::string& str, size_t width = std::string::npos) {
|
|
||||||
if (str.length() == 0) return 0;
|
|
||||||
|
|
||||||
const gchar* trunc_end = nullptr;
|
|
||||||
|
|
||||||
size_t total_width = 0;
|
|
||||||
|
|
||||||
for (gchar *data = str.data(), *end = data + str.size(); data != nullptr;) {
|
|
||||||
gunichar c = g_utf8_get_char_validated(data, end - data);
|
|
||||||
if (c == -1U || c == -2U) {
|
|
||||||
// invalid unicode, treat string as ascii
|
|
||||||
if (width != std::string::npos && str.length() > width) str.resize(width);
|
|
||||||
return str.length();
|
|
||||||
} else if (g_unichar_iswide(c)) {
|
|
||||||
total_width += 2;
|
|
||||||
} else if (!g_unichar_iszerowidth(c) && c != 0xAD) { // neither zero-width nor soft hyphen
|
|
||||||
total_width += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
data = g_utf8_find_next_char(data, end);
|
|
||||||
if (width != std::string::npos && total_width <= width && !g_unichar_isspace(c))
|
|
||||||
trunc_end = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (trunc_end) str.resize(trunc_end - str.data());
|
|
||||||
|
|
||||||
return total_width;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t utf8_width(const std::string& str) { return utf8_truncate(const_cast<std::string&>(str)); }
|
|
||||||
|
|
||||||
void truncate(std::string& s, const std::string& ellipsis, size_t max_len) {
|
|
||||||
if (max_len == 0) {
|
|
||||||
s.resize(0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
size_t len = utf8_truncate(s, max_len);
|
|
||||||
if (len > max_len) {
|
|
||||||
size_t ellipsis_len = utf8_width(ellipsis);
|
|
||||||
if (max_len >= ellipsis_len) {
|
|
||||||
if (ellipsis_len) utf8_truncate(s, max_len - ellipsis_len);
|
|
||||||
s += ellipsis;
|
|
||||||
} else {
|
|
||||||
s.resize(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Mpris::getArtistStr(const PlayerInfo& info, bool truncated) -> std::string {
|
auto Mpris::getArtistStr(const PlayerInfo& info, bool truncated) -> std::string {
|
||||||
auto artist = info.artist.value_or(std::string());
|
auto artist = info.artist.value_or(std::string());
|
||||||
if (truncated && artist_len_ >= 0) truncate(artist, ellipsis_, artist_len_);
|
if (truncated && artist_len_ >= 0) waybar::util::utf8_truncate(artist, ellipsis_, artist_len_);
|
||||||
return artist;
|
return artist;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Mpris::getAlbumStr(const PlayerInfo& info, bool truncated) -> std::string {
|
auto Mpris::getAlbumStr(const PlayerInfo& info, bool truncated) -> std::string {
|
||||||
auto album = info.album.value_or(std::string());
|
auto album = info.album.value_or(std::string());
|
||||||
if (truncated && album_len_ >= 0) truncate(album, ellipsis_, album_len_);
|
if (truncated && album_len_ >= 0) waybar::util::utf8_truncate(album, ellipsis_, album_len_);
|
||||||
return album;
|
return album;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Mpris::getTitleStr(const PlayerInfo& info, bool truncated) -> std::string {
|
auto Mpris::getTitleStr(const PlayerInfo& info, bool truncated) -> std::string {
|
||||||
auto title = info.title.value_or(std::string());
|
auto title = info.title.value_or(std::string());
|
||||||
if (truncated && title_len_ >= 0) truncate(title, ellipsis_, title_len_);
|
if (truncated && title_len_ >= 0) waybar::util::utf8_truncate(title, ellipsis_, title_len_);
|
||||||
return title;
|
return title;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,9 +246,9 @@ auto Mpris::getDynamicStr(const PlayerInfo& info, bool truncated, bool html) ->
|
|||||||
// keep position format same as length format
|
// keep position format same as length format
|
||||||
auto position = getPositionStr(info, truncated && truncate_hours_ && length.length() < 6);
|
auto position = getPositionStr(info, truncated && truncate_hours_ && length.length() < 6);
|
||||||
|
|
||||||
size_t artistLen = utf8_width(artist);
|
size_t artistLen = waybar::util::utf8_width(artist);
|
||||||
size_t albumLen = utf8_width(album);
|
size_t albumLen = waybar::util::utf8_width(album);
|
||||||
size_t titleLen = utf8_width(title);
|
size_t titleLen = waybar::util::utf8_width(title);
|
||||||
size_t lengthLen = length.length();
|
size_t lengthLen = length.length();
|
||||||
size_t posLen = position.length();
|
size_t posLen = position.length();
|
||||||
|
|
||||||
@@ -313,7 +267,7 @@ auto Mpris::getDynamicStr(const PlayerInfo& info, bool truncated, bool html) ->
|
|||||||
// Since the first element doesn't present a separator and we don't know a priori which one
|
// Since the first element doesn't present a separator and we don't know a priori which one
|
||||||
// it will be, we add a "virtual separatorLen" to the dynamicLen, since we are adding the
|
// it will be, we add a "virtual separatorLen" to the dynamicLen, since we are adding the
|
||||||
// separatorLen to all the other lengths.
|
// separatorLen to all the other lengths.
|
||||||
size_t separatorLen = utf8_width(dynamic_separator_);
|
size_t separatorLen = waybar::util::utf8_width(dynamic_separator_);
|
||||||
size_t dynamicLen = dynamic_len_ + separatorLen;
|
size_t dynamicLen = dynamic_len_ + separatorLen;
|
||||||
if (showArtist) artistLen += separatorLen;
|
if (showArtist) artistLen += separatorLen;
|
||||||
if (showAlbum) albumLen += separatorLen;
|
if (showAlbum) albumLen += separatorLen;
|
||||||
@@ -775,9 +729,12 @@ auto Mpris::update() -> void {
|
|||||||
auto tooltip_text = fmt::format(
|
auto tooltip_text = fmt::format(
|
||||||
fmt::runtime(tooltipstr), fmt::arg("player", info.name),
|
fmt::runtime(tooltipstr), fmt::arg("player", info.name),
|
||||||
fmt::arg("status", info.status_string),
|
fmt::arg("status", info.status_string),
|
||||||
fmt::arg("artist", getArtistStr(info, tooltip_len_limits_)),
|
fmt::arg("artist",
|
||||||
fmt::arg("title", getTitleStr(info, tooltip_len_limits_)),
|
std::string(Glib::Markup::escape_text(getArtistStr(info, tooltip_len_limits_)))),
|
||||||
fmt::arg("album", getAlbumStr(info, tooltip_len_limits_)),
|
fmt::arg("title",
|
||||||
|
std::string(Glib::Markup::escape_text(getTitleStr(info, tooltip_len_limits_)))),
|
||||||
|
fmt::arg("album",
|
||||||
|
std::string(Glib::Markup::escape_text(getAlbumStr(info, tooltip_len_limits_)))),
|
||||||
fmt::arg("length", tooltipLength), fmt::arg("position", tooltipPosition),
|
fmt::arg("length", tooltipLength), fmt::arg("position", tooltipPosition),
|
||||||
fmt::arg("dynamic", getDynamicStr(info, tooltip_len_limits_, false)),
|
fmt::arg("dynamic", getDynamicStr(info, tooltip_len_limits_, false)),
|
||||||
fmt::arg("player_icon", getIconFromJson(config_["player-icons"], info.name)),
|
fmt::arg("player_icon", getIconFromJson(config_["player-icons"], info.name)),
|
||||||
|
|||||||
+23
-4
@@ -280,14 +280,22 @@ auto waybar::modules::Network::update() -> void {
|
|||||||
std::string tooltip_format;
|
std::string tooltip_format;
|
||||||
auto now = std::chrono::steady_clock::now();
|
auto now = std::chrono::steady_clock::now();
|
||||||
auto elapsed_seconds = std::chrono::duration<double>(now - bandwidth_last_sample_time_).count();
|
auto elapsed_seconds = std::chrono::duration<double>(now - bandwidth_last_sample_time_).count();
|
||||||
|
|
||||||
|
auto bandwidth_down = 0ull;
|
||||||
|
auto bandwidth_up = 0ull;
|
||||||
|
|
||||||
|
// Only recalculate bandwidth when enough time has elapsed since the last
|
||||||
|
// sample. Event-driven dp.emit() calls (link/addr/route changes) can
|
||||||
|
// trigger update() between timer intervals, which would consume the byte
|
||||||
|
// delta prematurely and show near-zero bandwidth.
|
||||||
|
auto min_elapsed = std::chrono::duration<double>(interval_).count() * 0.5;
|
||||||
|
if (elapsed_seconds >= min_elapsed) {
|
||||||
if (elapsed_seconds <= 0.0) {
|
if (elapsed_seconds <= 0.0) {
|
||||||
elapsed_seconds = std::chrono::duration<double>(interval_).count();
|
elapsed_seconds = std::chrono::duration<double>(interval_).count();
|
||||||
}
|
}
|
||||||
bandwidth_last_sample_time_ = now;
|
bandwidth_last_sample_time_ = now;
|
||||||
|
|
||||||
auto bandwidth = readBandwidthUsage();
|
auto bandwidth = readBandwidthUsage();
|
||||||
auto bandwidth_down = 0ull;
|
|
||||||
auto bandwidth_up = 0ull;
|
|
||||||
if (bandwidth.has_value()) {
|
if (bandwidth.has_value()) {
|
||||||
auto down_octets = (*bandwidth).first;
|
auto down_octets = (*bandwidth).first;
|
||||||
auto up_octets = (*bandwidth).second;
|
auto up_octets = (*bandwidth).second;
|
||||||
@@ -297,6 +305,14 @@ auto waybar::modules::Network::update() -> void {
|
|||||||
|
|
||||||
bandwidth_up = up_octets - bandwidth_up_total_;
|
bandwidth_up = up_octets - bandwidth_up_total_;
|
||||||
bandwidth_up_total_ = up_octets;
|
bandwidth_up_total_ = up_octets;
|
||||||
|
|
||||||
|
bandwidth_down_prev_ = bandwidth_down;
|
||||||
|
bandwidth_up_prev_ = bandwidth_up;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bandwidth_down = bandwidth_down_prev_;
|
||||||
|
bandwidth_up = bandwidth_up_prev_;
|
||||||
|
elapsed_seconds = std::chrono::duration<double>(interval_).count();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!alt_) {
|
if (!alt_) {
|
||||||
@@ -677,12 +693,15 @@ int waybar::modules::Network::handleEvents(struct nl_msg* msg, void* data) {
|
|||||||
changed_cidr);
|
changed_cidr);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if (ifa->ifa_family == AF_INET) {
|
||||||
net->ipaddr_.clear();
|
net->ipaddr_.clear();
|
||||||
net->ipaddr6_.clear();
|
|
||||||
net->cidr_ = 0;
|
net->cidr_ = 0;
|
||||||
net->cidr6_ = 0;
|
|
||||||
net->netmask_.clear();
|
net->netmask_.clear();
|
||||||
|
} else if (ifa->ifa_family == AF_INET6) {
|
||||||
|
net->ipaddr6_.clear();
|
||||||
|
net->cidr6_ = 0;
|
||||||
net->netmask6_.clear();
|
net->netmask6_.clear();
|
||||||
|
}
|
||||||
spdlog::debug("network: {} addr deleted {}/{}", net->ifname_,
|
spdlog::debug("network: {} addr deleted {}/{}", net->ifname_,
|
||||||
inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta), ipaddr, sizeof(ipaddr)),
|
inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta), ipaddr, sizeof(ipaddr)),
|
||||||
ifa->ifa_prefixlen);
|
ifa->ifa_prefixlen);
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ void Language::doUpdate() {
|
|||||||
|
|
||||||
spdlog::debug("niri language formatted layout name {}", layoutName);
|
spdlog::debug("niri language formatted layout name {}", layoutName);
|
||||||
|
|
||||||
if (!format_.empty()) {
|
if (!layoutName.empty()) {
|
||||||
label_.show();
|
label_.show();
|
||||||
label_.set_markup(layoutName);
|
label_.set_markup(layoutName);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -103,7 +103,9 @@ void Workspaces::doUpdate() {
|
|||||||
fmt::arg("output", ws["output"].asString()));
|
fmt::arg("output", ws["output"].asString()));
|
||||||
}
|
}
|
||||||
if (!config_["disable-markup"].asBool()) {
|
if (!config_["disable-markup"].asBool()) {
|
||||||
static_cast<Gtk::Label*>(button.get_children()[0])->set_markup(name);
|
auto* child = gtk_bin_get_child(GTK_BIN(button.gobj()));
|
||||||
|
if (child != nullptr && GTK_IS_LABEL(child))
|
||||||
|
gtk_label_set_markup(GTK_LABEL(child), name.c_str());
|
||||||
} else {
|
} else {
|
||||||
button.set_label(name);
|
button.set_label(name);
|
||||||
}
|
}
|
||||||
@@ -114,6 +116,11 @@ void Workspaces::doUpdate() {
|
|||||||
button.show();
|
button.show();
|
||||||
else
|
else
|
||||||
button.hide();
|
button.hide();
|
||||||
|
} else if (config_["hide-empty"].asBool()) {
|
||||||
|
if (ws["active_window_id"].isNull() && !ws["is_focused"].asBool())
|
||||||
|
button.hide();
|
||||||
|
else
|
||||||
|
button.show();
|
||||||
} else {
|
} else {
|
||||||
button.show();
|
button.show();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ Privacy::Privacy(const std::string& id, const Json::Value& config, Gtk::Orientat
|
|||||||
auto* item = Gtk::make_managed<PrivacyItem>(module, nodeType, nodePtr, orientation, pos,
|
auto* item = Gtk::make_managed<PrivacyItem>(module, nodeType, nodePtr, orientation, pos,
|
||||||
iconSize, transition_duration);
|
iconSize, transition_duration);
|
||||||
box_.add(*item);
|
box_.add(*item);
|
||||||
|
modules_.push_back(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,9 +149,7 @@ auto Privacy::update() -> void {
|
|||||||
bool useAudioOut = false;
|
bool useAudioOut = false;
|
||||||
|
|
||||||
mutex_.lock();
|
mutex_.lock();
|
||||||
for (Gtk::Widget* widget : box_.get_children()) {
|
for (PrivacyItem* module : modules_) {
|
||||||
auto* module = dynamic_cast<PrivacyItem*>(widget);
|
|
||||||
if (module == nullptr) continue;
|
|
||||||
switch (module->privacy_type) {
|
switch (module->privacy_type) {
|
||||||
case util::PipewireBackend::PRIVACY_NODE_TYPE_VIDEO_INPUT:
|
case util::PipewireBackend::PRIVACY_NODE_TYPE_VIDEO_INPUT:
|
||||||
setScreenshare = true;
|
setScreenshare = true;
|
||||||
|
|||||||
@@ -10,73 +10,51 @@ PulseaudioSlider::PulseaudioSlider(const std::string& id, const Json::Value& con
|
|||||||
if (config_["target"].isString()) {
|
if (config_["target"].isString()) {
|
||||||
std::string target = config_["target"].asString();
|
std::string target = config_["target"].asString();
|
||||||
if (target == "sink") {
|
if (target == "sink") {
|
||||||
this->target = PulseaudioSliderTarget::Sink;
|
this->target = util::PulseaudioTarget::Sink;
|
||||||
} else if (target == "source") {
|
} else if (target == "source") {
|
||||||
this->target = PulseaudioSliderTarget::Source;
|
this->target = util::PulseaudioTarget::Source;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (config_["zero-on-mute"].isBool()) {
|
||||||
|
zero_on_mute = config_["zero-on-mute"].asBool();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config_["unmute-on-volume-change"].isBool()) {
|
||||||
|
unmute_on_volume_change = config_["unmute-on-volume-change"].asBool();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PulseaudioSlider::update() {
|
void PulseaudioSlider::update() {
|
||||||
switch (target) {
|
uint16_t display_value = backend->getVolume(target);
|
||||||
case PulseaudioSliderTarget::Sink:
|
bool is_muted = backend->getMuted(target);
|
||||||
if (backend->getSinkMuted()) {
|
|
||||||
scale_.set_value(min_);
|
|
||||||
} else {
|
|
||||||
scale_.set_value(backend->getSinkVolume());
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PulseaudioSliderTarget::Source:
|
if (is_muted) {
|
||||||
if (backend->getSourceMuted()) {
|
if (zero_on_mute) {
|
||||||
scale_.set_value(min_);
|
display_value = min_;
|
||||||
} else {
|
|
||||||
scale_.set_value(backend->getSourceVolume());
|
|
||||||
}
|
}
|
||||||
break;
|
if (!previously_muted) {
|
||||||
|
scale_.get_style_context()->add_class("muted");
|
||||||
}
|
}
|
||||||
|
} else if (previously_muted) {
|
||||||
|
scale_.get_style_context()->remove_class("muted");
|
||||||
|
}
|
||||||
|
|
||||||
|
scale_.set_value(display_value);
|
||||||
|
|
||||||
|
previously_muted = is_muted;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PulseaudioSlider::onValueChanged() {
|
void PulseaudioSlider::onValueChanged() {
|
||||||
bool is_mute = false;
|
uint16_t slider_value = scale_.get_value();
|
||||||
|
|
||||||
switch (target) {
|
// Avoid setting sink/source to volume 0 if the user muted it via other means.
|
||||||
case PulseaudioSliderTarget::Sink:
|
if (!backend->getMuted(target) || slider_value != 0) {
|
||||||
if (backend->getSinkMuted()) {
|
if (unmute_on_volume_change) {
|
||||||
is_mute = true;
|
backend->unmute(target);
|
||||||
}
|
}
|
||||||
break;
|
backend->changeVolume(slider_value, min_, max_);
|
||||||
|
|
||||||
case PulseaudioSliderTarget::Source:
|
|
||||||
if (backend->getSourceMuted()) {
|
|
||||||
is_mute = true;
|
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t volume = scale_.get_value();
|
|
||||||
|
|
||||||
if (is_mute) {
|
|
||||||
// Avoid setting sink/source to volume 0 if the user muted if via another mean.
|
|
||||||
if (volume == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the sink/source is mute, but the user clicked the slider, unmute it!
|
|
||||||
else {
|
|
||||||
switch (target) {
|
|
||||||
case PulseaudioSliderTarget::Sink:
|
|
||||||
backend->toggleSinkMute(false);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PulseaudioSliderTarget::Source:
|
|
||||||
backend->toggleSourceMute(false);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
backend->changeVolume(volume, min_, max_);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace waybar::modules
|
} // namespace waybar::modules
|
||||||
@@ -6,6 +6,9 @@
|
|||||||
|
|
||||||
namespace waybar::modules::SNI {
|
namespace waybar::modules::SNI {
|
||||||
|
|
||||||
|
static const unsigned RETRY_DELAY_MS = 200;
|
||||||
|
static const unsigned MAX_RETRIES = 10;
|
||||||
|
|
||||||
Host::Host(const std::size_t id, const Json::Value& config, const Bar& bar,
|
Host::Host(const std::size_t id, const Json::Value& config, const Bar& bar,
|
||||||
const std::function<void(std::unique_ptr<Item>&)>& on_add,
|
const std::function<void(std::unique_ptr<Item>&)>& on_add,
|
||||||
const std::function<void(std::unique_ptr<Item>&)>& on_remove,
|
const std::function<void(std::unique_ptr<Item>&)>& on_remove,
|
||||||
@@ -22,6 +25,7 @@ Host::Host(const std::size_t id, const Json::Value& config, const Bar& bar,
|
|||||||
on_update_(on_update) {}
|
on_update_(on_update) {}
|
||||||
|
|
||||||
Host::~Host() {
|
Host::~Host() {
|
||||||
|
retry_connection_.disconnect();
|
||||||
if (bus_name_id_ > 0) {
|
if (bus_name_id_ > 0) {
|
||||||
Gio::DBus::unown_name(bus_name_id_);
|
Gio::DBus::unown_name(bus_name_id_);
|
||||||
bus_name_id_ = 0;
|
bus_name_id_ = 0;
|
||||||
@@ -53,6 +57,8 @@ void Host::nameAppeared(const Glib::RefPtr<Gio::DBus::Connection>& conn, const G
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Host::nameVanished(const Glib::RefPtr<Gio::DBus::Connection>& conn, const Glib::ustring name) {
|
void Host::nameVanished(const Glib::RefPtr<Gio::DBus::Connection>& conn, const Glib::ustring name) {
|
||||||
|
retry_connection_.disconnect();
|
||||||
|
retry_count_ = 0;
|
||||||
g_cancellable_cancel(cancellable_);
|
g_cancellable_cancel(cancellable_);
|
||||||
g_clear_object(&cancellable_);
|
g_clear_object(&cancellable_);
|
||||||
g_clear_object(&watcher_);
|
g_clear_object(&watcher_);
|
||||||
@@ -72,11 +78,30 @@ void Host::proxyReady(GObject* src, GAsyncResult* res, gpointer data) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto host = static_cast<SNI::Host*>(data);
|
auto host = static_cast<SNI::Host*>(data);
|
||||||
host->watcher_ = watcher;
|
|
||||||
if (error != nullptr) {
|
if (error != nullptr) {
|
||||||
spdlog::error("Host: {}", error->message);
|
spdlog::error("Host: {}", error->message);
|
||||||
|
g_clear_object(&host->cancellable_);
|
||||||
|
if (host->retry_count_ >= MAX_RETRIES) {
|
||||||
|
spdlog::warn("Host: giving up on watcher proxy creation after {} retries", host->retry_count_);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
host->retry_count_ += 1;
|
||||||
|
// Store the timeout connection so it is disconnected in ~Host, avoiding a
|
||||||
|
// use-after-free if the Host is destroyed before the retry fires.
|
||||||
|
host->retry_connection_ = Glib::signal_timeout().connect(
|
||||||
|
[host]() {
|
||||||
|
if (host->watcher_ != nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto conn = Gio::DBus::Connection::get_sync(Gio::DBus::BusType::BUS_TYPE_SESSION);
|
||||||
|
host->nameAppeared(conn, "org.kde.StatusNotifierWatcher", "");
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
RETRY_DELAY_MS);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
host->retry_count_ = 0;
|
||||||
|
host->watcher_ = watcher;
|
||||||
sn_watcher_call_register_host(host->watcher_, host->object_path_.c_str(), host->cancellable_,
|
sn_watcher_call_register_host(host->watcher_, host->object_path_.c_str(), host->cancellable_,
|
||||||
&Host::registerHost, data);
|
&Host::registerHost, data);
|
||||||
}
|
}
|
||||||
@@ -178,9 +203,11 @@ void Host::addRegisteredItem(const std::string& service) {
|
|||||||
return bus_name == item->bus_name && object_path == item->object_path;
|
return bus_name == item->bus_name && object_path == item->object_path;
|
||||||
});
|
});
|
||||||
if (it == items_.end()) {
|
if (it == items_.end()) {
|
||||||
items_.emplace_back(new Item(
|
items_.emplace_back(std::make_unique<Item>(
|
||||||
bus_name, object_path, config_, bar_, [this](Item& item) { itemReady(item); },
|
bus_name, object_path, config_, bar_,
|
||||||
[this](Item& item) { itemInvalidated(item); }, on_update_));
|
[this](Item& item) { itemReady(item); },
|
||||||
|
[this](Item& item) { itemInvalidated(item); },
|
||||||
|
on_update_));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -190,9 +190,17 @@ void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) {
|
|||||||
} else if (name == "Status") {
|
} else if (name == "Status") {
|
||||||
setStatus(get_variant<Glib::ustring>(value));
|
setStatus(get_variant<Glib::ustring>(value));
|
||||||
} else if (name == "IconName") {
|
} else if (name == "IconName") {
|
||||||
|
if (has_custom_icon_) {
|
||||||
|
spdlog::trace("Item '{}': ignoring IconName update, custom icon is set", id);
|
||||||
|
} else {
|
||||||
icon_name = get_variant<std::string>(value);
|
icon_name = get_variant<std::string>(value);
|
||||||
|
}
|
||||||
} else if (name == "IconPixmap") {
|
} else if (name == "IconPixmap") {
|
||||||
|
if (has_custom_icon_) {
|
||||||
|
spdlog::trace("Item '{}': ignoring IconPixmap update, custom icon is set", id);
|
||||||
|
} else {
|
||||||
icon_pixmap = this->extractPixBuf(value.gobj());
|
icon_pixmap = this->extractPixBuf(value.gobj());
|
||||||
|
}
|
||||||
} else if (name == "OverlayIconName") {
|
} else if (name == "OverlayIconName") {
|
||||||
overlay_icon_name = get_variant<std::string>(value);
|
overlay_icon_name = get_variant<std::string>(value);
|
||||||
} else if (name == "OverlayIconPixmap") {
|
} else if (name == "OverlayIconPixmap") {
|
||||||
@@ -270,11 +278,13 @@ void Item::setCustomIcon(const std::string& id) {
|
|||||||
Glib::RefPtr<Gdk::Pixbuf> custom_pixbuf = Gdk::Pixbuf::create_from_file(custom_icon);
|
Glib::RefPtr<Gdk::Pixbuf> custom_pixbuf = Gdk::Pixbuf::create_from_file(custom_icon);
|
||||||
icon_name = ""; // icon_name has priority over pixmap
|
icon_name = ""; // icon_name has priority over pixmap
|
||||||
icon_pixmap = custom_pixbuf;
|
icon_pixmap = custom_pixbuf;
|
||||||
|
has_custom_icon_ = true;
|
||||||
} catch (const Glib::Error& e) {
|
} catch (const Glib::Error& e) {
|
||||||
spdlog::error("Failed to load custom icon {}: {}", custom_icon, e.what());
|
spdlog::error("Failed to load custom icon {}: {}", custom_icon, e.what());
|
||||||
}
|
}
|
||||||
} else { // if file doesn't exist it's most likely an icon_name
|
} else { // if file doesn't exist it's most likely an icon_name
|
||||||
icon_name = custom_icon;
|
icon_name = custom_icon;
|
||||||
|
has_custom_icon_ = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,9 +24,6 @@ Tray::Tray(const std::string& id, const Bar& bar, const Json::Value& config)
|
|||||||
if (config_["spacing"].isUInt()) {
|
if (config_["spacing"].isUInt()) {
|
||||||
box_.set_spacing(config_["spacing"].asUInt());
|
box_.set_spacing(config_["spacing"].asUInt());
|
||||||
}
|
}
|
||||||
if (config["show-passive-items"].isBool()) {
|
|
||||||
show_passive_ = config["show-passive-items"].asBool();
|
|
||||||
}
|
|
||||||
nb_hosts_ += 1;
|
nb_hosts_ += 1;
|
||||||
if (config_["icons"].isObject()) {
|
if (config_["icons"].isObject()) {
|
||||||
IconManager::instance().setIconsConfig(config_["icons"]);
|
IconManager::instance().setIconsConfig(config_["icons"]);
|
||||||
@@ -42,6 +39,8 @@ void Tray::onAdd(std::unique_ptr<Item>& item) {
|
|||||||
} else {
|
} else {
|
||||||
box_.pack_start(item->event_box);
|
box_.pack_start(item->event_box);
|
||||||
}
|
}
|
||||||
|
item->event_box.signal_show().connect([this] { dp.emit(); });
|
||||||
|
item->event_box.signal_hide().connect([this] { dp.emit(); });
|
||||||
dp.emit();
|
dp.emit();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,17 +50,9 @@ void Tray::onRemove(std::unique_ptr<Item>& item) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto Tray::update() -> void {
|
auto Tray::update() -> void {
|
||||||
// Show tray only when items are available
|
|
||||||
std::vector<Gtk::Widget*> children = box_.get_children();
|
std::vector<Gtk::Widget*> children = box_.get_children();
|
||||||
if (show_passive_) {
|
event_box_.set_visible(std::any_of(children.begin(), children.end(),
|
||||||
event_box_.set_visible(!children.empty());
|
[](Gtk::Widget* child) { return child->get_visible(); }));
|
||||||
} else {
|
|
||||||
event_box_.set_visible(!std::all_of(children.begin(), children.end(), [](Gtk::Widget* child) {
|
|
||||||
return child->get_style_context()->has_class("passive");
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call parent update
|
|
||||||
AModule::update();
|
AModule::update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -54,21 +54,28 @@ void Language::onCmd(const struct Ipc::ipc_response& res) {
|
|||||||
std::lock_guard<std::mutex> lock(mutex_);
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
auto payload = parser_.parse(res.payload);
|
auto payload = parser_.parse(res.payload);
|
||||||
std::vector<std::string> used_layouts;
|
std::vector<std::string> used_layouts;
|
||||||
// Display current layout of a device with a maximum count of layouts, expecting that all will
|
// Union layout names across every keyboard input so hot-plugged devices contribute their
|
||||||
// be OK
|
// layouts to the map. Track the device with the most layouts to seed the initially displayed
|
||||||
|
// layout, matching the previous behaviour at startup.
|
||||||
Json::ArrayIndex max_id = 0, max = 0;
|
Json::ArrayIndex max_id = 0, max = 0;
|
||||||
for (Json::ArrayIndex i = 0; i < payload.size(); i++) {
|
for (Json::ArrayIndex i = 0; i < payload.size(); i++) {
|
||||||
auto size = payload[i][XKB_LAYOUT_NAMES_KEY].size();
|
if (payload[i]["type"].asString() != "keyboard") continue;
|
||||||
if (size > max) {
|
const auto& names = payload[i][XKB_LAYOUT_NAMES_KEY];
|
||||||
max = size;
|
if (names.size() > max) {
|
||||||
|
max = names.size();
|
||||||
max_id = i;
|
max_id = i;
|
||||||
}
|
}
|
||||||
|
for (const auto& layout : names) {
|
||||||
|
const auto name = layout.asString();
|
||||||
|
if (std::find(used_layouts.begin(), used_layouts.end(), name) == used_layouts.end()) {
|
||||||
|
used_layouts.push_back(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& layout : payload[max_id][XKB_LAYOUT_NAMES_KEY]) {
|
// Rebuild from scratch so init_layouts_map's duplicate-suffix pass doesn't compound across
|
||||||
used_layouts.push_back(layout.asString());
|
// refreshes (e.g. "us" -> "us1" -> "us11").
|
||||||
}
|
layouts_map_.clear();
|
||||||
|
|
||||||
init_layouts_map(used_layouts);
|
init_layouts_map(used_layouts);
|
||||||
set_current_layout(payload[max_id][XKB_ACTIVE_LAYOUT_NAME_KEY].asString());
|
set_current_layout(payload[max_id][XKB_ACTIVE_LAYOUT_NAME_KEY].asString());
|
||||||
dp.emit();
|
dp.emit();
|
||||||
@@ -82,13 +89,26 @@ void Language::onEvent(const struct Ipc::ipc_response& res) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool refresh_inputs = false;
|
||||||
try {
|
try {
|
||||||
|
{
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
auto payload = parser_.parse(res.payload)["input"];
|
auto root = parser_.parse(res.payload);
|
||||||
|
auto change = root["change"].asString();
|
||||||
|
auto payload = root["input"];
|
||||||
if (payload["type"].asString() == "keyboard") {
|
if (payload["type"].asString() == "keyboard") {
|
||||||
|
// A device was added or its keymap changed - the layout set may have grown, so refresh
|
||||||
|
// layouts_map_ via IPC_GET_INPUTS once we've released mutex_.
|
||||||
|
refresh_inputs = (change == "added" || change == "xkb_keymap");
|
||||||
set_current_layout(payload[XKB_ACTIVE_LAYOUT_NAME_KEY].asString());
|
set_current_layout(payload[XKB_ACTIVE_LAYOUT_NAME_KEY].asString());
|
||||||
}
|
}
|
||||||
dp.emit();
|
dp.emit();
|
||||||
|
}
|
||||||
|
// sendCmd is synchronous: it blocks on the IPC reply and then emits signal_cmd on this same
|
||||||
|
// thread, which lands in onCmd and re-locks mutex_. Must call it with mutex_ released.
|
||||||
|
if (refresh_inputs) {
|
||||||
|
ipc_.sendCmd(IPC_GET_INPUTS);
|
||||||
|
}
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
spdlog::error("Language: {}", e.what());
|
spdlog::error("Language: {}", e.what());
|
||||||
}
|
}
|
||||||
@@ -125,8 +145,15 @@ auto Language::update() -> void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto Language::set_current_layout(const std::string& current_layout) -> void {
|
auto Language::set_current_layout(const std::string& current_layout) -> void {
|
||||||
|
// Guard against unknown / empty layout names: transient virtual keyboards (e.g. wtype) and
|
||||||
|
// hot-plugged devices whose layouts haven't made it into the map yet would otherwise blank out
|
||||||
|
// layout_ via map::operator[]'s default-construct-on-miss.
|
||||||
|
auto it = layouts_map_.find(current_layout);
|
||||||
|
if (it == layouts_map_.end()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
label_.get_style_context()->remove_class(layout_.short_name);
|
label_.get_style_context()->remove_class(layout_.short_name);
|
||||||
layout_ = layouts_map_[current_layout];
|
layout_ = it->second;
|
||||||
label_.get_style_context()->add_class(layout_.short_name);
|
label_.get_style_context()->add_class(layout_.short_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -94,12 +94,12 @@ auto Window::update() -> void {
|
|||||||
old_app_id_ = app_id_;
|
old_app_id_ = app_id_;
|
||||||
}
|
}
|
||||||
|
|
||||||
label_.set_markup(waybar::util::rewriteString(
|
setLabelMarkup(waybar::util::rewriteString(
|
||||||
fmt::format(fmt::runtime(format_), fmt::arg("title", window_), fmt::arg("app_id", app_id_),
|
fmt::format(fmt::runtime(format_), fmt::arg("title", window_), fmt::arg("app_id", app_id_),
|
||||||
fmt::arg("shell", shell_), fmt::arg("marks", marks_)),
|
fmt::arg("shell", shell_), fmt::arg("marks", marks_)),
|
||||||
config_["rewrite"]));
|
config_["rewrite"]));
|
||||||
if (tooltipEnabled()) {
|
if (tooltipEnabled()) {
|
||||||
label_.set_tooltip_markup(window_);
|
setTooltipMarkup(window_);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateAppIcon();
|
updateAppIcon();
|
||||||
|
|||||||
@@ -229,7 +229,10 @@ bool Workspaces::filterButtons() {
|
|||||||
auto ws = std::find_if(workspaces_.begin(), workspaces_.end(),
|
auto ws = std::find_if(workspaces_.begin(), workspaces_.end(),
|
||||||
[it](const auto& node) { return node["name"].asString() == it->first; });
|
[it](const auto& node) { return node["name"].asString() == it->first; });
|
||||||
if (ws == workspaces_.end() ||
|
if (ws == workspaces_.end() ||
|
||||||
(!config_["all-outputs"].asBool() && (*ws)["output"].asString() != bar_.output->name)) {
|
((*ws).isMember("target_output") ? (*ws)["target_output"].asString() != bar_.output->name &&
|
||||||
|
(*ws)["target_output"].asString() != ""
|
||||||
|
: !config_["all-outputs"].asBool() &&
|
||||||
|
(*ws)["output"].asString() != bar_.output->name)) {
|
||||||
it = buttons_.erase(it);
|
it = buttons_.erase(it);
|
||||||
needReorder = true;
|
needReorder = true;
|
||||||
} else {
|
} else {
|
||||||
@@ -329,7 +332,12 @@ auto Workspaces::update() -> void {
|
|||||||
} else {
|
} else {
|
||||||
button.get_style_context()->remove_class("current_output");
|
button.get_style_context()->remove_class("current_output");
|
||||||
}
|
}
|
||||||
std::string output = (*it)["name"].asString();
|
std::string output;
|
||||||
|
if (!config_["disable-markup"].asBool()) {
|
||||||
|
output = g_markup_escape_text((*it)["name"].asString().c_str(), -1);
|
||||||
|
} else {
|
||||||
|
output = (*it)["name"].asString();
|
||||||
|
}
|
||||||
std::string windows = "";
|
std::string windows = "";
|
||||||
if (config_["window-rewrite"].isObject()) {
|
if (config_["window-rewrite"].isObject()) {
|
||||||
updateWindows((*it), windows);
|
updateWindows((*it), windows);
|
||||||
@@ -369,11 +377,14 @@ Gtk::Button& Workspaces::addButton(const Json::Value& node) {
|
|||||||
node["name"].asString(), node["target_output"].asString(),
|
node["name"].asString(), node["target_output"].asString(),
|
||||||
"--no-auto-back-and-forth", node["name"].asString()));
|
"--no-auto-back-and-forth", node["name"].asString()));
|
||||||
} else {
|
} else {
|
||||||
ipc_.sendCmd(IPC_COMMAND, fmt::format("workspace {} \"{}\"",
|
std::string flag = config_["disable-auto-back-and-forth"].asBool()
|
||||||
config_["disable-auto-back-and-forth"].asBool()
|
|
||||||
? "--no-auto-back-and-forth"
|
? "--no-auto-back-and-forth"
|
||||||
: "",
|
: "";
|
||||||
node["name"].asString()));
|
if (node["num"].asInt() >= 0) {
|
||||||
|
ipc_.sendCmd(IPC_COMMAND, fmt::format(workspace_switch_number_cmd_, flag, node["num"].asInt()));
|
||||||
|
} else {
|
||||||
|
ipc_.sendCmd(IPC_COMMAND, fmt::format(workspace_switch_cmd_, flag, node["name"].asString()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
spdlog::error("Workspaces: {}", e.what());
|
spdlog::error("Workspaces: {}", e.what());
|
||||||
|
|||||||
@@ -84,6 +84,9 @@ SystemdFailedUnits::SystemdFailedUnits(const std::string& id, const Json::Value&
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!user_props_proxy_ && !system_props_proxy_)
|
||||||
|
throw std::runtime_error("Neither system nor user status is requested.");
|
||||||
|
|
||||||
updateData();
|
updateData();
|
||||||
/* Always update for the first time. */
|
/* Always update for the first time. */
|
||||||
dp.emit();
|
dp.emit();
|
||||||
@@ -103,7 +106,7 @@ auto SystemdFailedUnits::notify_cb(const Glib::ustring& sender_name,
|
|||||||
void SystemdFailedUnits::RequestSystemState() {
|
void SystemdFailedUnits::RequestSystemState() {
|
||||||
auto load = [](const char* kind, Glib::RefPtr<Gio::DBus::Proxy>& proxy) -> std::string {
|
auto load = [](const char* kind, Glib::RefPtr<Gio::DBus::Proxy>& proxy) -> std::string {
|
||||||
try {
|
try {
|
||||||
if (!proxy) return "unknown";
|
if (!proxy) return "ignored";
|
||||||
auto parameters = Glib::VariantContainerBase(
|
auto parameters = Glib::VariantContainerBase(
|
||||||
g_variant_new("(ss)", "org.freedesktop.systemd1.Manager", "SystemState"));
|
g_variant_new("(ss)", "org.freedesktop.systemd1.Manager", "SystemState"));
|
||||||
Glib::VariantContainerBase data = proxy->call_sync("Get", parameters);
|
Glib::VariantContainerBase data = proxy->call_sync("Get", parameters);
|
||||||
@@ -122,7 +125,9 @@ void SystemdFailedUnits::RequestSystemState() {
|
|||||||
|
|
||||||
system_state_ = load("systemwide", system_props_proxy_);
|
system_state_ = load("systemwide", system_props_proxy_);
|
||||||
user_state_ = load("user", user_props_proxy_);
|
user_state_ = load("user", user_props_proxy_);
|
||||||
if (system_state_ == "running" && user_state_ == "running")
|
|
||||||
|
if ((system_state_ == "running" || system_state_ == "ignored") &&
|
||||||
|
(user_state_ == "running" || user_state_ == "ignored"))
|
||||||
overall_state_ = "ok";
|
overall_state_ = "ok";
|
||||||
else
|
else
|
||||||
overall_state_ = "degraded";
|
overall_state_ = "degraded";
|
||||||
|
|||||||
@@ -20,6 +20,24 @@ waybar::modules::Temperature::Temperature(const std::string& id, const Json::Val
|
|||||||
if (check_set_path(item.asString())) break;
|
if (check_set_path(item.asString())) break;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
auto find_hwmon_by_name = [](const std::string& name) -> std::optional<std::filesystem::path> {
|
||||||
|
for (const auto& entry : std::filesystem::directory_iterator("/sys/class/hwmon")) {
|
||||||
|
std::ifstream f(entry.path() / "name");
|
||||||
|
std::string hwname;
|
||||||
|
if (f >> hwname && hwname == name) {
|
||||||
|
return entry.path();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ensure either hwmon-name OR old paths are used, not both
|
||||||
|
if (config_["hwmon-name"].isString() &&
|
||||||
|
(!config_["hwmon-path"].isNull() || !config_["hwmon-path-abs"].isNull())) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
"hwmon-name cannot be used together with hwmon-path or hwmon-path-abs");
|
||||||
|
}
|
||||||
|
|
||||||
// if hwmon_path is an array, loop to find first valid item
|
// if hwmon_path is an array, loop to find first valid item
|
||||||
traverseAsArray(config_["hwmon-path"], [this](const std::string& path) {
|
traverseAsArray(config_["hwmon-path"], [this](const std::string& path) {
|
||||||
if (!std::filesystem::exists(path)) return false;
|
if (!std::filesystem::exists(path)) return false;
|
||||||
@@ -40,6 +58,20 @@ waybar::modules::Temperature::Temperature(const std::string& id, const Json::Val
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (file_path_.empty() && config_["hwmon-name"].isString()) {
|
||||||
|
if (!config_["input-filename"].isString()) {
|
||||||
|
throw std::runtime_error("hwmon-name requires input-filename to be set");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto hwmon = find_hwmon_by_name(config_["hwmon-name"].asString());
|
||||||
|
|
||||||
|
if (!hwmon) {
|
||||||
|
throw std::runtime_error("hwmon-name '" + config_["hwmon-name"].asString() + "' not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
file_path_ = hwmon->string() + "/" + config_["input-filename"].asString();
|
||||||
|
}
|
||||||
|
|
||||||
if (file_path_.empty()) {
|
if (file_path_.empty()) {
|
||||||
auto zone = config_["thermal-zone"].isInt() ? config_["thermal-zone"].asInt() : 0;
|
auto zone = config_["thermal-zone"].isInt() ? config_["thermal-zone"].asInt() : 0;
|
||||||
file_path_ = fmt::format("/sys/class/thermal/thermal_zone{}/temp", zone);
|
file_path_ = fmt::format("/sys/class/thermal/thermal_zone{}/temp", zone);
|
||||||
@@ -155,3 +187,7 @@ bool waybar::modules::Temperature::isCritical(uint16_t temperature_c) {
|
|||||||
return config_["critical-threshold"].isInt() &&
|
return config_["critical-threshold"].isInt() &&
|
||||||
temperature_c >= config_["critical-threshold"].asInt();
|
temperature_c >= config_["critical-threshold"].asInt();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void waybar::modules::Temperature::suspend() { thread_.pause(); }
|
||||||
|
|
||||||
|
void waybar::modules::Temperature::resume() { thread_.resume(); }
|
||||||
|
|||||||
@@ -347,10 +347,13 @@ auto IPC::update_state_handler(const std::string& event, const Json::Value& data
|
|||||||
|
|
||||||
if (event == "output-wset-changed") {
|
if (event == "output-wset-changed") {
|
||||||
// data: { event, new-wset: wset.name, output: id, new-wset-data: wset, output-data: output }
|
// data: { event, new-wset: wset.name, output: id, new-wset-data: wset, output-data: output }
|
||||||
|
try {
|
||||||
auto& output = state.outputs.at(data["output-data"]["name"].asString());
|
auto& output = state.outputs.at(data["output-data"]["name"].asString());
|
||||||
auto wset_idx = data["new-wset-data"]["index"].asUInt();
|
auto wset_idx = data["new-wset-data"]["index"].asUInt();
|
||||||
state.wsets.at(wset_idx).output = output;
|
state.wsets.at(wset_idx).output = output;
|
||||||
output.wset_idx = wset_idx;
|
output.wset_idx = wset_idx;
|
||||||
|
} catch (const std::exception&) {
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -211,6 +211,7 @@ void waybar::modules::Wireplumber::onMixerChanged(waybar::modules::Wireplumber*
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
spdlog::warn("[{}]: (onMixerChanged: {}) - Object with id {} not found", self->name_,
|
spdlog::warn("[{}]: (onMixerChanged: {}) - Object with id {} not found", self->name_,
|
||||||
|
|||||||
@@ -94,9 +94,35 @@ Task::Task(const waybar::Bar& bar, const Json::Value& config, Taskbar* tbar,
|
|||||||
|
|
||||||
button.set_relief(Gtk::RELIEF_NONE);
|
button.set_relief(Gtk::RELIEF_NONE);
|
||||||
|
|
||||||
|
/* When "expand" is enabled the buttons stretch to fill the taskbar and the
|
||||||
|
* titles ellipsize to fit within the available space. This only makes sense
|
||||||
|
* on a horizontal bar; on a vertical bar the box grows along the vertical
|
||||||
|
* axis, so ellipsizing/forcing width_chars(1) would truncate every title to
|
||||||
|
* "…". Keep the historical behavior (buttons sized to their content) as the
|
||||||
|
* default and when the bar is vertical. */
|
||||||
|
bool expand = config_["expand"].isBool() && config_["expand"].asBool();
|
||||||
|
bool horizontal = bar.orientation == Gtk::ORIENTATION_HORIZONTAL;
|
||||||
|
|
||||||
|
if (expand && horizontal) {
|
||||||
|
button.set_hexpand(true);
|
||||||
|
content_.set_hexpand(true);
|
||||||
|
text_before_.set_ellipsize(Pango::ELLIPSIZE_END);
|
||||||
|
text_before_.set_single_line_mode(true);
|
||||||
|
text_before_.set_width_chars(1);
|
||||||
|
text_before_.set_xalign(0.0);
|
||||||
|
text_after_.set_ellipsize(Pango::ELLIPSIZE_END);
|
||||||
|
text_after_.set_single_line_mode(true);
|
||||||
|
text_after_.set_width_chars(1);
|
||||||
|
text_after_.set_xalign(0.0);
|
||||||
|
|
||||||
|
content_.pack_start(text_before_, true, true, 0);
|
||||||
|
content_.pack_start(icon_, false, false, 0);
|
||||||
|
content_.pack_start(text_after_, true, true, 0);
|
||||||
|
} else {
|
||||||
content_.add(text_before_);
|
content_.add(text_before_);
|
||||||
content_.add(icon_);
|
content_.add(icon_);
|
||||||
content_.add(text_after_);
|
content_.add(text_after_);
|
||||||
|
}
|
||||||
|
|
||||||
content_.show();
|
content_.show();
|
||||||
button.add(content_);
|
button.add(content_);
|
||||||
@@ -718,7 +744,15 @@ void Taskbar::handle_finished() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Taskbar::add_button(Gtk::Button& bt) {
|
void Taskbar::add_button(Gtk::Button& bt) {
|
||||||
|
/* Only let the buttons expand to fill the taskbar when "expand" is enabled
|
||||||
|
* and the bar is horizontal (see the Task constructor for details). */
|
||||||
|
bool expand = config_["expand"].isBool() && config_["expand"].asBool();
|
||||||
|
bool horizontal = bar_.orientation == Gtk::ORIENTATION_HORIZONTAL;
|
||||||
|
if (expand && horizontal) {
|
||||||
|
box_.pack_start(bt, true, true);
|
||||||
|
} else {
|
||||||
box_.pack_start(bt, false, false);
|
box_.pack_start(bt, false, false);
|
||||||
|
}
|
||||||
box_.get_style_context()->remove_class("empty");
|
box_.get_style_context()->remove_class("empty");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -99,6 +99,7 @@ void AudioBackend::contextStateCb(pa_context* c, void* data) {
|
|||||||
nullptr, nullptr);
|
nullptr, nullptr);
|
||||||
break;
|
break;
|
||||||
case PA_CONTEXT_FAILED:
|
case PA_CONTEXT_FAILED:
|
||||||
|
if (pa_context_errno(c) != PA_ERR_CONNECTIONREFUSED) {
|
||||||
// When pulseaudio server restarts, the connection is "failed". Try to reconnect.
|
// When pulseaudio server restarts, the connection is "failed". Try to reconnect.
|
||||||
// pa_threaded_mainloop_lock is already acquired in callback threads.
|
// pa_threaded_mainloop_lock is already acquired in callback threads.
|
||||||
// So there is no need to lock it again.
|
// So there is no need to lock it again.
|
||||||
@@ -108,6 +109,7 @@ void AudioBackend::contextStateCb(pa_context* c, void* data) {
|
|||||||
backend->context_ = nullptr;
|
backend->context_ = nullptr;
|
||||||
}
|
}
|
||||||
backend->connectContext();
|
backend->connectContext();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case PA_CONTEXT_CONNECTING:
|
case PA_CONTEXT_CONNECTING:
|
||||||
case PA_CONTEXT_AUTHORIZING:
|
case PA_CONTEXT_AUTHORIZING:
|
||||||
@@ -259,6 +261,14 @@ void AudioBackend::serverInfoCb(pa_context* context, const pa_server_info* i, vo
|
|||||||
pa_context_get_source_info_list(context, sourceInfoCb, data);
|
pa_context_get_source_info_list(context, sourceInfoCb, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint16_t AudioBackend::getVolume(PulseaudioTarget target) const {
|
||||||
|
if (target == PulseaudioTarget::Source) {
|
||||||
|
return source_volume_;
|
||||||
|
} else {
|
||||||
|
return volume_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void AudioBackend::changeVolume(uint16_t volume, uint16_t min_volume, uint16_t max_volume) {
|
void AudioBackend::changeVolume(uint16_t volume, uint16_t min_volume, uint16_t max_volume) {
|
||||||
// Early return if context is not ready
|
// Early return if context is not ready
|
||||||
if ((context_ == nullptr) || pa_context_get_state(context_) != PA_CONTEXT_READY) {
|
if ((context_ == nullptr) || pa_context_get_state(context_) != PA_CONTEXT_READY) {
|
||||||
@@ -368,6 +378,14 @@ void AudioBackend::changeVolume(ChangeType change_type, double step, uint16_t ma
|
|||||||
pa_threaded_mainloop_unlock(mainloop_);
|
pa_threaded_mainloop_unlock(mainloop_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool AudioBackend::getMuted(PulseaudioTarget target) const {
|
||||||
|
if (target == PulseaudioTarget::Source) {
|
||||||
|
return source_muted_;
|
||||||
|
} else {
|
||||||
|
return muted_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void AudioBackend::toggleSinkMute() {
|
void AudioBackend::toggleSinkMute() {
|
||||||
if (context_ == nullptr || pa_context_get_state(context_) != PA_CONTEXT_READY) return;
|
if (context_ == nullptr || pa_context_get_state(context_) != PA_CONTEXT_READY) return;
|
||||||
muted_ = !muted_;
|
muted_ = !muted_;
|
||||||
@@ -404,6 +422,14 @@ void AudioBackend::toggleSourceMute(bool mute) {
|
|||||||
pa_threaded_mainloop_unlock(mainloop_);
|
pa_threaded_mainloop_unlock(mainloop_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AudioBackend::unmute(PulseaudioTarget target) {
|
||||||
|
if (target == PulseaudioTarget::Source) {
|
||||||
|
toggleSourceMute(false);
|
||||||
|
} else {
|
||||||
|
toggleSinkMute(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool AudioBackend::isBluetooth() {
|
bool AudioBackend::isBluetooth() {
|
||||||
return monitor_.find("a2dp_sink") != std::string::npos || // PulseAudio
|
return monitor_.find("a2dp_sink") != std::string::npos || // PulseAudio
|
||||||
monitor_.find("a2dp-sink") != std::string::npos || // PipeWire
|
monitor_.find("a2dp-sink") != std::string::npos || // PipeWire
|
||||||
|
|||||||
@@ -23,7 +23,8 @@ namespace {
|
|||||||
const std::regex IMPORT_REGEX(R"(@import\s+(?:url\()?(?:"|')([^"')]+)(?:"|')\)?;)");
|
const std::regex IMPORT_REGEX(R"(@import\s+(?:url\()?(?:"|')([^"')]+)(?:"|')\)?;)");
|
||||||
}
|
}
|
||||||
|
|
||||||
waybar::CssReloadHelper::CssReloadHelper(std::string cssFile, std::function<void()> callback)
|
waybar::CssReloadHelper::CssReloadHelper(std::string cssFile,
|
||||||
|
std::function<void(const std::string&)> callback)
|
||||||
: m_cssFile(std::move(cssFile)), m_callback(std::move(callback)) {}
|
: m_cssFile(std::move(cssFile)), m_callback(std::move(callback)) {}
|
||||||
|
|
||||||
std::string waybar::CssReloadHelper::getFileContents(const std::string& filename) {
|
std::string waybar::CssReloadHelper::getFileContents(const std::string& filename) {
|
||||||
@@ -88,6 +89,12 @@ void waybar::CssReloadHelper::monitorChanges() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void waybar::CssReloadHelper::changeCssFile(const std::string& newCssFile) {
|
||||||
|
m_fileMonitors.clear();
|
||||||
|
m_cssFile = newCssFile;
|
||||||
|
monitorChanges();
|
||||||
|
}
|
||||||
|
|
||||||
void waybar::CssReloadHelper::handleFileChange(Glib::RefPtr<Gio::File> const& file,
|
void waybar::CssReloadHelper::handleFileChange(Glib::RefPtr<Gio::File> const& file,
|
||||||
Glib::RefPtr<Gio::File> const& other_type,
|
Glib::RefPtr<Gio::File> const& other_type,
|
||||||
Gio::FileMonitorEvent event_type) {
|
Gio::FileMonitorEvent event_type) {
|
||||||
@@ -95,7 +102,7 @@ void waybar::CssReloadHelper::handleFileChange(Glib::RefPtr<Gio::File> const& fi
|
|||||||
// fire for one
|
// fire for one
|
||||||
if (event_type == Gio::FileMonitorEvent::FILE_MONITOR_EVENT_CHANGES_DONE_HINT) {
|
if (event_type == Gio::FileMonitorEvent::FILE_MONITOR_EVENT_CHANGES_DONE_HINT) {
|
||||||
spdlog::debug("Reloading style, file changed: {}", file->get_path());
|
spdlog::debug("Reloading style, file changed: {}", file->get_path());
|
||||||
m_callback();
|
m_callback(m_cssFile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ std::string rewriteString(const std::string& value, const Json::Value& rules) {
|
|||||||
// malformated regexes will cause an exception.
|
// malformated regexes will cause an exception.
|
||||||
// in this case, log error and try the next rule.
|
// in this case, log error and try the next rule.
|
||||||
const std::regex rule{it.key().asString(), std::regex_constants::icase};
|
const std::regex rule{it.key().asString(), std::regex_constants::icase};
|
||||||
if (std::regex_match(value, rule)) {
|
if (std::regex_match(res, rule)) {
|
||||||
res = std::regex_replace(res, rule, it->asString());
|
res = std::regex_replace(res, rule, it->asString());
|
||||||
}
|
}
|
||||||
} catch (const std::regex_error& e) {
|
} catch (const std::regex_error& e) {
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
#include <glib.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <util/utf8_string.hpp>
|
||||||
|
|
||||||
|
namespace waybar::util {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
// Wide characters count as two, zero-width characters count as zero
|
||||||
|
// Modifies str in-place (unless width = std::string::npos)
|
||||||
|
// Returns the total width of the string pre-truncating
|
||||||
|
size_t measure_and_truncate(std::string& str, size_t width = std::string::npos) {
|
||||||
|
if (str.length() == 0) return 0;
|
||||||
|
|
||||||
|
const gchar* trunc_end = nullptr;
|
||||||
|
|
||||||
|
size_t total_width = 0;
|
||||||
|
|
||||||
|
for (gchar *data = str.data(), *end = data + str.size(); data != nullptr;) {
|
||||||
|
gunichar c = g_utf8_get_char_validated(data, end - data);
|
||||||
|
if (c == -1U || c == -2U) {
|
||||||
|
// invalid unicode, treat string as ascii
|
||||||
|
if (width != std::string::npos && str.length() > width) str.resize(width);
|
||||||
|
return str.length();
|
||||||
|
} else if (g_unichar_iswide(c)) {
|
||||||
|
total_width += 2;
|
||||||
|
} else if (!g_unichar_iszerowidth(c) && c != 0xAD) { // neither zero-width nor soft hyphen
|
||||||
|
total_width += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
data = g_utf8_find_next_char(data, end);
|
||||||
|
if (width != std::string::npos && total_width <= width && !g_unichar_isspace(c))
|
||||||
|
trunc_end = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trunc_end) str.resize(trunc_end - str.data());
|
||||||
|
|
||||||
|
return total_width;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
size_t utf8_width(const std::string& str) {
|
||||||
|
return measure_and_truncate(const_cast<std::string&>(str));
|
||||||
|
}
|
||||||
|
|
||||||
|
void utf8_truncate(std::string& s, const std::string& ellipsis, size_t max_len) {
|
||||||
|
if (max_len == 0) {
|
||||||
|
s.resize(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
size_t len = measure_and_truncate(s, max_len);
|
||||||
|
if (len > max_len) {
|
||||||
|
size_t ellipsis_len = utf8_width(ellipsis);
|
||||||
|
if (max_len >= ellipsis_len) {
|
||||||
|
if (ellipsis_len) measure_and_truncate(s, max_len - ellipsis_len);
|
||||||
|
s += ellipsis;
|
||||||
|
} else {
|
||||||
|
s.resize(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace waybar::util
|
||||||
@@ -15,6 +15,10 @@ namespace {
|
|||||||
class IPCTestHelper : public hyprland::IPC {
|
class IPCTestHelper : public hyprland::IPC {
|
||||||
public:
|
public:
|
||||||
static void resetSocketFolder() { socketFolder_.clear(); }
|
static void resetSocketFolder() { socketFolder_.clear(); }
|
||||||
|
static void resetLuaProtocolDetection() { s_luaProtocolDetected_.reset(); }
|
||||||
|
static void setLuaProtocolDetected(bool value) { s_luaProtocolDetected_ = value; }
|
||||||
|
using hyprland::IPC::buildLuaDispatch;
|
||||||
|
using hyprland::IPC::isLuaProtocol;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::size_t countOpenFds() {
|
std::size_t countOpenFds() {
|
||||||
@@ -133,3 +137,78 @@ TEST_CASE("getSocket1Reply failure paths do not leak fds", "[getSocket1Reply][fd
|
|||||||
REQUIRE(after_connect_failures == baseline);
|
REQUIRE(after_connect_failures == baseline);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// --- Tests for new Lua IPC dispatch functions ---
|
||||||
|
|
||||||
|
TEST_CASE("buildLuaDispatch workspace", "[buildLuaDispatch]") {
|
||||||
|
SECTION("numeric workspace") {
|
||||||
|
auto result = IPCTestHelper::buildLuaDispatch("workspace", "1");
|
||||||
|
REQUIRE(result == "/dispatch hl.dsp.focus({ workspace = \"1\" })");
|
||||||
|
}
|
||||||
|
SECTION("named workspace") {
|
||||||
|
auto result = IPCTestHelper::buildLuaDispatch("workspace", "name:term");
|
||||||
|
REQUIRE(result == "/dispatch hl.dsp.focus({ workspace = \"name:term\" })");
|
||||||
|
}
|
||||||
|
SECTION("relative workspace") {
|
||||||
|
auto result = IPCTestHelper::buildLuaDispatch("workspace", "e+1");
|
||||||
|
REQUIRE(result == "/dispatch hl.dsp.focus({ workspace = \"e+1\" })");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("buildLuaDispatch focusworkspaceoncurrentmonitor", "[buildLuaDispatch]") {
|
||||||
|
auto result =
|
||||||
|
IPCTestHelper::buildLuaDispatch("focusworkspaceoncurrentmonitor", "3");
|
||||||
|
REQUIRE(
|
||||||
|
result ==
|
||||||
|
"/dispatch hl.dsp.focus({ workspace = \"3\", on_current_monitor = true })");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("buildLuaDispatch togglespecialworkspace", "[buildLuaDispatch]") {
|
||||||
|
SECTION("with name") {
|
||||||
|
auto result =
|
||||||
|
IPCTestHelper::buildLuaDispatch("togglespecialworkspace", "scratchpad");
|
||||||
|
REQUIRE(result ==
|
||||||
|
"/dispatch hl.dsp.workspace.toggle_special(\"scratchpad\")");
|
||||||
|
}
|
||||||
|
SECTION("empty arg") {
|
||||||
|
auto result =
|
||||||
|
IPCTestHelper::buildLuaDispatch("togglespecialworkspace", "");
|
||||||
|
REQUIRE(result == "/dispatch hl.dsp.workspace.toggle_special()");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("buildLuaDispatch unknown dispatcher fallback", "[buildLuaDispatch]") {
|
||||||
|
auto result =
|
||||||
|
IPCTestHelper::buildLuaDispatch("unknown_dispatcher", "some_arg");
|
||||||
|
REQUIRE(result ==
|
||||||
|
"/dispatch hl.dsp.unknown_dispatcher(\"some_arg\")");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("dispatch throws when Hyprland is not running", "[dispatch]") {
|
||||||
|
unsetenv("HYPRLAND_INSTANCE_SIGNATURE");
|
||||||
|
IPCTestHelper::resetSocketFolder();
|
||||||
|
IPCTestHelper::resetLuaProtocolDetection();
|
||||||
|
|
||||||
|
CHECK_THROWS(hyprland::IPC::dispatch("workspace", "1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("isLuaProtocol uses cached value and avoids socket call",
|
||||||
|
"[isLuaProtocol]") {
|
||||||
|
unsetenv("HYPRLAND_INSTANCE_SIGNATURE");
|
||||||
|
IPCTestHelper::resetSocketFolder();
|
||||||
|
|
||||||
|
SECTION("cached false") {
|
||||||
|
IPCTestHelper::setLuaProtocolDetected(false);
|
||||||
|
// Should return false without throwing (no socket call needed)
|
||||||
|
REQUIRE(IPCTestHelper::isLuaProtocol() == false);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("cached true") {
|
||||||
|
IPCTestHelper::setLuaProtocolDetected(true);
|
||||||
|
// Should return true without throwing (no socket call needed)
|
||||||
|
REQUIRE(IPCTestHelper::isLuaProtocol() == true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup: reset detection so other tests aren't affected
|
||||||
|
IPCTestHelper::resetLuaProtocolDetection();
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
class CssReloadHelperTest : public waybar::CssReloadHelper {
|
class CssReloadHelperTest : public waybar::CssReloadHelper {
|
||||||
public:
|
public:
|
||||||
CssReloadHelperTest() : CssReloadHelper("/tmp/waybar_test.css", [this]() { callback(); }) {}
|
CssReloadHelperTest() : CssReloadHelper("/tmp/waybar_test.css", [this](const std::string&) { callback(); }) {}
|
||||||
|
|
||||||
void callback() { m_callbackCounter++; }
|
void callback() { m_callbackCounter++; }
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user