Merge remote-tracking branch 'origin/master' into pr-4084
# Conflicts: # src/modules/mpris/mpris.cpp
This commit is contained in:
@@ -14,10 +14,14 @@ class AIconLabel : public ALabel {
|
|||||||
bool enable_click = false, bool enable_scroll = false);
|
bool enable_click = false, bool enable_scroll = false);
|
||||||
virtual ~AIconLabel() = default;
|
virtual ~AIconLabel() = default;
|
||||||
auto update() -> void override;
|
auto update() -> void override;
|
||||||
|
static std::tuple<std::string, std::string> extractIcon(const std::string& input);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Gtk::Image image_;
|
Gtk::Image image_;
|
||||||
Gtk::Box box_;
|
Gtk::Box box_;
|
||||||
|
unsigned app_icon_size_{24};
|
||||||
|
|
||||||
|
bool label_contains_icon{false};
|
||||||
|
|
||||||
bool iconEnabled() const;
|
bool iconEnabled() const;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -10,6 +10,8 @@
|
|||||||
namespace waybar {
|
namespace waybar {
|
||||||
|
|
||||||
class Group : public AModule {
|
class Group : public AModule {
|
||||||
|
sigc::connection reveal_timeout_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Group(const std::string&, const std::string&, const Json::Value&, bool);
|
Group(const std::string&, const std::string&, const Json::Value&, bool);
|
||||||
~Group() override = default;
|
~Group() override = default;
|
||||||
@@ -26,6 +28,7 @@ class Group : public AModule {
|
|||||||
bool is_first_widget = true;
|
bool is_first_widget = true;
|
||||||
bool is_drawer = false;
|
bool is_drawer = false;
|
||||||
bool click_to_reveal = false;
|
bool click_to_reveal = false;
|
||||||
|
int reveal_delay = 0;
|
||||||
std::string add_class_to_drawer_children;
|
std::string add_class_to_drawer_children;
|
||||||
bool handleMouseEnter(GdkEventCrossing* const& ev) override;
|
bool handleMouseEnter(GdkEventCrossing* const& ev) override;
|
||||||
bool handleMouseLeave(GdkEventCrossing* const& ev) override;
|
bool handleMouseLeave(GdkEventCrossing* const& ev) override;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include <poll.h>
|
#include <poll.h>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <chrono>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@@ -51,6 +52,11 @@ class Battery : public ALabel {
|
|||||||
bool warnFirstTime_{true};
|
bool warnFirstTime_{true};
|
||||||
bool weightedAverage_{true};
|
bool weightedAverage_{true};
|
||||||
const Bar& bar_;
|
const Bar& bar_;
|
||||||
|
bool smoothPowerEnable_{false};
|
||||||
|
double time_constant_s_{260.0};
|
||||||
|
double smooth_power_{0.0}; // µW
|
||||||
|
std::chrono::steady_clock::time_point last_t_{std::chrono::steady_clock::now()};
|
||||||
|
std::string old_status_raw_{""};
|
||||||
|
|
||||||
util::SleeperThread thread_;
|
util::SleeperThread thread_;
|
||||||
util::SleeperThread thread_battery_update_;
|
util::SleeperThread thread_battery_update_;
|
||||||
|
|||||||
@@ -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; };
|
||||||
@@ -80,6 +90,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;
|
||||||
|
|||||||
@@ -36,8 +36,7 @@ class KeyboardState : public AModule {
|
|||||||
std::string capslock_format_;
|
std::string capslock_format_;
|
||||||
std::string scrolllock_format_;
|
std::string scrolllock_format_;
|
||||||
const std::chrono::seconds interval_;
|
const std::chrono::seconds interval_;
|
||||||
std::string icon_locked_;
|
std::unordered_map<std::string, std::vector<std::string>> key_icon_states_;
|
||||||
std::string icon_unlocked_;
|
|
||||||
std::string devices_path_;
|
std::string devices_path_;
|
||||||
|
|
||||||
struct libinput* libinput_;
|
struct libinput* libinput_;
|
||||||
|
|||||||
@@ -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_;
|
||||||
|
|||||||
@@ -9,9 +9,18 @@ namespace waybar::modules {
|
|||||||
|
|
||||||
struct Profile {
|
struct Profile {
|
||||||
std::string name;
|
std::string name;
|
||||||
|
// Legacy driver field, kept for backward compatibility with the
|
||||||
|
// `{driver}` format placeholder and with older power-profiles-daemon
|
||||||
|
// versions that only expose a single `Driver` DBus property.
|
||||||
std::string driver;
|
std::string driver;
|
||||||
|
std::string cpuDriver;
|
||||||
|
std::string platformDriver;
|
||||||
|
|
||||||
Profile(std::string n, std::string d) : name(std::move(n)), driver(std::move(d)) {}
|
Profile(std::string n, std::string d, std::string cd, std::string pd)
|
||||||
|
: name(std::move(n)),
|
||||||
|
driver(std::move(d)),
|
||||||
|
cpuDriver(std::move(cd)),
|
||||||
|
platformDriver(std::move(pd)) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
class PowerProfilesDaemon : public ALabel {
|
class PowerProfilesDaemon : public ALabel {
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
#include "AModule.hpp"
|
#include "AModule.hpp"
|
||||||
#include "bar.hpp"
|
#include "bar.hpp"
|
||||||
#include "client.hpp"
|
#include "client.hpp"
|
||||||
|
#include "ext-workspace-v1-client-protocol.h"
|
||||||
#include "giomm/desktopappinfo.h"
|
#include "giomm/desktopappinfo.h"
|
||||||
#include "util/icon_loader.hpp"
|
#include "util/icon_loader.hpp"
|
||||||
#include "util/json.hpp"
|
#include "util/json.hpp"
|
||||||
@@ -80,6 +81,7 @@ class Task {
|
|||||||
std::string title_;
|
std::string title_;
|
||||||
std::string app_id_;
|
std::string app_id_;
|
||||||
uint32_t state_ = 0;
|
uint32_t state_ = 0;
|
||||||
|
struct ext_workspace_handle_v1* workspace_ = nullptr;
|
||||||
|
|
||||||
int32_t drag_start_x;
|
int32_t drag_start_x;
|
||||||
int32_t drag_start_y;
|
int32_t drag_start_y;
|
||||||
@@ -102,6 +104,9 @@ class Task {
|
|||||||
bool minimized() const { return state_ & MINIMIZED; }
|
bool minimized() const { return state_ & MINIMIZED; }
|
||||||
bool active() const { return state_ & ACTIVE; }
|
bool active() const { return state_ & ACTIVE; }
|
||||||
bool fullscreen() const { return state_ & FULLSCREEN; }
|
bool fullscreen() const { return state_ & FULLSCREEN; }
|
||||||
|
bool visible() const { return button_visible_; }
|
||||||
|
struct ext_workspace_handle_v1* workspace() const { return workspace_; }
|
||||||
|
void set_workspace(struct ext_workspace_handle_v1* workspace) { workspace_ = workspace; }
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/* Callbacks for the wlr protocol */
|
/* Callbacks for the wlr protocol */
|
||||||
@@ -142,6 +147,12 @@ using TaskPtr = std::unique_ptr<Task>;
|
|||||||
|
|
||||||
class Taskbar : public waybar::AModule {
|
class Taskbar : public waybar::AModule {
|
||||||
public:
|
public:
|
||||||
|
struct WorkspaceState {
|
||||||
|
Taskbar* taskbar;
|
||||||
|
struct ext_workspace_handle_v1* handle;
|
||||||
|
uint32_t state = 0;
|
||||||
|
};
|
||||||
|
|
||||||
Taskbar(const std::string&, const waybar::Bar&, const Json::Value&);
|
Taskbar(const std::string&, const waybar::Bar&, const Json::Value&);
|
||||||
~Taskbar();
|
~Taskbar();
|
||||||
void update();
|
void update();
|
||||||
@@ -156,22 +167,35 @@ class Taskbar : public waybar::AModule {
|
|||||||
std::map<std::string, std::string> app_ids_replace_map_;
|
std::map<std::string, std::string> app_ids_replace_map_;
|
||||||
|
|
||||||
struct zwlr_foreign_toplevel_manager_v1* manager_;
|
struct zwlr_foreign_toplevel_manager_v1* manager_;
|
||||||
|
struct ext_workspace_manager_v1* workspace_manager_;
|
||||||
struct wl_seat* seat_;
|
struct wl_seat* seat_;
|
||||||
|
std::vector<struct ext_workspace_group_handle_v1*> workspace_groups_;
|
||||||
|
std::vector<std::unique_ptr<WorkspaceState>> workspaces_;
|
||||||
|
struct ext_workspace_handle_v1* current_workspace_ = nullptr;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/* Callbacks for global registration */
|
/* Callbacks for global registration */
|
||||||
void register_manager(struct wl_registry*, uint32_t name, uint32_t version);
|
void register_manager(struct wl_registry*, uint32_t name, uint32_t version);
|
||||||
|
void register_workspace_manager(struct wl_registry*, uint32_t name, uint32_t version);
|
||||||
void register_seat(struct wl_registry*, uint32_t name, uint32_t version);
|
void register_seat(struct wl_registry*, uint32_t name, uint32_t version);
|
||||||
|
|
||||||
/* Callbacks for the wlr protocol */
|
/* Callbacks for the wlr protocol */
|
||||||
void handle_toplevel_create(struct zwlr_foreign_toplevel_handle_v1*);
|
void handle_toplevel_create(struct zwlr_foreign_toplevel_handle_v1*);
|
||||||
void handle_finished();
|
void handle_finished();
|
||||||
|
void handle_workspace_group_create(struct ext_workspace_group_handle_v1*);
|
||||||
|
void handle_workspace_group_removed(struct ext_workspace_group_handle_v1*);
|
||||||
|
void handle_workspace_create(struct ext_workspace_handle_v1*);
|
||||||
|
void handle_workspace_done();
|
||||||
|
void handle_workspace_finished();
|
||||||
|
void handle_workspace_removed(struct ext_workspace_handle_v1*);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void add_button(Gtk::Button&);
|
void add_button(Gtk::Button&);
|
||||||
void move_button(Gtk::Button&, int);
|
void move_button(Gtk::Button&, int);
|
||||||
void remove_button(Gtk::Button&);
|
void remove_button(Gtk::Button&);
|
||||||
void remove_task(uint32_t);
|
void remove_task(uint32_t);
|
||||||
|
void assign_current_workspace(Task&);
|
||||||
|
void update_bar_css_classes();
|
||||||
|
|
||||||
bool show_output(struct wl_output*) const;
|
bool show_output(struct wl_output*) const;
|
||||||
bool all_outputs() const;
|
bool all_outputs() const;
|
||||||
@@ -179,6 +203,9 @@ class Taskbar : public waybar::AModule {
|
|||||||
const IconLoader& icon_loader() const;
|
const IconLoader& icon_loader() const;
|
||||||
const std::unordered_set<std::string>& ignore_list() const;
|
const std::unordered_set<std::string>& ignore_list() const;
|
||||||
const std::map<std::string, std::string>& app_ids_replace_map() const;
|
const std::map<std::string, std::string>& app_ids_replace_map() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void set_bar_css_class(const std::string&, bool);
|
||||||
};
|
};
|
||||||
|
|
||||||
} /* namespace waybar::modules::wlr */
|
} /* namespace waybar::modules::wlr */
|
||||||
|
|||||||
@@ -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,7 +97,11 @@ 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();
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace waybar::util
|
} // namespace waybar::util
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Result of transforming 8-bit hex codes to rgba().
|
||||||
|
*/
|
||||||
|
struct TransformResult {
|
||||||
|
std::string css;
|
||||||
|
bool was_transformed;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads a CSS file, searches for 8-bit hex codes (#RRGGBBAA),
|
||||||
|
* and transforms them into GTK-compatible rgba() syntax.
|
||||||
|
*/
|
||||||
|
TransformResult transform_8bit_to_hex(const std::string& file_path);
|
||||||
@@ -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: [] ++
|
||||||
|
|||||||
@@ -26,7 +26,10 @@ You must be a member of the input group to use this module.
|
|||||||
*format-icons*: ++
|
*format-icons*: ++
|
||||||
typeof: object ++
|
typeof: object ++
|
||||||
default: {"locked": "locked", "unlocked": "unlocked"} ++
|
default: {"locked": "locked", "unlocked": "unlocked"} ++
|
||||||
Based on the keyboard state, the corresponding icon gets selected. The same set of icons is used for number, caps, and scroll lock, but the icon is selected from the set independently for each. See *icons*.
|
Based on the keyboard state, the corresponding icon gets selected. Supports two syntaxes:
|
||||||
|
- Common format-icons: "locked" and "unlocked" keys apply to all lock types.
|
||||||
|
- Per-lock-type format-icons: per-lock-type objects with "numlock", "capslock", "scrolllock" keys, each containing "locked" and "unlocked" icons.
|
||||||
|
See *icons*.
|
||||||
|
|
||||||
*numlock*: ++
|
*numlock*: ++
|
||||||
typeof: bool ++
|
typeof: bool ++
|
||||||
@@ -68,15 +71,21 @@ You must be a member of the input group to use this module.
|
|||||||
|
|
||||||
The following *format-icons* can be set.
|
The following *format-icons* can be set.
|
||||||
|
|
||||||
|
## Common format-icons for all lock types:
|
||||||
- *locked*: Will be shown when the keyboard state is locked. Default "locked".
|
- *locked*: Will be shown when the keyboard state is locked. Default "locked".
|
||||||
- *unlocked*: Will be shown when the keyboard state is not locked. Default "unlocked"
|
- *unlocked*: Will be shown when the keyboard state is not locked. Default "unlocked".
|
||||||
|
|
||||||
|
## Per-lock-type format-icons:
|
||||||
|
- *numlock*, *capslock*, *scrolllock*: Object containing "locked" and "unlocked" keys for lock-type-specific icons. Defaults to {"locked": "locked", "unlocked": "unlocked"} for each lock type.
|
||||||
|
|
||||||
# EXAMPLE:
|
# EXAMPLE:
|
||||||
|
|
||||||
|
## Common format-icons for all lock types:
|
||||||
```
|
```
|
||||||
"keyboard-state": {
|
"keyboard-state": {
|
||||||
"numlock": true,
|
"numlock": true,
|
||||||
"capslock": true,
|
"capslock": true,
|
||||||
|
"scrolllock": true,
|
||||||
"format": "{name} {icon}",
|
"format": "{name} {icon}",
|
||||||
"format-icons": {
|
"format-icons": {
|
||||||
"locked": "",
|
"locked": "",
|
||||||
@@ -85,6 +94,30 @@ The following *format-icons* can be set.
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Per-lock-type format-icons:
|
||||||
|
```
|
||||||
|
"keyboard-state": {
|
||||||
|
"numlock": true,
|
||||||
|
"capslock": true,
|
||||||
|
"scrolllock": true,
|
||||||
|
"format": "{name} {icon}",
|
||||||
|
"format-icons": {
|
||||||
|
"numlock": {
|
||||||
|
"locked": "1",
|
||||||
|
"unlocked": "0"
|
||||||
|
},
|
||||||
|
"capslock": {
|
||||||
|
"locked": "A",
|
||||||
|
"unlocked": "a"
|
||||||
|
},
|
||||||
|
"scrolllock": {
|
||||||
|
"locked": "S",
|
||||||
|
"unlocked": "s"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
# STYLE
|
# STYLE
|
||||||
|
|
||||||
- *#keyboard-state*
|
- *#keyboard-state*
|
||||||
|
|||||||
+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
|
||||||
|
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ $XDG_CONFIG_HOME/waybar/config
|
|||||||
:[ Message displayed on the bar. {icon} and {profile} are respectively substituted with the icon representing the active profile and its full name.
|
:[ Message displayed on the bar. {icon} and {profile} are respectively substituted with the icon representing the active profile and its full name.
|
||||||
|[ *tooltip-format*
|
|[ *tooltip-format*
|
||||||
:[ string
|
:[ string
|
||||||
:[ "Power profile: {profile}\\nDriver: {driver}"
|
:[ "Power profile: {profile}\\nCPU driver: {cpu_driver}\\nPlatform driver: {platform_driver}"
|
||||||
:[ Messaged displayed in the module tooltip. {icon} and {profile} are respectively substituted with the icon representing the active profile and its full name.
|
:[ Messaged displayed in the module tooltip. {icon} and {profile} are respectively substituted with the icon representing the active profile and its full name. {cpu_driver} and {platform_driver} are substituted with the CPU and platform drivers reported by recent power-profiles-daemon versions. {driver} is kept for backward compatibility: it resolves to the legacy single driver on older daemons and falls back to the CPU driver on recent ones.
|
||||||
|[ *tooltip*
|
|[ *tooltip*
|
||||||
:[ bool
|
:[ bool
|
||||||
:[ true
|
:[ true
|
||||||
@@ -51,7 +51,7 @@ Compact display (default config):
|
|||||||
```
|
```
|
||||||
"power-profiles-daemon": {
|
"power-profiles-daemon": {
|
||||||
"format": "{icon}",
|
"format": "{icon}",
|
||||||
"tooltip-format": "Power profile: {profile}\nDriver: {driver}",
|
"tooltip-format": "Power profile: {profile}\nCPU driver: {cpu_driver}\nPlatform driver: {platform_driver}",
|
||||||
"tooltip": true,
|
"tooltip": true,
|
||||||
"format-icons": {
|
"format-icons": {
|
||||||
"default": "",
|
"default": "",
|
||||||
@@ -67,7 +67,7 @@ Display the full profile name:
|
|||||||
```
|
```
|
||||||
"power-profiles-daemon": {
|
"power-profiles-daemon": {
|
||||||
"format": "{icon} {profile}",
|
"format": "{icon} {profile}",
|
||||||
"tooltip-format": "Power profile: {profile}\nDriver: {driver}",
|
"tooltip-format": "Power profile: {profile}\nCPU driver: {cpu_driver}\nPlatform driver: {platform_driver}",
|
||||||
"tooltip": true,
|
"tooltip": true,
|
||||||
"format-icons": {
|
"format-icons": {
|
||||||
"default": "",
|
"default": "",
|
||||||
|
|||||||
@@ -28,9 +28,20 @@ The volume can be controlled by dragging the slider across the bar or clicking o
|
|||||||
The orientation of the slider. Can be either `horizontal` or `vertical`.
|
The orientation of the slider. Can be either `horizontal` or `vertical`.
|
||||||
|
|
||||||
*expand*: ++
|
*expand*: ++
|
||||||
typeof: bool ++
|
typeof: bool ++
|
||||||
default: false ++
|
default: false ++
|
||||||
Enables this module to consume all left over space dynamically.
|
Enables this module to consume all 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
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,11 @@ Addressed by *sway/workspaces*
|
|||||||
default: {value} ++
|
default: {value} ++
|
||||||
The format, how information should be displayed.
|
The format, how information should be displayed.
|
||||||
|
|
||||||
|
*format-for-negative-index*: ++
|
||||||
|
typeof: string ++
|
||||||
|
default: *format* ++
|
||||||
|
An alternative format, which will be used for workspaces with no explict or negative index ("num" in sway terms). Requires *format* to be set.
|
||||||
|
|
||||||
*format-icons*: ++
|
*format-icons*: ++
|
||||||
typeof: array ++
|
typeof: array ++
|
||||||
Based on the workspace name and state, the corresponding icon gets selected. See *icons*.
|
Based on the workspace name and state, the corresponding icon gets selected. See *icons*.
|
||||||
@@ -187,6 +192,13 @@ n.b.: the list of outputs can be obtained from command line using *swaymsg -t ge
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
"sway/workspaces": {
|
||||||
|
"format": "{index} - {name}",
|
||||||
|
"format-for-negative-index": "{name}"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
# Style
|
# Style
|
||||||
|
|
||||||
- *#workspaces button*
|
- *#workspaces button*
|
||||||
|
|||||||
@@ -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*
|
||||||
|
|||||||
@@ -18,6 +18,13 @@ Addressed by *wlr/taskbar*
|
|||||||
default: false ++
|
default: false ++
|
||||||
If set to false applications on the waybar's current output will be shown. Otherwise, all applications are shown.
|
If set to false applications on the waybar's current output will be shown. Otherwise, all applications are shown.
|
||||||
|
|
||||||
|
*bar-css-states*: ++
|
||||||
|
typeof: bool ++
|
||||||
|
default: false ++
|
||||||
|
If set to true, application state is exposed as CSS classes on the Waybar
|
||||||
|
window. Maximized and fullscreen state is aggregated across applications
|
||||||
|
known to belong to the active workspace. See *Bar state style* below.
|
||||||
|
|
||||||
*format*: ++
|
*format*: ++
|
||||||
typeof: string ++
|
typeof: string ++
|
||||||
default: {icon} ++
|
default: {icon} ++
|
||||||
@@ -52,11 +59,22 @@ Addressed by *wlr/taskbar*
|
|||||||
default: false ++
|
default: false ++
|
||||||
If set to true, always reorder the tasks in the taskbar so that the currently active one is first. Otherwise don't reorder.
|
If set to true, always reorder the tasks in the taskbar so that the currently active one is first. Otherwise don't reorder.
|
||||||
|
|
||||||
|
*active-only*: ++
|
||||||
|
typeof: bool ++
|
||||||
|
default: false ++
|
||||||
|
If set to true, only the currently active application button is shown.
|
||||||
|
Other applications remain tracked and reappear when activated.
|
||||||
|
|
||||||
*sort-by-app-id*: ++
|
*sort-by-app-id*: ++
|
||||||
typeof: bool ++
|
typeof: bool ++
|
||||||
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.
|
||||||
@@ -156,3 +174,35 @@ Invalid expressions (e.g., mismatched parentheses) are skipped.
|
|||||||
- *#taskbar button.minimized*
|
- *#taskbar button.minimized*
|
||||||
- *#taskbar button.active*
|
- *#taskbar button.active*
|
||||||
- *#taskbar button.fullscreen*
|
- *#taskbar button.fullscreen*
|
||||||
|
|
||||||
|
# Bar state style
|
||||||
|
|
||||||
|
When *bar-css-states* is enabled, the following classes are added to
|
||||||
|
*window#waybar*:
|
||||||
|
|
||||||
|
- *window#waybar.toplevel-active*
|
||||||
|
- *window#waybar.toplevel-maximized*
|
||||||
|
- *window#waybar.toplevel-minimized*
|
||||||
|
- *window#waybar.toplevel-fullscreen*
|
||||||
|
|
||||||
|
The active, minimized classes describe the active application. The maximized
|
||||||
|
and fullscreen classes are set if any non-minimized application known to belong
|
||||||
|
to the active workspace has that state.
|
||||||
|
|
||||||
|
Workspace membership is learned when an application is activated and requires
|
||||||
|
the compositor to support *ext-workspace-v1*. Before an application has been
|
||||||
|
activated during the current Waybar session, its workspace may be unknown. On
|
||||||
|
compositors without *ext-workspace-v1*, these classes fall back to the active
|
||||||
|
application's state.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```
|
||||||
|
window#waybar {
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
window#waybar.toplevel-maximized {
|
||||||
|
background-color: rgba(0, 0, 0, 1);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|||||||
+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.
|
||||||
|
|
||||||
|
|||||||
+3
-1
@@ -185,7 +185,9 @@ src_files = files(
|
|||||||
'src/util/gtk_icon.cpp',
|
'src/util/gtk_icon.cpp',
|
||||||
'src/util/icon_loader.cpp',
|
'src/util/icon_loader.cpp',
|
||||||
'src/util/regex_collection.cpp',
|
'src/util/regex_collection.cpp',
|
||||||
'src/util/css_reload_helper.cpp'
|
'src/util/css_reload_helper.cpp',
|
||||||
|
'src/util/transform_8bit_to_rgba.cpp',
|
||||||
|
'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')
|
||||||
|
|||||||
@@ -155,7 +155,7 @@
|
|||||||
},
|
},
|
||||||
"power-profiles-daemon": {
|
"power-profiles-daemon": {
|
||||||
"format": "{icon}",
|
"format": "{icon}",
|
||||||
"tooltip-format": "Power profile: {profile}\nDriver: {driver}",
|
"tooltip-format": "Power profile: {profile}\nCPU driver: {cpu_driver}\nPlatform driver: {platform_driver}",
|
||||||
"tooltip": true,
|
"tooltip": true,
|
||||||
"format-icons": {
|
"format-icons": {
|
||||||
"default": "",
|
"default": "",
|
||||||
|
|||||||
@@ -105,24 +105,26 @@ class PlayerManager:
|
|||||||
current_player = self.get_first_playing_player()
|
current_player = self.get_first_playing_player()
|
||||||
if current_player is not None:
|
if current_player is not None:
|
||||||
self.on_metadata_changed(current_player, current_player.props.metadata)
|
self.on_metadata_changed(current_player, current_player.props.metadata)
|
||||||
else:
|
else:
|
||||||
self.clear_output()
|
self.clear_output()
|
||||||
|
|
||||||
def on_metadata_changed(self, player, metadata, _=None):
|
def on_metadata_changed(self, player, metadata, _=None):
|
||||||
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":
|
||||||
|
|||||||
@@ -2,27 +2,27 @@
|
|||||||
<interface>
|
<interface>
|
||||||
<object class="GtkMenu" id="menu">
|
<object class="GtkMenu" id="menu">
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkMenuItem" id="suspend">
|
<object class="GtkMenuItem" id="suspend">
|
||||||
<property name="label">Suspend</property>
|
<property name="label">Suspend</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
|
||||||
<object class="GtkMenuItem" id="hibernate">
|
|
||||||
<property name="label">Hibernate</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkMenuItem" id="shutdown">
|
<object class="GtkMenuItem" id="hibernate">
|
||||||
<property name="label">Shutdown</property>
|
<property name="label">Hibernate</property>
|
||||||
</object>
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkMenuItem" id="shutdown">
|
||||||
|
<property name="label">Shutdown</property>
|
||||||
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkSeparatorMenuItem" id="delimiter1"/>
|
<object class="GtkSeparatorMenuItem" id="delimiter1"/>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkMenuItem" id="reboot">
|
<object class="GtkMenuItem" id="reboot">
|
||||||
<property name="label">Reboot</property>
|
<property name="label">Reboot</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
</interface>
|
</interface>
|
||||||
|
|||||||
+13
-13
@@ -33,21 +33,21 @@ 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)) {
|
||||||
}
|
if (entry.is_regular_file()) {
|
||||||
for (const auto& entry : std::filesystem::recursive_directory_iterator(dir)) {
|
std::string filename = entry.path().filename().string();
|
||||||
if (entry.is_regular_file()) {
|
if (filename.size() < suffix.size()) {
|
||||||
std::string filename = entry.path().filename().string();
|
continue;
|
||||||
if (filename.size() < suffix.size()) {
|
}
|
||||||
continue;
|
if ((filename.compare(filename.size() - suffix.size(), suffix.size(), suffix) == 0) ||
|
||||||
}
|
(check_lower_case && filename.compare(filename.size() - suffix.size(), suffix.size(),
|
||||||
if ((filename.compare(filename.size() - suffix.size(), suffix.size(), suffix) == 0) ||
|
toLowerCase(suffix)) == 0)) {
|
||||||
(check_lower_case && filename.compare(filename.size() - suffix.size(), suffix.size(),
|
return entry.path().string();
|
||||||
toLowerCase(suffix)) == 0)) {
|
}
|
||||||
return entry.path().string();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (const std::filesystem::filesystem_error&) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
|
|||||||
+48
-1
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
#include <gdkmm/pixbuf.h>
|
#include <gdkmm/pixbuf.h>
|
||||||
#include <spdlog/spdlog.h>
|
#include <spdlog/spdlog.h>
|
||||||
|
#include <regex>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
namespace waybar {
|
namespace waybar {
|
||||||
|
|
||||||
@@ -9,6 +11,10 @@ AIconLabel::AIconLabel(const Json::Value& config, const std::string& name, const
|
|||||||
const std::string& format, uint16_t interval, bool ellipsize,
|
const std::string& format, uint16_t interval, bool ellipsize,
|
||||||
bool enable_click, bool enable_scroll)
|
bool enable_click, bool enable_scroll)
|
||||||
: ALabel(config, name, id, format, interval, ellipsize, enable_click, enable_scroll) {
|
: ALabel(config, name, id, format, interval, ellipsize, enable_click, enable_scroll) {
|
||||||
|
if (config["icon-size"].isUInt()) {
|
||||||
|
app_icon_size_ = config["icon-size"].asUInt();
|
||||||
|
}
|
||||||
|
image_.set_pixel_size(app_icon_size_);
|
||||||
event_box_.remove();
|
event_box_.remove();
|
||||||
label_.unset_name();
|
label_.unset_name();
|
||||||
label_.get_style_context()->remove_class(MODULE_CLASS);
|
label_.get_style_context()->remove_class(MODULE_CLASS);
|
||||||
@@ -55,13 +61,54 @@ AIconLabel::AIconLabel(const Json::Value& config, const std::string& name, const
|
|||||||
event_box_.add(box_);
|
event_box_.add(box_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::tuple<std::string, std::string> AIconLabel::extractIcon(const std::string& input) {
|
||||||
|
std::string icon_result = "";
|
||||||
|
std::string label_result = input;
|
||||||
|
try {
|
||||||
|
static const std::regex icon_search(R"((?=\\0icon\\1f).+?(?=\\n))");
|
||||||
|
std::smatch icon_match;
|
||||||
|
if (std::regex_search(input, icon_match, icon_search)) {
|
||||||
|
icon_result = icon_match[0].str().substr(9);
|
||||||
|
|
||||||
|
static const std::regex clean_label_pattern(R"(\\0icon\\1f.+?\\n)");
|
||||||
|
label_result = std::regex_replace(input, clean_label_pattern, "");
|
||||||
|
}
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
spdlog::warn("Error while parsing icon from label. {}", e.what());
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::make_tuple(icon_result, label_result);
|
||||||
|
}
|
||||||
|
|
||||||
auto AIconLabel::update() -> void {
|
auto AIconLabel::update() -> void {
|
||||||
|
label_contains_icon = false;
|
||||||
|
|
||||||
|
auto [iconLabel, cleanLabel] = extractIcon(label_.get_label().c_str());
|
||||||
|
label_contains_icon = iconLabel.length() > 0;
|
||||||
|
|
||||||
|
if (label_contains_icon) {
|
||||||
|
label_.set_markup(cleanLabel);
|
||||||
|
|
||||||
|
if (iconLabel.front() == '/') {
|
||||||
|
int scaled_icon_size = app_icon_size_ * image_.get_scale_factor();
|
||||||
|
auto pixbuf = Gdk::Pixbuf::create_from_file(iconLabel, scaled_icon_size, scaled_icon_size);
|
||||||
|
|
||||||
|
auto surface = Gdk::Cairo::create_surface_from_pixbuf(pixbuf, image_.get_scale_factor(),
|
||||||
|
image_.get_window());
|
||||||
|
image_.set(surface);
|
||||||
|
image_.set_visible(true);
|
||||||
|
} else {
|
||||||
|
image_.set_from_icon_name(iconLabel, Gtk::ICON_SIZE_INVALID);
|
||||||
|
image_.set_visible(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
image_.set_visible(image_.get_visible() && iconEnabled());
|
image_.set_visible(image_.get_visible() && iconEnabled());
|
||||||
ALabel::update();
|
ALabel::update();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AIconLabel::iconEnabled() const {
|
bool AIconLabel::iconEnabled() const {
|
||||||
return config_["icon"].isBool() ? config_["icon"].asBool() : false;
|
return label_contains_icon || (config_["icon"].isBool() ? config_["icon"].asBool() : false);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace waybar
|
} // namespace waybar
|
||||||
|
|||||||
@@ -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()) {
|
||||||
|
|||||||
+15
-4
@@ -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()) {
|
||||||
setCursor(Gdk::HAND2);
|
if (config_["cursor"].asBool()) {
|
||||||
|
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;
|
||||||
|
|||||||
+71
-5
@@ -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.
|
||||||
*/
|
*/
|
||||||
gtk_layer_try_force_commit(gtk_window);
|
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);
|
||||||
|
}
|
||||||
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_);
|
||||||
|
}
|
||||||
|
|||||||
+41
-16
@@ -10,6 +10,7 @@
|
|||||||
#include "idle-inhibit-unstable-v1-client-protocol.h"
|
#include "idle-inhibit-unstable-v1-client-protocol.h"
|
||||||
#include "util/clara.hpp"
|
#include "util/clara.hpp"
|
||||||
#include "util/format.hpp"
|
#include "util/format.hpp"
|
||||||
|
#include "util/hex_checker.hpp"
|
||||||
|
|
||||||
waybar::Client* waybar::Client::inst() {
|
waybar::Client* waybar::Client::inst() {
|
||||||
static auto* c = new Client();
|
static auto* c = new Client();
|
||||||
@@ -22,21 +23,18 @@ void waybar::Client::handleGlobal(void* data, struct wl_registry* registry, uint
|
|||||||
|
|
||||||
if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0 &&
|
if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0 &&
|
||||||
version >= ZXDG_OUTPUT_V1_NAME_SINCE_VERSION) {
|
version >= ZXDG_OUTPUT_V1_NAME_SINCE_VERSION) {
|
||||||
|
|
||||||
if (client->xdg_output_manager != nullptr) {
|
if (client->xdg_output_manager != nullptr) {
|
||||||
zxdg_output_manager_v1_destroy(client->xdg_output_manager);
|
zxdg_output_manager_v1_destroy(client->xdg_output_manager);
|
||||||
client->xdg_output_manager = nullptr;
|
client->xdg_output_manager = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
client->xdg_output_manager = static_cast<struct zxdg_output_manager_v1*>(
|
client->xdg_output_manager = static_cast<struct zxdg_output_manager_v1*>(wl_registry_bind(
|
||||||
wl_registry_bind(registry, name, &zxdg_output_manager_v1_interface,
|
registry, name, &zxdg_output_manager_v1_interface, ZXDG_OUTPUT_V1_NAME_SINCE_VERSION));
|
||||||
ZXDG_OUTPUT_V1_NAME_SINCE_VERSION));
|
|
||||||
|
|
||||||
} else if (strcmp(interface, zwp_idle_inhibit_manager_v1_interface.name) == 0) {
|
} else if (strcmp(interface, zwp_idle_inhibit_manager_v1_interface.name) == 0) {
|
||||||
|
|
||||||
if (client->idle_inhibit_manager != nullptr) {
|
if (client->idle_inhibit_manager != nullptr) {
|
||||||
zwp_idle_inhibit_manager_v1_destroy(client->idle_inhibit_manager);
|
zwp_idle_inhibit_manager_v1_destroy(client->idle_inhibit_manager);
|
||||||
client->idle_inhibit_manager = nullptr;
|
client->idle_inhibit_manager = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
client->idle_inhibit_manager = static_cast<struct zwp_idle_inhibit_manager_v1*>(
|
client->idle_inhibit_manager = static_cast<struct zwp_idle_inhibit_manager_v1*>(
|
||||||
@@ -93,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) {
|
||||||
@@ -105,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();
|
||||||
@@ -209,11 +229,15 @@ auto waybar::Client::setupCss(const std::string& css_file) -> void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
css_provider_ = Gtk::CssProvider::create();
|
css_provider_ = Gtk::CssProvider::create();
|
||||||
if (!css_provider_->load_from_path(css_file)) {
|
auto [modified_css, was_transformed] = transform_8bit_to_hex(css_file);
|
||||||
css_provider_.reset();
|
if (was_transformed) {
|
||||||
throw std::runtime_error("Can't open style file");
|
css_provider_->load_from_data(modified_css);
|
||||||
|
} else {
|
||||||
|
if (!css_provider_->load_from_path(css_file)) {
|
||||||
|
css_provider_.reset();
|
||||||
|
throw std::runtime_error("Can't open style file");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Gtk::StyleContext::add_provider_for_screen(screen, css_provider_,
|
Gtk::StyleContext::add_provider_for_screen(screen, css_provider_,
|
||||||
GTK_STYLE_PROVIDER_PRIORITY_USER);
|
GTK_STYLE_PROVIDER_PRIORITY_USER);
|
||||||
}
|
}
|
||||||
@@ -304,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);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+16
-1
@@ -63,6 +63,7 @@ Group::Group(const std::string& name, const std::string& id, const Json::Value&
|
|||||||
? drawer_config["transition-left-to-right"].asBool()
|
? drawer_config["transition-left-to-right"].asBool()
|
||||||
: true);
|
: true);
|
||||||
click_to_reveal = drawer_config["click-to-reveal"].asBool();
|
click_to_reveal = drawer_config["click-to-reveal"].asBool();
|
||||||
|
reveal_delay = drawer_config["reveal-delay"].asInt();
|
||||||
|
|
||||||
const bool start_expanded =
|
const bool start_expanded =
|
||||||
(drawer_config["start-expanded"].isBool() ? drawer_config["start-expanded"].asBool()
|
(drawer_config["start-expanded"].isBool() ? drawer_config["start-expanded"].asBool()
|
||||||
@@ -104,13 +105,27 @@ void Group::hide_group() {
|
|||||||
|
|
||||||
bool Group::handleMouseEnter(GdkEventCrossing* const& e) {
|
bool Group::handleMouseEnter(GdkEventCrossing* const& e) {
|
||||||
if (!click_to_reveal) {
|
if (!click_to_reveal) {
|
||||||
show_group();
|
if (reveal_delay > 0) {
|
||||||
|
if (reveal_timeout_.connected()) {
|
||||||
|
reveal_timeout_.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
reveal_timeout_ = Glib::signal_timeout().connect(
|
||||||
|
[this]() { show_group(); return false; },
|
||||||
|
reveal_delay);
|
||||||
|
} else {
|
||||||
|
show_group();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Group::handleMouseLeave(GdkEventCrossing* const& e) {
|
bool Group::handleMouseLeave(GdkEventCrossing* const& e) {
|
||||||
if (!click_to_reveal && e->detail != GDK_NOTIFY_INFERIOR) {
|
if (!click_to_reveal && e->detail != GDK_NOTIFY_INFERIOR) {
|
||||||
|
if (reveal_delay > 0 && reveal_timeout_.connected()) {
|
||||||
|
reveal_timeout_.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
hide_group();
|
hide_group();
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
+5
-1
@@ -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,13 +121,15 @@ 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:
|
||||||
handleUserSignal(SIGUSR1, reload);
|
handleUserSignal(SIGUSR1, reload);
|
||||||
|
|||||||
+112
-63
@@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
#include "util/command.hpp"
|
#include "util/command.hpp"
|
||||||
#if defined(__FreeBSD__)
|
#if defined(__FreeBSD__)
|
||||||
@@ -33,6 +36,11 @@ waybar::modules::Battery::Battery(const std::string& id, const Bar& bar, const J
|
|||||||
}
|
}
|
||||||
udev_monitor_enable_receiving(mon_.get());
|
udev_monitor_enable_receiving(mon_.get());
|
||||||
|
|
||||||
|
if (config_["smooth-power"].isBool()) {
|
||||||
|
smoothPowerEnable_ = config_["smooth-power"].asBool();
|
||||||
|
if (smoothPowerEnable_ && config_["smooth-power-time-constant"].isNumeric())
|
||||||
|
time_constant_s_ = std::max(1.0, config_["smooth-power-time-constant"].asDouble());
|
||||||
|
}
|
||||||
if (config_["weighted-average"].isBool()) weightedAverage_ = config_["weighted-average"].asBool();
|
if (config_["weighted-average"].isBool()) weightedAverage_ = config_["weighted-average"].asBool();
|
||||||
#endif
|
#endif
|
||||||
spdlog::debug("battery: worker interval is {}", interval_.count());
|
spdlog::debug("battery: worker interval is {}", interval_.count());
|
||||||
@@ -117,17 +125,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 +161,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 +282,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"};
|
||||||
std::getline(std::ifstream(adapter_ / "status"), _status);
|
if (!std::getline(f, _status)) {
|
||||||
} else {
|
std::getline(std::ifstream(adapter_ / "status"), _status);
|
||||||
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 +295,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"}) {
|
||||||
current_now_exists = true;
|
if (current_now_f >> _current_now_int) {
|
||||||
std::ifstream(bat / "current_now") >> _current_now_int;
|
current_now_exists = true;
|
||||||
} else if (fs::exists(bat / "current_avg")) {
|
}
|
||||||
current_now_exists = true;
|
} else if (std::ifstream current_avg_f{bat / "current_avg"}) {
|
||||||
std::ifstream(bat / "current_avg") >> _current_now_int;
|
if (current_avg_f >> _current_now_int) {
|
||||||
|
current_now_exists = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// 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"}) {
|
||||||
time_to_empty_now_exists = true;
|
if (f >> time_to_empty_now) {
|
||||||
std::ifstream(bat / "time_to_empty_now") >> time_to_empty_now;
|
time_to_empty_now_exists = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fs::exists(bat / "time_to_full_now")) {
|
if (std::ifstream f{bat / "time_to_full_now"}) {
|
||||||
time_to_full_now_exists = true;
|
if (f >> time_to_full_now) {
|
||||||
std::ifstream(bat / "time_to_full_now") >> time_to_full_now;
|
time_to_full_now_exists = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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"}) {
|
||||||
voltage_now_exists = true;
|
if (voltage_now_f >> voltage_now) {
|
||||||
std::ifstream(bat / "voltage_now") >> voltage_now;
|
voltage_now_exists = true;
|
||||||
} else if (fs::exists(bat / "voltage_avg")) {
|
}
|
||||||
voltage_now_exists = true;
|
} else if (std::ifstream voltage_avg_f{bat / "voltage_avg"}) {
|
||||||
std::ifstream(bat / "voltage_avg") >> voltage_now;
|
if (voltage_avg_f >> voltage_now) {
|
||||||
|
voltage_now_exists = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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"}) {
|
||||||
charge_full_exists = true;
|
if (f >> charge_full) {
|
||||||
std::ifstream(bat / "charge_full") >> charge_full;
|
charge_full_exists = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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"}) {
|
||||||
charge_full_design_exists = true;
|
if (f >> charge_full_design) {
|
||||||
std::ifstream(bat / "charge_full_design") >> charge_full_design;
|
charge_full_design_exists = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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"}) {
|
||||||
charge_now_exists = true;
|
if (f >> charge_now) {
|
||||||
std::ifstream(bat / "charge_now") >> charge_now;
|
charge_now_exists = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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"}) {
|
||||||
power_now_exists = true;
|
if (f >> _power_now_int) {
|
||||||
std::ifstream(bat / "power_now") >> _power_now_int;
|
power_now_exists = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// 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 +370,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"}) {
|
||||||
energy_now_exists = true;
|
if (f >> energy_now) {
|
||||||
std::ifstream(bat / "energy_now") >> energy_now;
|
energy_now_exists = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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"}) {
|
||||||
energy_full_exists = true;
|
if (f >> energy_full) {
|
||||||
std::ifstream(bat / "energy_full") >> energy_full;
|
energy_full_exists = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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"}) {
|
||||||
energy_full_design_exists = true;
|
if (f >> energy_full_design) {
|
||||||
std::ifstream(bat / "energy_full_design") >> energy_full_design;
|
energy_full_design_exists = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 +424,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"}) {
|
||||||
capacity_exists = true;
|
if (f >> capacity) {
|
||||||
std::ifstream(bat / "capacity") >> capacity;
|
capacity_exists = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!voltage_now_exists) {
|
if (!voltage_now_exists) {
|
||||||
@@ -564,11 +585,31 @@ waybar::modules::Battery::getInfos() {
|
|||||||
if (online && current_status != "Discharging") status = "Plugged";
|
if (online && current_status != "Discharging") status = "Plugged";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (total_energy_exists && total_power_exists && total_power != 0) {
|
||||||
|
if (!smoothPowerEnable_) {
|
||||||
|
smooth_power_ = total_power;
|
||||||
|
} else {
|
||||||
|
if (status != old_status_raw_) {
|
||||||
|
smooth_power_ = total_power;
|
||||||
|
last_t_ = std::chrono::steady_clock::now();
|
||||||
|
} else {
|
||||||
|
std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
|
||||||
|
double dt_s =
|
||||||
|
std::chrono::duration_cast<std::chrono::duration<double> >(now - last_t_).count();
|
||||||
|
smooth_power_ = smooth_power_ + ((1 - std::exp(-dt_s / time_constant_s_)) *
|
||||||
|
(total_power - smooth_power_));
|
||||||
|
last_t_ = now;
|
||||||
|
}
|
||||||
|
|
||||||
|
old_status_raw_ = status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
float time_remaining{0.0f};
|
float time_remaining{0.0f};
|
||||||
if (status == "Discharging" && time_to_empty_now_exists) {
|
if (status == "Discharging" && time_to_empty_now_exists) {
|
||||||
if (time_to_empty_now != 0) time_remaining = (float)time_to_empty_now / 3600.0f;
|
if (time_to_empty_now != 0) time_remaining = (float)time_to_empty_now / 3600.0f;
|
||||||
} else if (status == "Discharging" && total_power_exists && total_energy_exists) {
|
} else if (status == "Discharging" && total_power_exists && total_energy_exists) {
|
||||||
if (total_power != 0) time_remaining = (float)total_energy / total_power;
|
if (smooth_power_ != 0) time_remaining = (float)total_energy / smooth_power_;
|
||||||
} else if (status == "Charging" && time_to_full_now_exists) {
|
} else if (status == "Charging" && time_to_full_now_exists) {
|
||||||
if (time_to_full_now_exists && (time_to_full_now != 0))
|
if (time_to_full_now_exists && (time_to_full_now != 0))
|
||||||
time_remaining = -(float)time_to_full_now / 3600.0f;
|
time_remaining = -(float)time_to_full_now / 3600.0f;
|
||||||
@@ -586,11 +627,12 @@ 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -687,13 +729,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() {
|
||||||
|
|||||||
@@ -70,17 +70,33 @@ static const zdwl_ipc_output_v2_listener output_status_listener_impl{
|
|||||||
|
|
||||||
static void handle_global(void* data, struct wl_registry* registry, uint32_t name,
|
static void handle_global(void* data, struct wl_registry* registry, uint32_t name,
|
||||||
const char* interface, uint32_t version) {
|
const char* interface, uint32_t version) {
|
||||||
if (std::strcmp(interface, zdwl_ipc_manager_v2_interface.name) == 0) {
|
|
||||||
static_cast<Tags*>(data)->status_manager_ = static_cast<struct zdwl_ipc_manager_v2*>(
|
if (std::strcmp(interface, zdwl_ipc_manager_v2_interface.name) == 0) {
|
||||||
(zdwl_ipc_manager_v2*)wl_registry_bind(registry, name, &zdwl_ipc_manager_v2_interface, 1));
|
auto* self = static_cast<Tags*>(data);
|
||||||
}
|
|
||||||
if (std::strcmp(interface, wl_seat_interface.name) == 0) {
|
if (self->status_manager_) {
|
||||||
version = std::min<uint32_t>(version, 1);
|
zdwl_ipc_manager_v2_destroy(self->status_manager_);
|
||||||
static_cast<Tags*>(data)->seat_ =
|
self->status_manager_ = nullptr;
|
||||||
static_cast<struct wl_seat*>(wl_registry_bind(registry, name, &wl_seat_interface, version));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self->status_manager_ = static_cast<struct zdwl_ipc_manager_v2*>(
|
||||||
|
wl_registry_bind(registry, name, &zdwl_ipc_manager_v2_interface, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std::strcmp(interface, wl_seat_interface.name) == 0) {
|
||||||
|
auto* self = static_cast<Tags*>(data);
|
||||||
|
|
||||||
|
if (self->seat_) {
|
||||||
|
wl_seat_destroy(self->seat_);
|
||||||
|
self->seat_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
version = std::min<uint32_t>(version, 1);
|
||||||
|
|
||||||
|
self->seat_ = static_cast<struct wl_seat*>(
|
||||||
|
wl_registry_bind(registry, name, &wl_seat_interface, version));
|
||||||
|
}
|
||||||
|
}
|
||||||
static void handle_global_remove(void* data, struct wl_registry* registry, uint32_t name) {
|
static void handle_global_remove(void* data, struct wl_registry* registry, uint32_t name) {
|
||||||
/* Ignore event */
|
/* Ignore event */
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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")) {
|
||||||
return; // ignore
|
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
|
||||||
|
}
|
||||||
|
|
||||||
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 <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -30,6 +31,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);
|
||||||
|
|
||||||
@@ -45,6 +51,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) {
|
||||||
@@ -66,25 +78,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) {
|
||||||
@@ -115,7 +223,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) {
|
||||||
@@ -130,7 +242,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; });
|
||||||
@@ -142,7 +257,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()) {
|
||||||
@@ -161,6 +276,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()) {
|
||||||
@@ -234,6 +360,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");
|
||||||
@@ -270,7 +397,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
|
||||||
@@ -301,15 +430,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()),
|
||||||
@@ -334,8 +466,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()) {
|
||||||
@@ -349,7 +481,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 {
|
||||||
|
|||||||
@@ -78,6 +78,53 @@ auto supportsLockStates(const libevdev* dev) -> bool {
|
|||||||
libevdev_has_event_code(dev, EV_LED, LED_SCROLLL);
|
libevdev_has_event_code(dev, EV_LED, LED_SCROLLL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto isCommonFormatIcons(const Json::Value& config) -> bool {
|
||||||
|
return config["format-icons"].isObject() && (config["format-icons"]["locked"].isString() ||
|
||||||
|
config["format-icons"]["unlocked"].isString());
|
||||||
|
}
|
||||||
|
|
||||||
|
auto keyStateToIcons(const Json::Value& config)
|
||||||
|
-> std::unordered_map<std::string, std::vector<std::string>> {
|
||||||
|
std::unordered_map<std::string, std::vector<std::string>> key_icon_states;
|
||||||
|
std::vector<std::string> default_icons = {"unlocked", "locked"};
|
||||||
|
|
||||||
|
if (isCommonFormatIcons(config)) {
|
||||||
|
std::vector<std::string> icons = {
|
||||||
|
config["format-icons"]["unlocked"].isString()
|
||||||
|
? config["format-icons"]["unlocked"].asString()
|
||||||
|
: "unlocked",
|
||||||
|
config["format-icons"]["locked"].isString() ? config["format-icons"]["locked"].asString()
|
||||||
|
: "locked",
|
||||||
|
};
|
||||||
|
key_icon_states["Lock"] = icons;
|
||||||
|
return key_icon_states;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool found_any = false;
|
||||||
|
for (const auto& key : std::vector<std::string>{"numlock", "capslock", "scrolllock"}) {
|
||||||
|
std::string map_key = key.substr(0, key.length() - 4);
|
||||||
|
map_key[0] = std::toupper(map_key[0]);
|
||||||
|
if (config["format-icons"].isObject() && config["format-icons"][key].isObject()) {
|
||||||
|
std::string unlocked = config["format-icons"][key]["unlocked"].isString()
|
||||||
|
? config["format-icons"][key]["unlocked"].asString()
|
||||||
|
: "unlocked";
|
||||||
|
std::string locked = config["format-icons"][key]["locked"].isString()
|
||||||
|
? config["format-icons"][key]["locked"].asString()
|
||||||
|
: "locked";
|
||||||
|
key_icon_states[map_key] = {unlocked, locked};
|
||||||
|
found_any = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found_any) {
|
||||||
|
key_icon_states["Num"] = default_icons;
|
||||||
|
key_icon_states["Caps"] = default_icons;
|
||||||
|
key_icon_states["Scroll"] = default_icons;
|
||||||
|
}
|
||||||
|
|
||||||
|
return key_icon_states;
|
||||||
|
}
|
||||||
|
|
||||||
waybar::modules::KeyboardState::KeyboardState(const std::string& id, const Bar& bar,
|
waybar::modules::KeyboardState::KeyboardState(const std::string& id, const Bar& bar,
|
||||||
const Json::Value& config)
|
const Json::Value& config)
|
||||||
: AModule(config, "keyboard-state", id, false, !config["disable-scroll"].asBool()),
|
: AModule(config, "keyboard-state", id, false, !config["disable-scroll"].asBool()),
|
||||||
@@ -98,12 +145,7 @@ waybar::modules::KeyboardState::KeyboardState(const std::string& id, const Bar&
|
|||||||
: "{name} {icon}"),
|
: "{name} {icon}"),
|
||||||
interval_(
|
interval_(
|
||||||
std::chrono::seconds(config_["interval"].isUInt() ? config_["interval"].asUInt() : 1)),
|
std::chrono::seconds(config_["interval"].isUInt() ? config_["interval"].asUInt() : 1)),
|
||||||
icon_locked_(config_["format-icons"]["locked"].isString()
|
key_icon_states_(keyStateToIcons(config_)),
|
||||||
? config_["format-icons"]["locked"].asString()
|
|
||||||
: "locked"),
|
|
||||||
icon_unlocked_(config_["format-icons"]["unlocked"].isString()
|
|
||||||
? config_["format-icons"]["unlocked"].asString()
|
|
||||||
: "unlocked"),
|
|
||||||
devices_path_("/dev/input/"),
|
devices_path_("/dev/input/"),
|
||||||
libinput_(nullptr),
|
libinput_(nullptr),
|
||||||
libinput_devices_({}) {
|
libinput_devices_({}) {
|
||||||
@@ -290,7 +332,7 @@ auto waybar::modules::KeyboardState::update() -> void {
|
|||||||
bool state;
|
bool state;
|
||||||
Gtk::Label& label;
|
Gtk::Label& label;
|
||||||
const std::string& format;
|
const std::string& format;
|
||||||
const char* name;
|
const std::string name;
|
||||||
} label_states[] = {
|
} label_states[] = {
|
||||||
{(bool)numl, numlock_label_, numlock_format_, "Num"},
|
{(bool)numl, numlock_label_, numlock_format_, "Num"},
|
||||||
{(bool)capsl, capslock_label_, capslock_format_, "Caps"},
|
{(bool)capsl, capslock_label_, capslock_format_, "Caps"},
|
||||||
@@ -298,8 +340,21 @@ auto waybar::modules::KeyboardState::update() -> void {
|
|||||||
};
|
};
|
||||||
for (auto& label_state : label_states) {
|
for (auto& label_state : label_states) {
|
||||||
std::string text;
|
std::string text;
|
||||||
|
std::string map_key = isCommonFormatIcons(config_) ? "Lock" : label_state.name;
|
||||||
|
|
||||||
|
if (key_icon_states_.find(map_key) == key_icon_states_.end()) {
|
||||||
|
spdlog::warn("keyboard-state: Missing icon configuration for '{}'", map_key);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& icons = key_icon_states_[map_key];
|
||||||
|
if (icons.size() < 2) {
|
||||||
|
spdlog::warn("keyboard-state: Invalid icon vector size for '{}'", map_key);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
text = fmt::format(fmt::runtime(label_state.format),
|
text = fmt::format(fmt::runtime(label_state.format),
|
||||||
fmt::arg("icon", label_state.state ? icon_locked_ : icon_unlocked_),
|
fmt::arg("icon", label_state.state ? icons[1] : icons[0]),
|
||||||
fmt::arg("name", label_state.name));
|
fmt::arg("name", label_state.name));
|
||||||
label_state.label.set_markup(text);
|
label_state.label.set_markup(text);
|
||||||
if (label_state.state) {
|
if (label_state.state) {
|
||||||
|
|||||||
@@ -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());
|
||||||
|
|||||||
+16
-59
@@ -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>
|
||||||
@@ -206,53 +207,6 @@ 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());
|
||||||
@@ -262,19 +216,19 @@ auto Mpris::getArtistStr(const PlayerInfo& info, bool truncated) -> std::string
|
|||||||
artist = album_artist;
|
artist = album_artist;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -302,9 +256,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();
|
||||||
|
|
||||||
@@ -323,7 +277,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;
|
||||||
@@ -589,14 +543,14 @@ auto Mpris::getPlayerInfo() -> std::optional<PlayerInfo> {
|
|||||||
}
|
}
|
||||||
if (error) goto errorexit;
|
if (error) goto errorexit;
|
||||||
|
|
||||||
if (auto* album_artist_ = playerctl_player_print_metadata_prop(player, "xesam:albumArtist", &error)) {
|
if (auto* album_artist_ =
|
||||||
|
playerctl_player_print_metadata_prop(player, "xesam:albumArtist", &error)) {
|
||||||
spdlog::debug("mpris[{}]: albumArtist = {}", info.name, album_artist_);
|
spdlog::debug("mpris[{}]: albumArtist = {}", info.name, album_artist_);
|
||||||
info.album_artist = album_artist_;
|
info.album_artist = album_artist_;
|
||||||
g_free(album_artist_);
|
g_free(album_artist_);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auto* album_ = playerctl_player_get_album(last_active_player_, &error)) {
|
if (auto* album_ = playerctl_player_get_album(last_active_player_, &error)) {
|
||||||
|
|
||||||
spdlog::debug("mpris[{}]: album = {}", info.name, album_);
|
spdlog::debug("mpris[{}]: album = {}", info.name, album_);
|
||||||
info.album = album_;
|
info.album = album_;
|
||||||
g_free(album_);
|
g_free(album_);
|
||||||
@@ -793,9 +747,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)),
|
||||||
|
|||||||
+37
-18
@@ -280,23 +280,39 @@ 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();
|
||||||
if (elapsed_seconds <= 0.0) {
|
|
||||||
elapsed_seconds = std::chrono::duration<double>(interval_).count();
|
|
||||||
}
|
|
||||||
bandwidth_last_sample_time_ = now;
|
|
||||||
|
|
||||||
auto bandwidth = readBandwidthUsage();
|
|
||||||
auto bandwidth_down = 0ull;
|
auto bandwidth_down = 0ull;
|
||||||
auto bandwidth_up = 0ull;
|
auto bandwidth_up = 0ull;
|
||||||
if (bandwidth.has_value()) {
|
|
||||||
auto down_octets = (*bandwidth).first;
|
|
||||||
auto up_octets = (*bandwidth).second;
|
|
||||||
|
|
||||||
bandwidth_down = down_octets - bandwidth_down_total_;
|
// Only recalculate bandwidth when enough time has elapsed since the last
|
||||||
bandwidth_down_total_ = down_octets;
|
// 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) {
|
||||||
|
elapsed_seconds = std::chrono::duration<double>(interval_).count();
|
||||||
|
}
|
||||||
|
bandwidth_last_sample_time_ = now;
|
||||||
|
|
||||||
bandwidth_up = up_octets - bandwidth_up_total_;
|
auto bandwidth = readBandwidthUsage();
|
||||||
bandwidth_up_total_ = up_octets;
|
if (bandwidth.has_value()) {
|
||||||
|
auto down_octets = (*bandwidth).first;
|
||||||
|
auto up_octets = (*bandwidth).second;
|
||||||
|
|
||||||
|
bandwidth_down = down_octets - bandwidth_down_total_;
|
||||||
|
bandwidth_down_total_ = down_octets;
|
||||||
|
|
||||||
|
bandwidth_up = up_octets - bandwidth_up_total_;
|
||||||
|
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 {
|
||||||
net->ipaddr_.clear();
|
if (ifa->ifa_family == AF_INET) {
|
||||||
net->ipaddr6_.clear();
|
net->ipaddr_.clear();
|
||||||
net->cidr_ = 0;
|
net->cidr_ = 0;
|
||||||
net->cidr6_ = 0;
|
net->netmask_.clear();
|
||||||
net->netmask_.clear();
|
} else if (ifa->ifa_family == AF_INET6) {
|
||||||
net->netmask6_.clear();
|
net->ipaddr6_.clear();
|
||||||
|
net->cidr6_ = 0;
|
||||||
|
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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ PowerProfilesDaemon::PowerProfilesDaemon(const std::string& id, const Json::Valu
|
|||||||
if (config_["tooltip-format"].isString()) {
|
if (config_["tooltip-format"].isString()) {
|
||||||
tooltipFormat_ = config_["tooltip-format"].asString();
|
tooltipFormat_ = config_["tooltip-format"].asString();
|
||||||
} else {
|
} else {
|
||||||
tooltipFormat_ = "Power profile: {profile}\nDriver: {driver}";
|
tooltipFormat_ =
|
||||||
|
"Power profile: {profile}\nCPU driver: {cpu_driver}\nPlatform driver: {platform_driver}";
|
||||||
}
|
}
|
||||||
// Fasten your seatbelt, we're up for quite a ride. The rest of the
|
// Fasten your seatbelt, we're up for quite a ride. The rest of the
|
||||||
// init is performed asynchronously. There's 2 callbacks involved.
|
// init is performed asynchronously. There's 2 callbacks involved.
|
||||||
@@ -95,15 +96,51 @@ void PowerProfilesDaemon::populateInitState() {
|
|||||||
powerProfilesProxy_->get_cached_property(profilesVariant, "Profiles");
|
powerProfilesProxy_->get_cached_property(profilesVariant, "Profiles");
|
||||||
for (auto& variantDict : profilesVariant.get()) {
|
for (auto& variantDict : profilesVariant.get()) {
|
||||||
Glib::ustring name;
|
Glib::ustring name;
|
||||||
|
// Legacy single `Driver` property, still exposed by older
|
||||||
|
// power-profiles-daemon versions.
|
||||||
Glib::ustring driver;
|
Glib::ustring driver;
|
||||||
|
Glib::ustring cpuDriver;
|
||||||
|
Glib::ustring platformDriver;
|
||||||
if (auto p = variantDict.find("Profile"); p != variantDict.end()) {
|
if (auto p = variantDict.find("Profile"); p != variantDict.end()) {
|
||||||
name = p->second.get();
|
name = p->second.get();
|
||||||
}
|
}
|
||||||
if (auto d = variantDict.find("Driver"); d != variantDict.end()) {
|
if (auto d = variantDict.find("Driver"); d != variantDict.end()) {
|
||||||
driver = d->second.get();
|
driver = d->second.get();
|
||||||
}
|
}
|
||||||
|
if (auto cd = variantDict.find("CpuDriver"); cd != variantDict.end()) {
|
||||||
|
cpuDriver = cd->second.get();
|
||||||
|
}
|
||||||
|
if (auto pd = variantDict.find("PlatformDriver"); pd != variantDict.end()) {
|
||||||
|
platformDriver = pd->second.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recent power-profiles-daemon versions split the single `Driver`
|
||||||
|
// property into `CpuDriver` and `PlatformDriver`. When talking to an
|
||||||
|
// older daemon that only exposes `Driver`, fall back to it so the new
|
||||||
|
// {cpu_driver}/{platform_driver} placeholders still resolve.
|
||||||
|
if (cpuDriver.empty()) {
|
||||||
|
cpuDriver = driver;
|
||||||
|
}
|
||||||
|
if (platformDriver.empty()) {
|
||||||
|
platformDriver = driver;
|
||||||
|
}
|
||||||
|
// Conversely, keep the legacy {driver} placeholder working against a
|
||||||
|
// recent daemon that no longer exposes `Driver` by deriving it from
|
||||||
|
// the CPU driver.
|
||||||
|
if (driver.empty()) {
|
||||||
|
driver = cpuDriver;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (driver.empty()) {
|
||||||
|
driver = "Unavailable";
|
||||||
|
cpuDriver = "Unavailable";
|
||||||
|
platformDriver = "Unavailable";
|
||||||
|
spdlog::warn("Cannot find power profiles daemon driver.");
|
||||||
|
}
|
||||||
|
|
||||||
if (!name.empty()) {
|
if (!name.empty()) {
|
||||||
availableProfiles_.emplace_back(std::move(name), std::move(driver));
|
availableProfiles_.emplace_back(std::move(name), std::move(driver), std::move(cpuDriver),
|
||||||
|
std::move(platformDriver));
|
||||||
} else {
|
} else {
|
||||||
spdlog::error(
|
spdlog::error(
|
||||||
"Power profiles daemon: power-profiles-daemon sent us an empty power profile name. "
|
"Power profiles daemon: power-profiles-daemon sent us an empty power profile name. "
|
||||||
@@ -153,7 +190,10 @@ auto PowerProfilesDaemon::update() -> void {
|
|||||||
// Set label
|
// Set label
|
||||||
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||||
store.push_back(fmt::arg("profile", profile.name));
|
store.push_back(fmt::arg("profile", profile.name));
|
||||||
|
// Legacy placeholder, kept for backward compatibility with existing configs.
|
||||||
store.push_back(fmt::arg("driver", profile.driver));
|
store.push_back(fmt::arg("driver", profile.driver));
|
||||||
|
store.push_back(fmt::arg("cpu_driver", profile.cpuDriver));
|
||||||
|
store.push_back(fmt::arg("platform_driver", profile.platformDriver));
|
||||||
store.push_back(fmt::arg("icon", getIcon(0, profile.name)));
|
store.push_back(fmt::arg("icon", getIcon(0, profile.name)));
|
||||||
label_.set_markup(fmt::vformat(format_, store));
|
label_.set_markup(fmt::vformat(format_, store));
|
||||||
if (tooltipEnabled()) {
|
if (tooltipEnabled()) {
|
||||||
|
|||||||
@@ -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());
|
if (!previously_muted) {
|
||||||
}
|
scale_.get_style_context()->add_class("muted");
|
||||||
break;
|
}
|
||||||
|
} 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;
|
|
||||||
|
|
||||||
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(slider_value, min_, max_);
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
|
}
|
||||||
|
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;
|
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") {
|
||||||
icon_name = get_variant<std::string>(value);
|
if (has_custom_icon_) {
|
||||||
|
spdlog::trace("Item '{}': ignoring IconName update, custom icon is set", id);
|
||||||
|
} else {
|
||||||
|
icon_name = get_variant<std::string>(value);
|
||||||
|
}
|
||||||
} else if (name == "IconPixmap") {
|
} else if (name == "IconPixmap") {
|
||||||
icon_pixmap = this->extractPixBuf(value.gobj());
|
if (has_custom_icon_) {
|
||||||
|
spdlog::trace("Item '{}': ignoring IconPixmap update, custom icon is set", id);
|
||||||
|
} else {
|
||||||
|
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_);
|
{
|
||||||
auto payload = parser_.parse(res.payload)["input"];
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
if (payload["type"].asString() == "keyboard") {
|
auto root = parser_.parse(res.payload);
|
||||||
set_current_layout(payload[XKB_ACTIVE_LAYOUT_NAME_KEY].asString());
|
auto change = root["change"].asString();
|
||||||
|
auto payload = root["input"];
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
dp.emit();
|
|
||||||
} 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,24 +332,44 @@ 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 full_name;
|
||||||
|
if (!config_["disable-markup"].asBool()) {
|
||||||
|
full_name = g_markup_escape_text((*it)["name"].asString().c_str(), -1);
|
||||||
|
} else {
|
||||||
|
full_name = (*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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto index = (*it)["num"].asInt();
|
||||||
|
|
||||||
if (config_["format"].isString()) {
|
if (config_["format"].isString()) {
|
||||||
auto format = config_["format"].asString();
|
std::string format;
|
||||||
output = fmt::format(
|
if (config_["format-for-negative-index"].isString() && index < 0) {
|
||||||
fmt::runtime(format), fmt::arg("icon", getIcon(output, *it)), fmt::arg("value", output),
|
format = config_["format-for-negative-index"].asString();
|
||||||
fmt::arg("name", trimWorkspaceName(output)), fmt::arg("index", (*it)["num"].asString()),
|
} else {
|
||||||
fmt::arg("windows",
|
format = config_["format"].asString();
|
||||||
windows.substr(0, windows.length() - m_formatWindowSeparator.length())),
|
}
|
||||||
fmt::arg("output", (*it)["output"].asString()));
|
|
||||||
|
auto name = trimWorkspaceName(full_name);
|
||||||
|
auto output = (*it)["output"].asString();
|
||||||
|
auto icon = getIcon(full_name, *it);
|
||||||
|
auto separated_windows =
|
||||||
|
windows.substr(0, windows.length() - m_formatWindowSeparator.length());
|
||||||
|
|
||||||
|
full_name =
|
||||||
|
fmt::format(fmt::runtime(format), fmt::arg("index", index), fmt::arg("name", name),
|
||||||
|
fmt::arg("value", full_name), fmt::arg("output", output),
|
||||||
|
fmt::arg("icon", icon), fmt::arg("windows", separated_windows));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!config_["disable-markup"].asBool()) {
|
if (!config_["disable-markup"].asBool()) {
|
||||||
static_cast<Gtk::Label*>(button.get_children()[0])->set_markup(output);
|
static_cast<Gtk::Label*>(button.get_children()[0])->set_markup(full_name);
|
||||||
} else {
|
} else {
|
||||||
button.set_label(output);
|
button.set_label(full_name);
|
||||||
}
|
}
|
||||||
onButtonReady(*it, button);
|
onButtonReady(*it, button);
|
||||||
}
|
}
|
||||||
@@ -369,11 +392,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"
|
: "";
|
||||||
: "",
|
if (node["num"].asInt() >= 0) {
|
||||||
node["name"].asString()));
|
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 }
|
||||||
auto& output = state.outputs.at(data["output-data"]["name"].asString());
|
try {
|
||||||
auto wset_idx = data["new-wset-data"]["index"].asUInt();
|
auto& output = state.outputs.at(data["output-data"]["name"].asString());
|
||||||
state.wsets.at(wset_idx).output = output;
|
auto wset_idx = data["new-wset-data"]["index"].asUInt();
|
||||||
output.wset_idx = wset_idx;
|
state.wsets.at(wset_idx).output = output;
|
||||||
|
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_,
|
||||||
|
|||||||
+282
-5
@@ -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);
|
||||||
|
|
||||||
content_.add(text_before_);
|
/* When "expand" is enabled the buttons stretch to fill the taskbar and the
|
||||||
content_.add(icon_);
|
* titles ellipsize to fit within the available space. This only makes sense
|
||||||
content_.add(text_after_);
|
* 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(icon_);
|
||||||
|
content_.add(text_after_);
|
||||||
|
}
|
||||||
|
|
||||||
content_.show();
|
content_.show();
|
||||||
button.add(content_);
|
button.add(content_);
|
||||||
@@ -293,9 +319,12 @@ void Task::handle_output_enter(struct wl_output* output) {
|
|||||||
button.signal_size_allocate().connect_notify(
|
button.signal_size_allocate().connect_notify(
|
||||||
sigc::mem_fun(this, &Task::on_button_size_allocated));
|
sigc::mem_fun(this, &Task::on_button_size_allocated));
|
||||||
tbar_->add_button(button);
|
tbar_->add_button(button);
|
||||||
button.show();
|
if (!config_["active-only"].asBool() || active()) {
|
||||||
|
button.show();
|
||||||
|
}
|
||||||
button_visible_ = true;
|
button_visible_ = true;
|
||||||
spdlog::debug("{} now visible on {}", repr(), bar_.output->name);
|
spdlog::debug("{} now visible on {}", repr(), bar_.output->name);
|
||||||
|
tbar_->update_bar_css_classes();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -308,6 +337,7 @@ void Task::handle_output_leave(struct wl_output* output) {
|
|||||||
button.hide();
|
button.hide();
|
||||||
button_visible_ = false;
|
button_visible_ = false;
|
||||||
spdlog::debug("{} now invisible on {}", repr(), bar_.output->name);
|
spdlog::debug("{} now invisible on {}", repr(), bar_.output->name);
|
||||||
|
tbar_->update_bar_css_classes();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -350,6 +380,20 @@ void Task::handle_done() {
|
|||||||
button.get_style_context()->remove_class("fullscreen");
|
button.get_style_context()->remove_class("fullscreen");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (button_visible_ && config_["active-only"].asBool()) {
|
||||||
|
if (active()) {
|
||||||
|
button.show();
|
||||||
|
} else {
|
||||||
|
button.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (active()) {
|
||||||
|
tbar_->assign_current_workspace(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
tbar_->update_bar_css_classes();
|
||||||
|
|
||||||
if (config_["active-first"].isBool() && config_["active-first"].asBool() && active())
|
if (config_["active-first"].isBool() && config_["active-first"].asBool() && active())
|
||||||
tbar_->move_button(button, 0);
|
tbar_->move_button(button, 0);
|
||||||
|
|
||||||
@@ -551,6 +595,8 @@ static void handle_global(void* data, struct wl_registry* registry, uint32_t nam
|
|||||||
const char* interface, uint32_t version) {
|
const char* interface, uint32_t version) {
|
||||||
if (std::strcmp(interface, zwlr_foreign_toplevel_manager_v1_interface.name) == 0) {
|
if (std::strcmp(interface, zwlr_foreign_toplevel_manager_v1_interface.name) == 0) {
|
||||||
static_cast<Taskbar*>(data)->register_manager(registry, name, version);
|
static_cast<Taskbar*>(data)->register_manager(registry, name, version);
|
||||||
|
} else if (std::strcmp(interface, ext_workspace_manager_v1_interface.name) == 0) {
|
||||||
|
static_cast<Taskbar*>(data)->register_workspace_manager(registry, name, version);
|
||||||
} else if (std::strcmp(interface, wl_seat_interface.name) == 0) {
|
} else if (std::strcmp(interface, wl_seat_interface.name) == 0) {
|
||||||
static_cast<Taskbar*>(data)->register_seat(registry, name, version);
|
static_cast<Taskbar*>(data)->register_seat(registry, name, version);
|
||||||
}
|
}
|
||||||
@@ -568,6 +614,7 @@ Taskbar::Taskbar(const std::string& id, const waybar::Bar& bar, const Json::Valu
|
|||||||
bar_(bar),
|
bar_(bar),
|
||||||
box_{bar.orientation, 0},
|
box_{bar.orientation, 0},
|
||||||
manager_{nullptr},
|
manager_{nullptr},
|
||||||
|
workspace_manager_{nullptr},
|
||||||
seat_{nullptr} {
|
seat_{nullptr} {
|
||||||
box_.set_name("taskbar");
|
box_.set_name("taskbar");
|
||||||
if (!id.empty()) {
|
if (!id.empty()) {
|
||||||
@@ -623,6 +670,27 @@ Taskbar::Taskbar(const std::string& id, const waybar::Bar& bar, const Json::Valu
|
|||||||
}
|
}
|
||||||
|
|
||||||
Taskbar::~Taskbar() {
|
Taskbar::~Taskbar() {
|
||||||
|
for (auto& workspace : workspaces_) {
|
||||||
|
ext_workspace_handle_v1_destroy(workspace->handle);
|
||||||
|
}
|
||||||
|
workspaces_.clear();
|
||||||
|
for (auto* group : workspace_groups_) {
|
||||||
|
ext_workspace_group_handle_v1_destroy(group);
|
||||||
|
}
|
||||||
|
workspace_groups_.clear();
|
||||||
|
|
||||||
|
if (workspace_manager_) {
|
||||||
|
struct wl_display* display = Client::inst()->wl_display;
|
||||||
|
ext_workspace_manager_v1_stop(workspace_manager_);
|
||||||
|
wl_display_roundtrip(display);
|
||||||
|
|
||||||
|
if (workspace_manager_) {
|
||||||
|
spdlog::warn("Workspace manager destroyed before .finished event");
|
||||||
|
ext_workspace_manager_v1_destroy(workspace_manager_);
|
||||||
|
workspace_manager_ = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (manager_) {
|
if (manager_) {
|
||||||
struct wl_display* display = Client::inst()->wl_display;
|
struct wl_display* display = Client::inst()->wl_display;
|
||||||
/*
|
/*
|
||||||
@@ -639,6 +707,13 @@ Taskbar::~Taskbar() {
|
|||||||
manager_ = nullptr;
|
manager_ = nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (config_["bar-css-states"].asBool()) {
|
||||||
|
set_bar_css_class("toplevel-active", false);
|
||||||
|
set_bar_css_class("toplevel-maximized", false);
|
||||||
|
set_bar_css_class("toplevel-minimized", false);
|
||||||
|
set_bar_css_class("toplevel-fullscreen", false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Taskbar::update() {
|
void Taskbar::update() {
|
||||||
@@ -674,6 +749,80 @@ static const struct zwlr_foreign_toplevel_manager_v1_listener toplevel_manager_i
|
|||||||
.finished = tm_handle_finished,
|
.finished = tm_handle_finished,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static void workspace_handle_id(void*, struct ext_workspace_handle_v1*, const char*) {}
|
||||||
|
static void workspace_handle_name(void*, struct ext_workspace_handle_v1*, const char*) {}
|
||||||
|
static void workspace_handle_coordinates(void*, struct ext_workspace_handle_v1*, struct wl_array*) {
|
||||||
|
}
|
||||||
|
|
||||||
|
static void workspace_handle_state(void* data, struct ext_workspace_handle_v1*, uint32_t state) {
|
||||||
|
static_cast<Taskbar::WorkspaceState*>(data)->state = state;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void workspace_handle_capabilities(void*, struct ext_workspace_handle_v1*, uint32_t) {}
|
||||||
|
|
||||||
|
static void workspace_handle_removed(void* data, struct ext_workspace_handle_v1* handle) {
|
||||||
|
static_cast<Taskbar::WorkspaceState*>(data)->taskbar->handle_workspace_removed(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct ext_workspace_handle_v1_listener workspace_handle_impl = {
|
||||||
|
.id = workspace_handle_id,
|
||||||
|
.name = workspace_handle_name,
|
||||||
|
.coordinates = workspace_handle_coordinates,
|
||||||
|
.state = workspace_handle_state,
|
||||||
|
.capabilities = workspace_handle_capabilities,
|
||||||
|
.removed = workspace_handle_removed,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void workspace_group_handle_capabilities(void*, struct ext_workspace_group_handle_v1*,
|
||||||
|
uint32_t) {}
|
||||||
|
static void workspace_group_handle_output_enter(void*, struct ext_workspace_group_handle_v1*,
|
||||||
|
struct wl_output*) {}
|
||||||
|
static void workspace_group_handle_output_leave(void*, struct ext_workspace_group_handle_v1*,
|
||||||
|
struct wl_output*) {}
|
||||||
|
static void workspace_group_handle_workspace_enter(void*, struct ext_workspace_group_handle_v1*,
|
||||||
|
struct ext_workspace_handle_v1*) {}
|
||||||
|
static void workspace_group_handle_workspace_leave(void*, struct ext_workspace_group_handle_v1*,
|
||||||
|
struct ext_workspace_handle_v1*) {}
|
||||||
|
|
||||||
|
static void workspace_group_handle_removed(void* data,
|
||||||
|
struct ext_workspace_group_handle_v1* group) {
|
||||||
|
static_cast<Taskbar*>(data)->handle_workspace_group_removed(group);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct ext_workspace_group_handle_v1_listener workspace_group_impl = {
|
||||||
|
.capabilities = workspace_group_handle_capabilities,
|
||||||
|
.output_enter = workspace_group_handle_output_enter,
|
||||||
|
.output_leave = workspace_group_handle_output_leave,
|
||||||
|
.workspace_enter = workspace_group_handle_workspace_enter,
|
||||||
|
.workspace_leave = workspace_group_handle_workspace_leave,
|
||||||
|
.removed = workspace_group_handle_removed,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void workspace_manager_handle_group(void* data, struct ext_workspace_manager_v1*,
|
||||||
|
struct ext_workspace_group_handle_v1* group) {
|
||||||
|
static_cast<Taskbar*>(data)->handle_workspace_group_create(group);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void workspace_manager_handle_workspace(void* data, struct ext_workspace_manager_v1*,
|
||||||
|
struct ext_workspace_handle_v1* workspace) {
|
||||||
|
static_cast<Taskbar*>(data)->handle_workspace_create(workspace);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void workspace_manager_handle_done(void* data, struct ext_workspace_manager_v1*) {
|
||||||
|
static_cast<Taskbar*>(data)->handle_workspace_done();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void workspace_manager_handle_finished(void* data, struct ext_workspace_manager_v1*) {
|
||||||
|
static_cast<Taskbar*>(data)->handle_workspace_finished();
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct ext_workspace_manager_v1_listener workspace_manager_impl = {
|
||||||
|
.workspace_group = workspace_manager_handle_group,
|
||||||
|
.workspace = workspace_manager_handle_workspace,
|
||||||
|
.done = workspace_manager_handle_done,
|
||||||
|
.finished = workspace_manager_handle_finished,
|
||||||
|
};
|
||||||
|
|
||||||
void Taskbar::register_manager(struct wl_registry* registry, uint32_t name, uint32_t version) {
|
void Taskbar::register_manager(struct wl_registry* registry, uint32_t name, uint32_t version) {
|
||||||
if (manager_) {
|
if (manager_) {
|
||||||
spdlog::warn("Register foreign toplevel manager again although already existing!");
|
spdlog::warn("Register foreign toplevel manager again although already existing!");
|
||||||
@@ -698,6 +847,18 @@ void Taskbar::register_manager(struct wl_registry* registry, uint32_t name, uint
|
|||||||
spdlog::debug("Failed to register manager");
|
spdlog::debug("Failed to register manager");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Taskbar::register_workspace_manager(struct wl_registry* registry, uint32_t name,
|
||||||
|
uint32_t version) {
|
||||||
|
if (workspace_manager_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
version = std::min<uint32_t>(version, ext_workspace_manager_v1_interface.version);
|
||||||
|
workspace_manager_ = static_cast<struct ext_workspace_manager_v1*>(
|
||||||
|
wl_registry_bind(registry, name, &ext_workspace_manager_v1_interface, version));
|
||||||
|
ext_workspace_manager_v1_add_listener(workspace_manager_, &workspace_manager_impl, this);
|
||||||
|
}
|
||||||
|
|
||||||
void Taskbar::register_seat(struct wl_registry* registry, uint32_t name, uint32_t version) {
|
void Taskbar::register_seat(struct wl_registry* registry, uint32_t name, uint32_t version) {
|
||||||
if (seat_) {
|
if (seat_) {
|
||||||
spdlog::warn("Register seat again although already existing!");
|
spdlog::warn("Register seat again although already existing!");
|
||||||
@@ -717,8 +878,74 @@ void Taskbar::handle_finished() {
|
|||||||
manager_ = nullptr;
|
manager_ = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Taskbar::handle_workspace_group_create(struct ext_workspace_group_handle_v1* handle) {
|
||||||
|
ext_workspace_group_handle_v1_add_listener(handle, &workspace_group_impl, this);
|
||||||
|
workspace_groups_.push_back(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Taskbar::handle_workspace_group_removed(struct ext_workspace_group_handle_v1* handle) {
|
||||||
|
const auto group = std::find(workspace_groups_.begin(), workspace_groups_.end(), handle);
|
||||||
|
if (group != workspace_groups_.end()) {
|
||||||
|
ext_workspace_group_handle_v1_destroy(*group);
|
||||||
|
workspace_groups_.erase(group);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Taskbar::handle_workspace_create(struct ext_workspace_handle_v1* handle) {
|
||||||
|
auto workspace = std::make_unique<WorkspaceState>(WorkspaceState{this, handle});
|
||||||
|
ext_workspace_handle_v1_add_listener(handle, &workspace_handle_impl, workspace.get());
|
||||||
|
workspaces_.push_back(std::move(workspace));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Taskbar::handle_workspace_done() {
|
||||||
|
const auto active_workspace =
|
||||||
|
std::find_if(workspaces_.begin(), workspaces_.end(), [](const auto& workspace) {
|
||||||
|
return workspace->state & EXT_WORKSPACE_HANDLE_V1_STATE_ACTIVE;
|
||||||
|
});
|
||||||
|
current_workspace_ =
|
||||||
|
active_workspace == workspaces_.end() ? nullptr : (*active_workspace)->handle;
|
||||||
|
if (current_workspace_) {
|
||||||
|
const auto active_task =
|
||||||
|
std::find_if(tasks_.begin(), tasks_.end(), [](const auto& task) { return task->active(); });
|
||||||
|
if (active_task != tasks_.end()) {
|
||||||
|
(*active_task)->set_workspace(current_workspace_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
update_bar_css_classes();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Taskbar::handle_workspace_finished() { workspace_manager_ = nullptr; }
|
||||||
|
|
||||||
|
void Taskbar::handle_workspace_removed(struct ext_workspace_handle_v1* handle) {
|
||||||
|
if (current_workspace_ == handle) {
|
||||||
|
current_workspace_ = nullptr;
|
||||||
|
}
|
||||||
|
for (auto& task : tasks_) {
|
||||||
|
if (task->workspace() == handle) {
|
||||||
|
task->set_workspace(nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto workspace =
|
||||||
|
std::find_if(workspaces_.begin(), workspaces_.end(),
|
||||||
|
[handle](const auto& workspace) { return workspace->handle == handle; });
|
||||||
|
if (workspace != workspaces_.end()) {
|
||||||
|
ext_workspace_handle_v1_destroy((*workspace)->handle);
|
||||||
|
workspaces_.erase(workspace);
|
||||||
|
}
|
||||||
|
update_bar_css_classes();
|
||||||
|
}
|
||||||
|
|
||||||
void Taskbar::add_button(Gtk::Button& bt) {
|
void Taskbar::add_button(Gtk::Button& bt) {
|
||||||
box_.pack_start(bt, false, false);
|
/* 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_.get_style_context()->remove_class("empty");
|
box_.get_style_context()->remove_class("empty");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -741,6 +968,56 @@ void Taskbar::remove_task(uint32_t id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tasks_.erase(it);
|
tasks_.erase(it);
|
||||||
|
update_bar_css_classes();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Taskbar::assign_current_workspace(Task& task) {
|
||||||
|
if (current_workspace_) {
|
||||||
|
task.set_workspace(current_workspace_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Taskbar::update_bar_css_classes() {
|
||||||
|
if (!config_["bar-css-states"].asBool()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto active_task = std::find_if(tasks_.begin(), tasks_.end(), [](const TaskPtr& task) {
|
||||||
|
return task->visible() && task->active();
|
||||||
|
});
|
||||||
|
|
||||||
|
const bool has_active_task = active_task != tasks_.end();
|
||||||
|
const auto on_current_workspace = [this](const TaskPtr& task) {
|
||||||
|
if (!current_workspace_) {
|
||||||
|
return task->active();
|
||||||
|
}
|
||||||
|
return task->workspace() == current_workspace_;
|
||||||
|
};
|
||||||
|
const bool has_maximized_task =
|
||||||
|
std::any_of(tasks_.begin(), tasks_.end(), [&on_current_workspace](const TaskPtr& task) {
|
||||||
|
return task->visible() && !task->minimized() && on_current_workspace(task) &&
|
||||||
|
task->maximized();
|
||||||
|
});
|
||||||
|
const bool has_fullscreen_task =
|
||||||
|
std::any_of(tasks_.begin(), tasks_.end(), [&on_current_workspace](const TaskPtr& task) {
|
||||||
|
return task->visible() && !task->minimized() && on_current_workspace(task) &&
|
||||||
|
task->fullscreen();
|
||||||
|
});
|
||||||
|
set_bar_css_class("toplevel-active", has_active_task);
|
||||||
|
set_bar_css_class("toplevel-maximized", has_maximized_task);
|
||||||
|
set_bar_css_class("toplevel-minimized", has_active_task && (*active_task)->minimized());
|
||||||
|
set_bar_css_class("toplevel-fullscreen", has_fullscreen_task);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Taskbar::set_bar_css_class(const std::string& class_name, bool enabled) {
|
||||||
|
const auto style = bar_.window.get_style_context();
|
||||||
|
if (enabled && !style->has_class(class_name)) {
|
||||||
|
spdlog::trace("Adding bar class: {}", class_name);
|
||||||
|
style->add_class(class_name);
|
||||||
|
} else if (!enabled && style->has_class(class_name)) {
|
||||||
|
spdlog::trace("Removing bar class: {}", class_name);
|
||||||
|
style->remove_class(class_name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Taskbar::show_output(struct wl_output* output) const {
|
bool Taskbar::show_output(struct wl_output* output) const {
|
||||||
|
|||||||
@@ -99,15 +99,17 @@ void AudioBackend::contextStateCb(pa_context* c, void* data) {
|
|||||||
nullptr, nullptr);
|
nullptr, nullptr);
|
||||||
break;
|
break;
|
||||||
case PA_CONTEXT_FAILED:
|
case PA_CONTEXT_FAILED:
|
||||||
// When pulseaudio server restarts, the connection is "failed". Try to reconnect.
|
if (pa_context_errno(c) != PA_ERR_CONNECTIONREFUSED) {
|
||||||
// pa_threaded_mainloop_lock is already acquired in callback threads.
|
// When pulseaudio server restarts, the connection is "failed". Try to reconnect.
|
||||||
// So there is no need to lock it again.
|
// pa_threaded_mainloop_lock is already acquired in callback threads.
|
||||||
if (backend->context_ != nullptr) {
|
// So there is no need to lock it again.
|
||||||
pa_context_disconnect(backend->context_);
|
if (backend->context_ != nullptr) {
|
||||||
pa_context_unref(backend->context_);
|
pa_context_disconnect(backend->context_);
|
||||||
backend->context_ = nullptr;
|
pa_context_unref(backend->context_);
|
||||||
|
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,60 @@
|
|||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <regex>
|
||||||
|
#include <sstream>
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
struct TransformResult {
|
||||||
|
std::string css;
|
||||||
|
bool was_transformed;
|
||||||
|
};
|
||||||
|
|
||||||
|
TransformResult transform_8bit_to_hex(const std::string& file_path) {
|
||||||
|
std::ifstream f(file_path, std::ios::in | std::ios::binary);
|
||||||
|
const auto size = fs::file_size(file_path);
|
||||||
|
std::string result(size, '\0');
|
||||||
|
if (!f.is_open() || !f.good()) {
|
||||||
|
throw std::runtime_error("Cannot open file: " + file_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (size == 0) {
|
||||||
|
return {.css = result, .was_transformed = false};
|
||||||
|
}
|
||||||
|
|
||||||
|
f.read(result.data(), size);
|
||||||
|
|
||||||
|
static std::regex pattern(
|
||||||
|
R"(\#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2}))");
|
||||||
|
std::string final_output;
|
||||||
|
|
||||||
|
auto it = std::sregex_iterator(result.begin(), result.end(), pattern);
|
||||||
|
auto eof = std::sregex_iterator();
|
||||||
|
|
||||||
|
if (it == eof) {
|
||||||
|
return {.css = result, .was_transformed = false};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::smatch match;
|
||||||
|
while (it != eof) {
|
||||||
|
match = *it;
|
||||||
|
|
||||||
|
final_output += match.prefix().str();
|
||||||
|
|
||||||
|
int r = stoi(match[1].str(), nullptr, 16);
|
||||||
|
int g = stoi(match[2].str(), nullptr, 16);
|
||||||
|
int b = stoi(match[3].str(), nullptr, 16);
|
||||||
|
double a = (stoi(match[4].str(), nullptr, 16) / 255.0);
|
||||||
|
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << "rgba(" << r << "," << g << "," << b << "," << std::fixed << std::setprecision(2) << a
|
||||||
|
<< ")";
|
||||||
|
final_output += ss.str();
|
||||||
|
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
|
||||||
|
final_output += match.suffix().str();
|
||||||
|
|
||||||
|
return {.css = final_output, .was_transformed = true};
|
||||||
|
}
|
||||||
@@ -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