Merge remote-tracking branch 'origin/master' into refactor/generic-tooltip
# Conflicts: # src/modules/wireplumber.cpp
This commit is contained in:
@@ -0,0 +1,57 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <glibmm/markup.h>
|
||||||
|
#include <gtkmm/label.h>
|
||||||
|
#include <json/json.h>
|
||||||
|
|
||||||
|
#include <deque>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "AModule.hpp"
|
||||||
|
|
||||||
|
namespace waybar {
|
||||||
|
|
||||||
|
enum class GraphType { LINE, BAR, GAUGE };
|
||||||
|
|
||||||
|
class AGraph : public AModule {
|
||||||
|
public:
|
||||||
|
AGraph(const Json::Value&, const std::string&, const std::string&, uint16_t interval = 0,
|
||||||
|
bool enable_click = false, bool enable_scroll = false);
|
||||||
|
virtual ~AGraph() = default;
|
||||||
|
auto update() -> void override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Gtk::DrawingArea graph_;
|
||||||
|
std::deque<int> values_;
|
||||||
|
uint16_t datapoints_ = 20;
|
||||||
|
GraphType graph_type_ = GraphType::LINE;
|
||||||
|
|
||||||
|
void addValue(const int n);
|
||||||
|
|
||||||
|
const std::chrono::seconds interval_;
|
||||||
|
|
||||||
|
bool onDraw(const Cairo::RefPtr<Cairo::Context>& cr);
|
||||||
|
|
||||||
|
std::map<std::string, GtkMenuItem*> submenus_;
|
||||||
|
std::map<std::string, std::string> menuActionsMap_;
|
||||||
|
static void handleGtkMenuEvent(GtkMenuItem* menuitem, gpointer data);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void drawFilledArea(const Cairo::RefPtr<Cairo::Context>& cr,
|
||||||
|
const std::vector<std::pair<double, double>>& points, double height,
|
||||||
|
const Gdk::RGBA& bg_color);
|
||||||
|
|
||||||
|
void drawLine(const Cairo::RefPtr<Cairo::Context>& cr,
|
||||||
|
const std::vector<std::pair<double, double>>& points, const Gdk::RGBA& fg_color);
|
||||||
|
|
||||||
|
void drawPath(const Cairo::RefPtr<Cairo::Context>& cr,
|
||||||
|
const std::vector<std::pair<double, double>>& points);
|
||||||
|
|
||||||
|
void drawBars(const Cairo::RefPtr<Cairo::Context>& cr, double width, double height,
|
||||||
|
int current_value, const Gdk::RGBA& fg_color);
|
||||||
|
|
||||||
|
void drawGauge(const Cairo::RefPtr<Cairo::Context>& cr, double width, double height,
|
||||||
|
int current_value, const Gdk::RGBA& fg_color);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace waybar
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
struct zwp_idle_inhibitor_v1;
|
struct zwp_idle_inhibitor_v1;
|
||||||
struct zwp_idle_inhibit_manager_v1;
|
struct zwp_idle_inhibit_manager_v1;
|
||||||
|
struct ext_idle_notifier_v1;
|
||||||
|
|
||||||
namespace waybar {
|
namespace waybar {
|
||||||
|
|
||||||
@@ -27,6 +28,7 @@ class Client {
|
|||||||
struct wl_registry* registry = nullptr;
|
struct wl_registry* registry = nullptr;
|
||||||
struct zxdg_output_manager_v1* xdg_output_manager = nullptr;
|
struct zxdg_output_manager_v1* xdg_output_manager = nullptr;
|
||||||
struct zwp_idle_inhibit_manager_v1* idle_inhibit_manager = nullptr;
|
struct zwp_idle_inhibit_manager_v1* idle_inhibit_manager = nullptr;
|
||||||
|
struct ext_idle_notifier_v1* idle_notifier = nullptr;
|
||||||
std::vector<std::unique_ptr<Bar>> bars;
|
std::vector<std::unique_ptr<Bar>> bars;
|
||||||
Config config;
|
Config config;
|
||||||
std::string bar_id;
|
std::string bar_id;
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ class Bluetooth : public ALabel {
|
|||||||
bool services_resolved;
|
bool services_resolved;
|
||||||
// NOTE: experimental feature in bluez
|
// NOTE: experimental feature in bluez
|
||||||
std::optional<unsigned char> battery_percentage;
|
std::optional<unsigned char> battery_percentage;
|
||||||
|
std::optional<unsigned char> battery_percentage_peripheral;
|
||||||
};
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@@ -59,6 +60,12 @@ class Bluetooth : public ALabel {
|
|||||||
gpointer) -> void;
|
gpointer) -> void;
|
||||||
|
|
||||||
auto getDeviceBatteryPercentage(GDBusObject*) -> std::optional<unsigned char>;
|
auto getDeviceBatteryPercentage(GDBusObject*) -> std::optional<unsigned char>;
|
||||||
|
auto getDeviceGattBatteryLevels(GDBusObject*, std::optional<unsigned char>&,
|
||||||
|
std::optional<unsigned char>&) -> void;
|
||||||
|
static auto processBatteryServiceCharacteristics(GList*, const std::string&, const std::string&,
|
||||||
|
const std::string&,
|
||||||
|
std::optional<unsigned char>&,
|
||||||
|
std::optional<unsigned char>&) -> void;
|
||||||
auto getDeviceProperties(GDBusObject*, DeviceInfo&) -> bool;
|
auto getDeviceProperties(GDBusObject*, DeviceInfo&) -> bool;
|
||||||
auto getControllerProperties(GDBusObject*, ControllerInfo&) -> bool;
|
auto getControllerProperties(GDBusObject*, ControllerInfo&) -> bool;
|
||||||
|
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ class Clock final : public ALabel {
|
|||||||
void cldShift_reset();
|
void cldShift_reset();
|
||||||
void tz_up();
|
void tz_up();
|
||||||
void tz_down();
|
void tz_down();
|
||||||
|
void action_exec(const std::string& action);
|
||||||
// Module Action Map
|
// Module Action Map
|
||||||
static inline std::map<const std::string, void (waybar::modules::Clock::* const)()> actionMap_{
|
static inline std::map<const std::string, void (waybar::modules::Clock::* const)()> actionMap_{
|
||||||
{"mode", &waybar::modules::Clock::cldModeSwitch},
|
{"mode", &waybar::modules::Clock::cldModeSwitch},
|
||||||
@@ -88,6 +89,9 @@ class Clock final : public ALabel {
|
|||||||
{"shift_reset", &waybar::modules::Clock::cldShift_reset},
|
{"shift_reset", &waybar::modules::Clock::cldShift_reset},
|
||||||
{"tz_up", &waybar::modules::Clock::tz_up},
|
{"tz_up", &waybar::modules::Clock::tz_up},
|
||||||
{"tz_down", &waybar::modules::Clock::tz_down}};
|
{"tz_down", &waybar::modules::Clock::tz_down}};
|
||||||
|
static inline std::map<const std::string,
|
||||||
|
void (waybar::modules::Clock::* const)(const std::string& action)>
|
||||||
|
actionWithArgsMap_{{"exec", &waybar::modules::Clock::action_exec}};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace waybar::modules
|
} // namespace waybar::modules
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <fmt/format.h>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <fstream>
|
||||||
|
#include <numeric>
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "AGraph.hpp"
|
||||||
|
#include "util/sleeper_thread.hpp"
|
||||||
|
|
||||||
|
namespace waybar::modules {
|
||||||
|
|
||||||
|
class CpuGraph : public AGraph {
|
||||||
|
public:
|
||||||
|
CpuGraph(const std::string&, const Json::Value&);
|
||||||
|
virtual ~CpuGraph() = default;
|
||||||
|
auto update() -> void override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static constexpr const char* MODERATE_CLASS = "cpu-moderate";
|
||||||
|
static constexpr const char* HIGH_CLASS = "cpu-high";
|
||||||
|
static constexpr const char* INTENSIVE_CLASS = "cpu-intensive";
|
||||||
|
|
||||||
|
std::vector<std::tuple<size_t, size_t>> prev_times_;
|
||||||
|
util::SleeperThread thread_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace waybar::modules
|
||||||
@@ -5,14 +5,14 @@
|
|||||||
#include <csignal>
|
#include <csignal>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "ALabel.hpp"
|
#include "AIconLabel.hpp"
|
||||||
#include "util/command.hpp"
|
#include "util/command.hpp"
|
||||||
#include "util/json.hpp"
|
#include "util/json.hpp"
|
||||||
#include "util/sleeper_thread.hpp"
|
#include "util/sleeper_thread.hpp"
|
||||||
|
|
||||||
namespace waybar::modules {
|
namespace waybar::modules {
|
||||||
|
|
||||||
class Custom : public ALabel {
|
class Custom : public AIconLabel {
|
||||||
public:
|
public:
|
||||||
Custom(const std::string&, const std::string&, const Json::Value&, const std::string&);
|
Custom(const std::string&, const std::string&, const Json::Value&, const std::string&);
|
||||||
virtual ~Custom();
|
virtual ~Custom();
|
||||||
@@ -36,6 +36,9 @@ class Custom : public ALabel {
|
|||||||
std::string alt_;
|
std::string alt_;
|
||||||
std::string tooltip_;
|
std::string tooltip_;
|
||||||
std::string last_tooltip_markup_;
|
std::string last_tooltip_markup_;
|
||||||
|
std::string image_path_;
|
||||||
|
std::string image_name_;
|
||||||
|
unsigned app_icon_size_{24};
|
||||||
const bool tooltip_format_enabled_;
|
const bool tooltip_format_enabled_;
|
||||||
std::vector<std::string> class_;
|
std::vector<std::string> class_;
|
||||||
int percentage_;
|
int percentage_;
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <fmt/format.h>
|
||||||
|
|
||||||
|
#include <csignal>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "AGraph.hpp"
|
||||||
|
#include "util/command.hpp"
|
||||||
|
#include "util/json.hpp"
|
||||||
|
#include "util/sleeper_thread.hpp"
|
||||||
|
|
||||||
|
namespace waybar::modules {
|
||||||
|
|
||||||
|
class CustomGraph : public AGraph {
|
||||||
|
public:
|
||||||
|
CustomGraph(const std::string&, const std::string&, const Json::Value&, const std::string&);
|
||||||
|
virtual ~CustomGraph();
|
||||||
|
auto update() -> void override;
|
||||||
|
void refresh(int /*signal*/) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void delayWorker();
|
||||||
|
void continuousWorker();
|
||||||
|
void waitingWorker();
|
||||||
|
void parseOutputRaw();
|
||||||
|
void parseOutputJson();
|
||||||
|
void handleEvent();
|
||||||
|
bool handleScroll(GdkEventScroll* e) override;
|
||||||
|
bool handleToggle(GdkEventButton* const& e) override;
|
||||||
|
|
||||||
|
const std::string name_;
|
||||||
|
const std::string output_name_;
|
||||||
|
std::string text_;
|
||||||
|
std::string id_;
|
||||||
|
std::string alt_;
|
||||||
|
std::string tooltip_;
|
||||||
|
const bool tooltip_format_enabled_;
|
||||||
|
std::vector<std::string> class_;
|
||||||
|
int percentage_;
|
||||||
|
FILE* fp_;
|
||||||
|
int pid_;
|
||||||
|
util::command::res output_;
|
||||||
|
util::JsonParser parser_;
|
||||||
|
|
||||||
|
util::SleeperThread thread_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace waybar::modules
|
||||||
@@ -64,6 +64,8 @@ class Workspaces : public AModule, public EventHandler {
|
|||||||
|
|
||||||
std::string getRewrite(const std::string& window_class, const std::string& window_title);
|
std::string getRewrite(const std::string& window_class, const std::string& window_title);
|
||||||
std::string& getWindowSeparator() { return m_formatWindowSeparator; }
|
std::string& getWindowSeparator() { return m_formatWindowSeparator; }
|
||||||
|
auto windowRewriteGroupThreshold() const -> int { return m_windowRewriteGroupThreshold; }
|
||||||
|
auto const& getWindowRewriteGroupFormat() const { return m_windowRewriteGroupFormat; }
|
||||||
bool isWorkspaceIgnored(std::string const& workspace_name);
|
bool isWorkspaceIgnored(std::string const& workspace_name);
|
||||||
|
|
||||||
bool windowRewriteConfigUsesTitle() const { return m_anyWindowRewriteRuleUsesTitle; }
|
bool windowRewriteConfigUsesTitle() const { return m_anyWindowRewriteRuleUsesTitle; }
|
||||||
@@ -177,6 +179,8 @@ class Workspaces : public AModule, public EventHandler {
|
|||||||
util::RegexCollection m_windowRewriteRules;
|
util::RegexCollection m_windowRewriteRules;
|
||||||
bool m_anyWindowRewriteRuleUsesTitle = false;
|
bool m_anyWindowRewriteRuleUsesTitle = false;
|
||||||
std::string m_formatWindowSeparator;
|
std::string m_formatWindowSeparator;
|
||||||
|
int m_windowRewriteGroupThreshold = 0;
|
||||||
|
std::string m_windowRewriteGroupFormat = "{icon}×{count}";
|
||||||
|
|
||||||
bool m_withIcon;
|
bool m_withIcon;
|
||||||
uint64_t m_monitorId;
|
uint64_t m_monitorId;
|
||||||
|
|||||||
@@ -6,25 +6,35 @@
|
|||||||
#include "bar.hpp"
|
#include "bar.hpp"
|
||||||
#include "client.hpp"
|
#include "client.hpp"
|
||||||
|
|
||||||
|
struct ext_idle_notification_v1;
|
||||||
|
|
||||||
namespace waybar::modules {
|
namespace waybar::modules {
|
||||||
|
|
||||||
class IdleInhibitor : public ALabel {
|
class IdleInhibitor : public ALabel {
|
||||||
sigc::connection timeout_;
|
sigc::connection timeout_;
|
||||||
|
ext_idle_notification_v1* idle_notification_;
|
||||||
|
uint32_t idle_timeout_ms_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
IdleInhibitor(const std::string&, const waybar::Bar&, const Json::Value&);
|
IdleInhibitor(const std::string&, const waybar::Bar&, const Json::Value&);
|
||||||
virtual ~IdleInhibitor();
|
virtual ~IdleInhibitor();
|
||||||
auto update() -> void override;
|
auto update() -> void override;
|
||||||
|
auto refresh(int) -> void override;
|
||||||
static std::list<waybar::AModule*> modules;
|
static std::list<waybar::AModule*> modules;
|
||||||
static bool status;
|
static bool status;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool handleToggle(GdkEventButton* const& e) override;
|
bool handleToggle(GdkEventButton* const& e) override;
|
||||||
void toggleStatus();
|
void toggleStatus();
|
||||||
|
void setupIdleNotification();
|
||||||
|
void teardownIdleNotification();
|
||||||
|
static void handleIdled(void* data, ext_idle_notification_v1* notification);
|
||||||
|
static void handleResumed(void* data, ext_idle_notification_v1* notification);
|
||||||
|
|
||||||
const Bar& bar_;
|
const Bar& bar_;
|
||||||
struct zwp_idle_inhibitor_v1* idle_inhibitor_;
|
struct zwp_idle_inhibitor_v1* idle_inhibitor_;
|
||||||
int pid_;
|
int pid_;
|
||||||
|
bool wait_for_activity_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace waybar::modules
|
} // namespace waybar::modules
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ class MPD : public ALabel {
|
|||||||
|
|
||||||
unsigned timeout_;
|
unsigned timeout_;
|
||||||
|
|
||||||
|
unsigned playing_interval_;
|
||||||
|
|
||||||
detail::unique_connection connection_;
|
detail::unique_connection connection_;
|
||||||
|
|
||||||
detail::unique_status status_;
|
detail::unique_status status_;
|
||||||
@@ -63,6 +65,7 @@ class MPD : public ALabel {
|
|||||||
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; }
|
||||||
inline bool paused() const { return connection_ && state_ == MPD_STATE_PAUSE; }
|
inline bool paused() const { return connection_ && state_ == MPD_STATE_PAUSE; }
|
||||||
|
inline unsigned playing_interval() const { return playing_interval_; }
|
||||||
};
|
};
|
||||||
|
|
||||||
#if !defined(MPD_NOINLINE)
|
#if !defined(MPD_NOINLINE)
|
||||||
|
|||||||
@@ -198,6 +198,7 @@ class Context {
|
|||||||
bool is_paused() const;
|
bool is_paused() const;
|
||||||
bool is_stopped() const;
|
bool is_stopped() const;
|
||||||
constexpr std::size_t interval() const;
|
constexpr std::size_t interval() const;
|
||||||
|
unsigned playing_interval() const;
|
||||||
void tryConnect() const;
|
void tryConnect() const;
|
||||||
void checkErrors(mpd_connection*) const;
|
void checkErrors(mpd_connection*) const;
|
||||||
void do_update();
|
void do_update();
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ 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_ / 1s; }
|
constexpr inline std::size_t Context::interval() const { return mpd_module_->interval_ / 1s; }
|
||||||
|
inline unsigned Context::playing_interval() const { return mpd_module_->playing_interval(); }
|
||||||
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_; }
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ class Workspaces : public AModule, public EventHandler {
|
|||||||
void sortWorkspaces(std::vector<Json::Value>& workspaces) const;
|
void sortWorkspaces(std::vector<Json::Value>& workspaces) const;
|
||||||
Gtk::Button& addButton(const Json::Value& ws);
|
Gtk::Button& addButton(const Json::Value& ws);
|
||||||
std::string getIcon(const std::string& value, const Json::Value& ws);
|
std::string getIcon(const std::string& value, const Json::Value& ws);
|
||||||
|
bool handleScroll(GdkEventScroll* /*unused*/) override;
|
||||||
|
|
||||||
const Bar& bar_;
|
const Bar& bar_;
|
||||||
Gtk::Box box_;
|
Gtk::Box box_;
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ class Wireplumber : public ALabel {
|
|||||||
static void onDefaultNodesApiChanged(waybar::modules::Wireplumber* self);
|
static void onDefaultNodesApiChanged(waybar::modules::Wireplumber* self);
|
||||||
|
|
||||||
bool handleScroll(GdkEventScroll* e) override;
|
bool handleScroll(GdkEventScroll* e) override;
|
||||||
|
std::vector<std::string> getWPIcon();
|
||||||
|
|
||||||
static std::list<waybar::modules::Wireplumber*> modules;
|
static std::list<waybar::modules::Wireplumber*> modules;
|
||||||
|
|
||||||
@@ -54,6 +55,7 @@ class Wireplumber : public ALabel {
|
|||||||
bool source_muted_;
|
bool source_muted_;
|
||||||
double source_volume_;
|
double source_volume_;
|
||||||
gchar* default_source_name_;
|
gchar* default_source_name_;
|
||||||
|
std::string form_factor_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace waybar::modules
|
} // namespace waybar::modules
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ class AudioBackend {
|
|||||||
std::string default_source_name_;
|
std::string default_source_name_;
|
||||||
|
|
||||||
std::vector<std::string> ignored_sinks_;
|
std::vector<std::string> ignored_sinks_;
|
||||||
|
std::map<std::string, std::string> sink_mapping_;
|
||||||
|
|
||||||
std::function<void()> on_updated_cb_ = NOOP;
|
std::function<void()> on_updated_cb_ = NOOP;
|
||||||
|
|
||||||
@@ -80,6 +81,7 @@ class AudioBackend {
|
|||||||
PulseaudioTarget target = PulseaudioTarget::Sink);
|
PulseaudioTarget target = PulseaudioTarget::Sink);
|
||||||
|
|
||||||
void setIgnoredSinks(const Json::Value& config);
|
void setIgnoredSinks(const Json::Value& config);
|
||||||
|
void setSinkMapping(const Json::Value& config);
|
||||||
|
|
||||||
std::string getSinkPortName() const { return port_name_; }
|
std::string getSinkPortName() const { return port_name_; }
|
||||||
std::string getFormFactor() const { return form_factor_; }
|
std::string getFormFactor() const { return form_factor_; }
|
||||||
|
|||||||
@@ -178,6 +178,9 @@ At the time of writing, the experimental features of BlueZ need to be turned on,
|
|||||||
|
|
||||||
*{device_battery_percentage}*: Battery percentage of the displayed device if available. Use only in the config options defined below.
|
*{device_battery_percentage}*: Battery percentage of the displayed device if available. Use only in the config options defined below.
|
||||||
|
|
||||||
|
*{device_battery_percentage_peripheral}*: Battery percentage of the peripheral half of a split keyboard (e.g., ZMK keyboards with separate central and peripheral batteries). ++
|
||||||
|
This is read from GATT Battery Service characteristics that have a User Description descriptor. Use only in the config options defined below.
|
||||||
|
|
||||||
## CONFIGURATION
|
## CONFIGURATION
|
||||||
|
|
||||||
*format-connected-battery*: ++
|
*format-connected-battery*: ++
|
||||||
@@ -220,6 +223,17 @@ At the time of writing, the experimental features of BlueZ need to be turned on,
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Split keyboard with separate central/peripheral batteries (e.g., ZMK):
|
||||||
|
|
||||||
|
```
|
||||||
|
"bluetooth": {
|
||||||
|
"format-device-preference": [ "Keyball44" ],
|
||||||
|
"format": "",
|
||||||
|
"format-connected-battery": " {device_battery_percentage}%|{device_battery_percentage_peripheral}%",
|
||||||
|
"tooltip-format-connected": "{device_alias}\\nCentral: {device_battery_percentage}%\\nPeripheral: {device_battery_percentage_peripheral}%"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
# STYLE
|
# STYLE
|
||||||
|
|
||||||
- *#bluetooth*
|
- *#bluetooth*
|
||||||
|
|||||||
@@ -185,6 +185,8 @@ View all valid format options in *strftime(3)* or have a look https://en.cpprefe
|
|||||||
:[ Switch to the next calendar month/year
|
:[ Switch to the next calendar month/year
|
||||||
|[ *shift_down*
|
|[ *shift_down*
|
||||||
:[ Switch to the previous calendar month/year
|
:[ Switch to the previous calendar month/year
|
||||||
|
|[ *exec <cmd>*
|
||||||
|
:[ Execute the specified command
|
||||||
|
|
||||||
# FORMAT REPLACEMENTS
|
# FORMAT REPLACEMENTS
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
waybar-cpu-graph(5)
|
||||||
|
|
||||||
|
# NAME
|
||||||
|
|
||||||
|
waybar - cpu graph module
|
||||||
|
|
||||||
|
# DESCRIPTION
|
||||||
|
|
||||||
|
The *cpu graph* module displays a line graph with the CPU utilization.
|
||||||
|
|
||||||
|
# CONFIGURATION
|
||||||
|
|
||||||
|
*interval*: ++
|
||||||
|
typeof: integer ++
|
||||||
|
default: 10 ++
|
||||||
|
The interval in which the information gets polled.
|
||||||
|
|
||||||
|
*width*: ++
|
||||||
|
typeof: integer ++
|
||||||
|
The length in pixels the module should display.
|
||||||
|
|
||||||
|
*datapoints*: ++
|
||||||
|
typeof: integer ++
|
||||||
|
How many data points to show.
|
||||||
|
|
||||||
|
*on-click*: ++
|
||||||
|
typeof: string ++
|
||||||
|
Command to execute when clicked on the module.
|
||||||
|
|
||||||
|
*on-click-middle*: ++
|
||||||
|
typeof: string ++
|
||||||
|
Command to execute when middle-clicked on the module using mousewheel.
|
||||||
|
|
||||||
|
*on-click-right*: ++
|
||||||
|
typeof: string ++
|
||||||
|
Command to execute when you right-click on the module.
|
||||||
|
|
||||||
|
*on-update*: ++
|
||||||
|
typeof: string ++
|
||||||
|
Command to execute when the module is updated.
|
||||||
|
|
||||||
|
*on-scroll-up*: ++
|
||||||
|
typeof: string ++
|
||||||
|
Command to execute when scrolling up on the module.
|
||||||
|
|
||||||
|
*on-scroll-down*: ++
|
||||||
|
typeof: string ++
|
||||||
|
Command to execute when scrolling down on the module.
|
||||||
|
|
||||||
|
*smooth-scrolling-threshold*: ++
|
||||||
|
typeof: double ++
|
||||||
|
Threshold to be used when scrolling.
|
||||||
|
|
||||||
|
*tooltip*: ++
|
||||||
|
typeof: bool ++
|
||||||
|
default: true ++
|
||||||
|
Option to disable tooltip on hover.
|
||||||
|
|
||||||
|
*expand*: ++
|
||||||
|
typeof: bool ++
|
||||||
|
default: false ++
|
||||||
|
Enables this module to consume all left over space dynamically.
|
||||||
|
|
||||||
|
# EXAMPLES
|
||||||
|
|
||||||
|
Basic configuration:
|
||||||
|
|
||||||
|
```
|
||||||
|
"cpu_graph": {
|
||||||
|
"interval": 2,
|
||||||
|
"width": 10
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# STYLE
|
||||||
|
|
||||||
|
- *#cpu_graph*
|
||||||
|
- *.cpu-intensive*
|
||||||
|
- *.cpu-high*
|
||||||
|
- *.cpu-moderate*
|
||||||
@@ -0,0 +1,189 @@
|
|||||||
|
waybar-custom-graph(5)
|
||||||
|
# NAME
|
||||||
|
|
||||||
|
waybar - custom graph module
|
||||||
|
|
||||||
|
# DESCRIPTION
|
||||||
|
|
||||||
|
The *custom-graph* module displays a graph with the percentage output of a script.
|
||||||
|
|
||||||
|
# CONFIGURATION
|
||||||
|
|
||||||
|
Addressed by *custom-graph/<name>*
|
||||||
|
|
||||||
|
*exec*: ++
|
||||||
|
typeof: string ++
|
||||||
|
The path to the script, which should be executed.
|
||||||
|
|
||||||
|
*exec-if*: ++
|
||||||
|
typeof: string ++
|
||||||
|
The path to a script, which determines if the script in *exec* should be executed. ++
|
||||||
|
*exec* will be executed if the exit code of *exec-if* equals 0.
|
||||||
|
|
||||||
|
*exec-on-event*: ++
|
||||||
|
typeof: bool ++
|
||||||
|
default: true ++
|
||||||
|
If an event command is set (e.g. *on-click* or *on-scroll-up*) then re-execute the script after executing the event command.
|
||||||
|
|
||||||
|
*return-type*: ++
|
||||||
|
typeof: string ++
|
||||||
|
See *return-type*
|
||||||
|
|
||||||
|
*interval*: ++
|
||||||
|
typeof: integer or float ++
|
||||||
|
The interval (in seconds) in which the information gets polled. ++
|
||||||
|
Minimum value is 0.001 (1ms). Values smaller than 1ms will be set to 1ms. ++
|
||||||
|
Use *once* if you want to execute the module only on startup. ++
|
||||||
|
You can update it manually with a signal. If no *interval* or *signal* is defined, it is assumed that the out script loops itself. ++
|
||||||
|
If a *signal* is defined then the script will run once on startup and will only update with a signal.
|
||||||
|
|
||||||
|
*restart-interval*: ++
|
||||||
|
typeof: integer or float ++
|
||||||
|
The restart interval (in seconds). ++
|
||||||
|
Minimum value is 0.001 (1ms). Values smaller than 1ms will be set to 1ms. ++
|
||||||
|
Can't be used with the *interval* option, so only with continuous scripts. ++
|
||||||
|
Once the script exits, it'll be re-executed after the *restart-interval*.
|
||||||
|
|
||||||
|
*signal*: ++
|
||||||
|
typeof: integer ++
|
||||||
|
The signal number used to update the module. ++
|
||||||
|
The number is valid between 1 and N, where *SIGRTMIN+N* = *SIGRTMAX*. ++
|
||||||
|
If no interval is defined then a signal will be the only way to update the module.
|
||||||
|
|
||||||
|
*format*: ++
|
||||||
|
typeof: string ++
|
||||||
|
default: {text} ++
|
||||||
|
The format, how information should be displayed. On {text} data gets inserted.
|
||||||
|
|
||||||
|
*format-icons*: ++
|
||||||
|
typeof: array ++
|
||||||
|
Based on the set percentage, the corresponding icon gets selected. The order is *low* to *high*.
|
||||||
|
|
||||||
|
*rotate*: ++
|
||||||
|
typeof: integer ++
|
||||||
|
Positive value to rotate the text label (in 90 degree increments).
|
||||||
|
|
||||||
|
*on-click*: ++
|
||||||
|
typeof: string ++
|
||||||
|
Command to execute when clicked on the module.
|
||||||
|
|
||||||
|
*on-click-middle*: ++
|
||||||
|
typeof: string ++
|
||||||
|
Command to execute when middle-clicked on the module using mousewheel.
|
||||||
|
|
||||||
|
*on-click-right*: ++
|
||||||
|
typeof: string ++
|
||||||
|
Command to execute when you right-click on the module.
|
||||||
|
|
||||||
|
*on-update*: ++
|
||||||
|
typeof: string ++
|
||||||
|
Command to execute when the module is updated.
|
||||||
|
|
||||||
|
*on-scroll-up*: ++
|
||||||
|
typeof: string ++
|
||||||
|
Command to execute when scrolling up on the module.
|
||||||
|
|
||||||
|
*on-scroll-down*: ++
|
||||||
|
typeof: string ++
|
||||||
|
Command to execute when scrolling down on the module.
|
||||||
|
|
||||||
|
*smooth-scrolling-threshold*: ++
|
||||||
|
typeof: double ++
|
||||||
|
Threshold to be used when scrolling.
|
||||||
|
|
||||||
|
*tooltip*: ++
|
||||||
|
typeof: bool ++
|
||||||
|
default: true ++
|
||||||
|
Option to disable tooltip on hover.
|
||||||
|
|
||||||
|
*tooltip-format*: ++
|
||||||
|
typeof: string ++
|
||||||
|
The tooltip format. If specified, overrides any tooltip output from the script in *exec*. ++
|
||||||
|
Uses the same format replacements as *format*.
|
||||||
|
|
||||||
|
*escape*: ++
|
||||||
|
typeof: bool ++
|
||||||
|
default: false ++
|
||||||
|
Option to enable escaping of script output.
|
||||||
|
|
||||||
|
*menu*: ++
|
||||||
|
typeof: string ++
|
||||||
|
Action that popups the menu.
|
||||||
|
|
||||||
|
*menu-file*: ++
|
||||||
|
typeof: string ++
|
||||||
|
Location of the menu descriptor file. There need to be an element of type
|
||||||
|
GtkMenu with id *menu*
|
||||||
|
|
||||||
|
*menu-actions*: ++
|
||||||
|
typeof: array ++
|
||||||
|
The actions corresponding to the buttons of the menu.
|
||||||
|
|
||||||
|
*expand*: ++
|
||||||
|
typeof: bool ++
|
||||||
|
default: false ++
|
||||||
|
Enables this module to consume all left over space dynamically.
|
||||||
|
|
||||||
|
# RETURN-TYPE
|
||||||
|
|
||||||
|
When *return-type* is set to *json*, Waybar expects the *exec*-script to output its data in JSON format.
|
||||||
|
This should look like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
{"text": "$text", "tooltip": "$tooltip", "class": "$class", "percentage": $percentage }
|
||||||
|
```
|
||||||
|
|
||||||
|
The *class* parameter also accepts an array of strings.
|
||||||
|
|
||||||
|
If nothing or an invalid option is specified, Waybar expects i3blocks style output. Values are *newline* separated.
|
||||||
|
This should look like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
$text\\n$tooltip\\n$class*
|
||||||
|
```
|
||||||
|
|
||||||
|
*class* is a CSS class, to apply different styles in *style.css*
|
||||||
|
|
||||||
|
# FORMAT REPLACEMENTS
|
||||||
|
|
||||||
|
*{text}*: Output of the script.
|
||||||
|
|
||||||
|
*{percentage}* Percentage which can be set via a json return type.
|
||||||
|
|
||||||
|
*{icon}*: An icon from 'format-icons' according to percentage.
|
||||||
|
|
||||||
|
# EXAMPLES
|
||||||
|
|
||||||
|
## Memory:
|
||||||
|
|
||||||
|
```
|
||||||
|
"custom-graph/memory": {
|
||||||
|
"interval": 60,
|
||||||
|
"graph_type": "gauge",
|
||||||
|
"width": 52,
|
||||||
|
"exec": "/path/mem.sh",
|
||||||
|
"signal": 8,
|
||||||
|
"return-type": "json"
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
mem.sh:
|
||||||
|
|
||||||
|
```
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
mem_info=$(cat /proc/meminfo)
|
||||||
|
mem_total=$(echo "$mem_info" | grep '^MemTotal:' | awk '{print $2}')
|
||||||
|
mem_available=$(echo "$mem_info" | grep '^MemAvailable:' | awk '{print $2}')
|
||||||
|
|
||||||
|
mem_used=$((mem_total - mem_available))
|
||||||
|
mem_percent=$((mem_used * 100 / mem_total))
|
||||||
|
|
||||||
|
echo "{\"text\": \"${mem_percent}%\", \"percentage\": ${mem_percent},\"tooltip\": \"Memory: ${mem_used}KB used / ${mem_total}KB total\"}'"
|
||||||
|
```
|
||||||
|
|
||||||
|
# STYLE
|
||||||
|
|
||||||
|
- *#custom-graph-<name>*
|
||||||
|
- *#custom-graph-<name>.<class>*
|
||||||
|
- *<class>* can be set by the script. For more information see *return-type*
|
||||||
@@ -41,6 +41,19 @@ This setting is ignored if *workspace-taskbar.enable* is set to true.
|
|||||||
The separator to be used between windows in a workspace. ++
|
The separator to be used between windows in a workspace. ++
|
||||||
This setting is ignored if *workspace-taskbar.enable* is set to true.
|
This setting is ignored if *workspace-taskbar.enable* is set to true.
|
||||||
|
|
||||||
|
*window-rewrite-group-threshold*: ++
|
||||||
|
typeof: int ++
|
||||||
|
default: 0 ++
|
||||||
|
When a workspace contains at least this many windows with the same rewrite result, they are collapsed into a single one using *window-rewrite-group-format*. ++
|
||||||
|
Set to 0 to disable grouping. ++
|
||||||
|
This setting is ignored if *workspace-taskbar.enable* is set to true.
|
||||||
|
|
||||||
|
*window-rewrite-group-format*: ++
|
||||||
|
typeof: string ++
|
||||||
|
default: "{icon}×{count}" ++
|
||||||
|
The format used to represent a group of collapsed windows. Available placeholders are {icon} (the icon being grouped) and {count} (how many windows share it). ++
|
||||||
|
This setting is ignored if *workspace-taskbar.enable* is set to true.
|
||||||
|
|
||||||
*workspace-taskbar*: ++
|
*workspace-taskbar*: ++
|
||||||
typeof: object ++
|
typeof: object ++
|
||||||
Contains settings for the workspace taskbar, an alternative mode for the workspaces module which displays the window icons as images instead of text.
|
Contains settings for the workspace taskbar, an alternative mode for the workspaces module which displays the window icons as images instead of text.
|
||||||
|
|||||||
@@ -76,6 +76,17 @@ screensaver, also known as "presentation mode".
|
|||||||
typeof: double ++
|
typeof: double ++
|
||||||
The number of minutes the inhibition should last.
|
The number of minutes the inhibition should last.
|
||||||
|
|
||||||
|
*wait-for-activity*: ++
|
||||||
|
typeof: bool ++
|
||||||
|
default: *false* ++
|
||||||
|
When enabled, the idle inhibitor remains active as long as there is keyboard or mouse activity on the bar. If there is no activity for the duration specified in *timeout*, the inhibitor will automatically toggle off. This option requires *timeout* to be set.
|
||||||
|
|
||||||
|
*signal*: ++
|
||||||
|
typeof: integer ++
|
||||||
|
The signal number used to toggle the idle inhibitor externally. ++
|
||||||
|
The number is valid between 1 and N, where *SIGRTMIN+N* = *SIGRTMAX*. ++
|
||||||
|
Use `pkill -SIGRTMIN+N waybar` to toggle the idle inhibitor from scripts or keybindings.
|
||||||
|
|
||||||
*tooltip*: ++
|
*tooltip*: ++
|
||||||
typeof: bool ++
|
typeof: bool ++
|
||||||
default: true ++
|
default: true ++
|
||||||
@@ -115,17 +126,46 @@ screensaver, also known as "presentation mode".
|
|||||||
|
|
||||||
# EXAMPLES
|
# EXAMPLES
|
||||||
|
|
||||||
|
Basic usage with timeout:
|
||||||
|
|
||||||
```
|
```
|
||||||
"idle_inhibitor": {
|
"idle_inhibitor": {
|
||||||
"format": "{icon}",
|
"format": "{icon}",
|
||||||
"format-icons": {
|
"format-icons": {
|
||||||
"activated": "",
|
"activated": "",
|
||||||
"deactivated": ""
|
"deactivated": ""
|
||||||
},
|
},
|
||||||
"timeout": 30.5
|
"timeout": 30.5
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
With external control via signals (can be toggled with `pkill -SIGRTMIN+8 waybar`):
|
||||||
|
|
||||||
|
```
|
||||||
|
"idle_inhibitor": {
|
||||||
|
"format": "{icon}",
|
||||||
|
"format-icons": {
|
||||||
|
"activated": "",
|
||||||
|
"deactivated": ""
|
||||||
|
},
|
||||||
|
"signal": 8
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
With wait-for-activity feature:
|
||||||
|
|
||||||
|
```
|
||||||
|
"idle_inhibitor": {
|
||||||
|
"format": "{icon}",
|
||||||
|
"format-icons": {
|
||||||
|
"activated": "",
|
||||||
|
"deactivated": ""
|
||||||
|
},
|
||||||
|
"timeout": 5.0,
|
||||||
|
"wait-for-activity": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
# STYLE
|
# STYLE
|
||||||
|
|
||||||
- *#idle_inhibitor*
|
- *#idle_inhibitor*
|
||||||
|
|||||||
@@ -29,6 +29,11 @@ Addressed by *mpd*
|
|||||||
default: 5 ++
|
default: 5 ++
|
||||||
The interval in which the connection to the MPD server is retried
|
The interval in which the connection to the MPD server is retried
|
||||||
|
|
||||||
|
*playing-interval*: ++
|
||||||
|
typeof: integer++
|
||||||
|
default: 1000 ++
|
||||||
|
The interval (in milliseconds) in which the playing state is updated.
|
||||||
|
|
||||||
*timeout*: ++
|
*timeout*: ++
|
||||||
typeof: integer++
|
typeof: integer++
|
||||||
default: 30 ++
|
default: 30 ++
|
||||||
|
|||||||
@@ -53,6 +53,11 @@ If none of the sorting options are enabled, workspaces keep their output/index o
|
|||||||
default: false ++
|
default: false ++
|
||||||
If set to false, you can click to change workspace. If set to true this behaviour is disabled.
|
If set to false, you can click to change workspace. If set to true this behaviour is disabled.
|
||||||
|
|
||||||
|
*enable-bar-scroll*: ++
|
||||||
|
typeof: bool ++
|
||||||
|
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.
|
||||||
|
|
||||||
*disable-markup*: ++
|
*disable-markup*: ++
|
||||||
typeof: bool ++
|
typeof: bool ++
|
||||||
default: false ++
|
default: false ++
|
||||||
@@ -90,6 +95,8 @@ as defined by niri.
|
|||||||
|
|
||||||
*{output}*: Output where the workspace is located.
|
*{output}*: Output where the workspace is located.
|
||||||
|
|
||||||
|
*{total}*: The total number of workspaces.
|
||||||
|
|
||||||
# ICONS
|
# ICONS
|
||||||
|
|
||||||
Additional to workspace name matching, the following *format-icons* can be set.
|
Additional to workspace name matching, the following *format-icons* can be set.
|
||||||
|
|||||||
@@ -117,6 +117,10 @@ Additionally, you can control the volume by scrolling *up* or *down* while the c
|
|||||||
typeof: array ++
|
typeof: array ++
|
||||||
Sinks in this list will not be shown as active sink by Waybar. Entries should be the sink's description field.
|
Sinks in this list will not be shown as active sink by Waybar. Entries should be the sink's description field.
|
||||||
|
|
||||||
|
*sink-mapping*: ++
|
||||||
|
typeof: object ++
|
||||||
|
Sinks named by the values of this mapping will be considered to be the current sink instead of the sinks named by the respective keys.
|
||||||
|
|
||||||
*menu*: ++
|
*menu*: ++
|
||||||
typeof: string ++
|
typeof: string ++
|
||||||
Action that popups the menu.
|
Action that popups the menu.
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ Addressed by *river/tags*
|
|||||||
- *#tags button.focused*
|
- *#tags button.focused*
|
||||||
- *#tags button.urgent*
|
- *#tags button.urgent*
|
||||||
- *#tags button.output*
|
- *#tags button.output*
|
||||||
|
- *#tags button.tag-N*
|
||||||
|
|
||||||
Note that occupied/focused/urgent/output status may overlap. That is, a tag may be
|
Note that occupied/focused/urgent/output status may overlap. That is, a tag may be
|
||||||
both occupied and focused at the same time.
|
both occupied and focused at the same time.
|
||||||
|
|||||||
@@ -375,6 +375,11 @@ A group may hide all but one element, showing them only on mouse hover. In order
|
|||||||
Defines the direction of the transition animation. If true, the hidden elements will slide from left to right. If false, they will slide from right to left.
|
Defines the direction of the transition animation. If true, the hidden elements will slide from left to right. If false, they will slide from right to left.
|
||||||
When the bar is vertical, it reads as top-to-bottom.
|
When the bar is vertical, it reads as top-to-bottom.
|
||||||
|
|
||||||
|
*reveal-by-default*: ++
|
||||||
|
typeof: bool ++
|
||||||
|
default: false ++
|
||||||
|
Whether the child should be revealed when Waybar starts up. This has to be used with click-to-reveal to take effect.
|
||||||
|
|
||||||
Group drawers are also given the `.expanded` CSS class when they are expanded.
|
Group drawers are also given the `.expanded` CSS class when they are expanded.
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -159,11 +159,13 @@ endif
|
|||||||
|
|
||||||
src_files = files(
|
src_files = files(
|
||||||
'src/factory.cpp',
|
'src/factory.cpp',
|
||||||
|
'src/AGraph.cpp',
|
||||||
'src/AModule.cpp',
|
'src/AModule.cpp',
|
||||||
'src/ALabel.cpp',
|
'src/ALabel.cpp',
|
||||||
'src/AIconLabel.cpp',
|
'src/AIconLabel.cpp',
|
||||||
'src/AAppIconLabel.cpp',
|
'src/AAppIconLabel.cpp',
|
||||||
'src/modules/custom.cpp',
|
'src/modules/custom.cpp',
|
||||||
|
'src/modules/custom_graph.cpp',
|
||||||
'src/modules/disk.cpp',
|
'src/modules/disk.cpp',
|
||||||
'src/modules/idle_inhibitor.cpp',
|
'src/modules/idle_inhibitor.cpp',
|
||||||
'src/modules/image.cpp',
|
'src/modules/image.cpp',
|
||||||
@@ -212,6 +214,7 @@ if is_linux
|
|||||||
'src/modules/bluetooth.cpp',
|
'src/modules/bluetooth.cpp',
|
||||||
'src/modules/cffi.cpp',
|
'src/modules/cffi.cpp',
|
||||||
'src/modules/cpu.cpp',
|
'src/modules/cpu.cpp',
|
||||||
|
'src/modules/cpu_graph.cpp',
|
||||||
'src/modules/cpu_frequency/common.cpp',
|
'src/modules/cpu_frequency/common.cpp',
|
||||||
'src/modules/cpu_frequency/linux.cpp',
|
'src/modules/cpu_frequency/linux.cpp',
|
||||||
'src/modules/cpu_usage/common.cpp',
|
'src/modules/cpu_usage/common.cpp',
|
||||||
@@ -236,6 +239,7 @@ elif is_dragonfly or is_freebsd or is_netbsd or is_openbsd
|
|||||||
src_files += files(
|
src_files += files(
|
||||||
'src/modules/cffi.cpp',
|
'src/modules/cffi.cpp',
|
||||||
'src/modules/cpu.cpp',
|
'src/modules/cpu.cpp',
|
||||||
|
'src/modules/cpu_graph.cpp',
|
||||||
'src/modules/cpu_frequency/bsd.cpp',
|
'src/modules/cpu_frequency/bsd.cpp',
|
||||||
'src/modules/cpu_frequency/common.cpp',
|
'src/modules/cpu_frequency/common.cpp',
|
||||||
'src/modules/cpu_usage/bsd.cpp',
|
'src/modules/cpu_usage/bsd.cpp',
|
||||||
|
|||||||
@@ -0,0 +1,131 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<protocol name="ext_idle_notify_v1">
|
||||||
|
<copyright>
|
||||||
|
Copyright © 2015 Martin Gräßlin
|
||||||
|
Copyright © 2022 Simon Ser
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
copy of this software and associated documentation files (the "Software"),
|
||||||
|
to deal in the Software without restriction, including without limitation
|
||||||
|
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||||
|
and/or sell copies of the Software, and to permit persons to whom the
|
||||||
|
Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice (including the next
|
||||||
|
paragraph) shall be included in all copies or substantial portions of the
|
||||||
|
Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||||
|
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
DEALINGS IN THE SOFTWARE.
|
||||||
|
</copyright>
|
||||||
|
|
||||||
|
<interface name="ext_idle_notifier_v1" version="2">
|
||||||
|
<description summary="idle notification manager">
|
||||||
|
This interface allows clients to monitor user idle status.
|
||||||
|
|
||||||
|
After binding to this global, clients can create ext_idle_notification_v1
|
||||||
|
objects to get notified when the user is idle for a given amount of time.
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<request name="destroy" type="destructor">
|
||||||
|
<description summary="destroy the manager">
|
||||||
|
Destroy the manager object. All objects created via this interface
|
||||||
|
remain valid.
|
||||||
|
</description>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<request name="get_idle_notification">
|
||||||
|
<description summary="create a notification object">
|
||||||
|
Create a new idle notification object.
|
||||||
|
|
||||||
|
The notification object has a minimum timeout duration and is tied to a
|
||||||
|
seat. The client will be notified if the seat is inactive for at least
|
||||||
|
the provided timeout. See ext_idle_notification_v1 for more details.
|
||||||
|
|
||||||
|
A zero timeout is valid and means the client wants to be notified as
|
||||||
|
soon as possible when the seat is inactive.
|
||||||
|
</description>
|
||||||
|
<arg name="id" type="new_id" interface="ext_idle_notification_v1"/>
|
||||||
|
<arg name="timeout" type="uint" summary="minimum idle timeout in msec"/>
|
||||||
|
<arg name="seat" type="object" interface="wl_seat"/>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<!-- Version 2 additions -->
|
||||||
|
|
||||||
|
<request name="get_input_idle_notification" since="2">
|
||||||
|
<description summary="create a notification object">
|
||||||
|
Create a new idle notification object to track input from the
|
||||||
|
user, such as keyboard and mouse movement. Because this object is
|
||||||
|
meant to track user input alone, it ignores idle inhibitors.
|
||||||
|
|
||||||
|
The notification object has a minimum timeout duration and is tied to a
|
||||||
|
seat. The client will be notified if the seat is inactive for at least
|
||||||
|
the provided timeout. See ext_idle_notification_v1 for more details.
|
||||||
|
|
||||||
|
A zero timeout is valid and means the client wants to be notified as
|
||||||
|
soon as possible when the seat is inactive.
|
||||||
|
</description>
|
||||||
|
<arg name="id" type="new_id" interface="ext_idle_notification_v1"/>
|
||||||
|
<arg name="timeout" type="uint" summary="minimum idle timeout in msec"/>
|
||||||
|
<arg name="seat" type="object" interface="wl_seat"/>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
</interface>
|
||||||
|
|
||||||
|
<interface name="ext_idle_notification_v1" version="2">
|
||||||
|
<description summary="idle notification">
|
||||||
|
This interface is used by the compositor to send idle notification events
|
||||||
|
to clients.
|
||||||
|
|
||||||
|
Initially the notification object is not idle. The notification object
|
||||||
|
becomes idle when no user activity has happened for at least the timeout
|
||||||
|
duration, starting from the creation of the notification object. User
|
||||||
|
activity may include input events or a presence sensor, but is
|
||||||
|
compositor-specific.
|
||||||
|
|
||||||
|
How this notification responds to idle inhibitors depends on how
|
||||||
|
it was constructed. If constructed from the
|
||||||
|
get_idle_notification request, then if an idle inhibitor is
|
||||||
|
active (e.g. another client has created a zwp_idle_inhibitor_v1
|
||||||
|
on a visible surface), the compositor must not make the
|
||||||
|
notification object idle. However, if constructed from the
|
||||||
|
get_input_idle_notification request, then idle inhibitors are
|
||||||
|
ignored, and only input from the user, e.g. from a keyboard or
|
||||||
|
mouse, counts as activity.
|
||||||
|
|
||||||
|
When the notification object becomes idle, an idled event is sent. When
|
||||||
|
user activity starts again, the notification object stops being idle,
|
||||||
|
a resumed event is sent and the timeout is restarted.
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<request name="destroy" type="destructor">
|
||||||
|
<description summary="destroy the notification object">
|
||||||
|
Destroy the notification object.
|
||||||
|
</description>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<event name="idled">
|
||||||
|
<description summary="notification object is idle">
|
||||||
|
This event is sent when the notification object becomes idle.
|
||||||
|
|
||||||
|
It's a compositor protocol error to send this event twice without a
|
||||||
|
resumed event in-between.
|
||||||
|
</description>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<event name="resumed">
|
||||||
|
<description summary="notification object is no longer idle">
|
||||||
|
This event is sent when the notification object stops being idle.
|
||||||
|
|
||||||
|
It's a compositor protocol error to send this event twice without an
|
||||||
|
idled event in-between. It's a compositor protocol error to send this
|
||||||
|
event prior to any idled event.
|
||||||
|
</description>
|
||||||
|
</event>
|
||||||
|
</interface>
|
||||||
|
</protocol>
|
||||||
@@ -29,6 +29,7 @@ client_protocols = [
|
|||||||
['river-status-unstable-v1.xml'],
|
['river-status-unstable-v1.xml'],
|
||||||
['river-control-unstable-v1.xml'],
|
['river-control-unstable-v1.xml'],
|
||||||
['dwl-ipc-unstable-v2.xml'],
|
['dwl-ipc-unstable-v2.xml'],
|
||||||
|
['ext-idle-notify-v1.xml'],
|
||||||
]
|
]
|
||||||
|
|
||||||
if wayland_protos.version().version_compare('>=1.39')
|
if wayland_protos.version().version_compare('>=1.39')
|
||||||
|
|||||||
+298
@@ -0,0 +1,298 @@
|
|||||||
|
#include "AGraph.hpp"
|
||||||
|
|
||||||
|
#include <cairomm/context.h>
|
||||||
|
#include <fmt/format.h>
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <util/command.hpp>
|
||||||
|
|
||||||
|
#include "config.hpp"
|
||||||
|
|
||||||
|
namespace waybar {
|
||||||
|
|
||||||
|
AGraph::AGraph(const Json::Value& config, const std::string& name, const std::string& id,
|
||||||
|
uint16_t interval, bool enable_click, bool enable_scroll)
|
||||||
|
: AModule(config, name, id,
|
||||||
|
config["format-alt"].isString() || config["menu"].isString() || enable_click,
|
||||||
|
enable_scroll),
|
||||||
|
interval_(config_["interval"] == "once"
|
||||||
|
? std::chrono::seconds::max()
|
||||||
|
: std::chrono::seconds(
|
||||||
|
config_["interval"].isUInt() ? config_["interval"].asUInt() : interval)) {
|
||||||
|
graph_.signal_draw().connect(sigc::mem_fun(*this, &AGraph::onDraw));
|
||||||
|
graph_.set_name(name);
|
||||||
|
if (!id.empty()) {
|
||||||
|
graph_.get_style_context()->add_class(id);
|
||||||
|
}
|
||||||
|
graph_.get_style_context()->add_class(MODULE_CLASS);
|
||||||
|
if (config_["width"].isUInt()) {
|
||||||
|
graph_.set_size_request(config_["width"].asUInt(), -1);
|
||||||
|
} else {
|
||||||
|
graph_.set_size_request(100, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
event_box_.add(graph_);
|
||||||
|
|
||||||
|
if (config_["datapoints"].isUInt() && config_["datapoints"].asUInt() > 0) {
|
||||||
|
datapoints_ = config_["datapoints"].asUInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config_["graph_type"].isString()) {
|
||||||
|
std::string type = config_["graph_type"].asString();
|
||||||
|
if (type == "line") {
|
||||||
|
graph_type_ = GraphType::LINE;
|
||||||
|
} else if (type == "bar") {
|
||||||
|
graph_type_ = GraphType::BAR;
|
||||||
|
} else if (type == "gauge") {
|
||||||
|
graph_type_ = GraphType::GAUGE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If a GTKMenu is requested in the config
|
||||||
|
if (config_["menu"].isString()) {
|
||||||
|
// Create the GTKMenu widget
|
||||||
|
try {
|
||||||
|
// Check that the file exists
|
||||||
|
std::string menuFile = config_["menu-file"].asString();
|
||||||
|
|
||||||
|
// there might be "~" or "$HOME" in original path, try to expand it.
|
||||||
|
auto result = Config::tryExpandPath(menuFile, "");
|
||||||
|
if (result.empty()) {
|
||||||
|
throw std::runtime_error("Failed to expand file: " + menuFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
menuFile = result.front();
|
||||||
|
// Read the menu descriptor file
|
||||||
|
std::ifstream file(menuFile);
|
||||||
|
if (!file.is_open()) {
|
||||||
|
throw std::runtime_error("Failed to open file: " + menuFile);
|
||||||
|
}
|
||||||
|
std::stringstream fileContent;
|
||||||
|
fileContent << file.rdbuf();
|
||||||
|
GtkBuilder* builder = gtk_builder_new();
|
||||||
|
|
||||||
|
// Make the GtkBuilder and check for errors in his parsing
|
||||||
|
if (gtk_builder_add_from_string(builder, fileContent.str().c_str(), -1, nullptr) == 0U) {
|
||||||
|
throw std::runtime_error("Error found in the file " + menuFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
menu_ = gtk_builder_get_object(builder, "menu");
|
||||||
|
if (menu_ == nullptr) {
|
||||||
|
throw std::runtime_error("Failed to get 'menu' object from GtkBuilder");
|
||||||
|
}
|
||||||
|
submenus_ = std::map<std::string, GtkMenuItem*>();
|
||||||
|
menuActionsMap_ = std::map<std::string, std::string>();
|
||||||
|
|
||||||
|
// Linking actions to the GTKMenu based on
|
||||||
|
for (Json::Value::const_iterator it = config_["menu-actions"].begin();
|
||||||
|
it != config_["menu-actions"].end(); ++it) {
|
||||||
|
std::string key = it.key().asString();
|
||||||
|
submenus_[key] = GTK_MENU_ITEM(gtk_builder_get_object(builder, key.c_str()));
|
||||||
|
menuActionsMap_[key] = it->asString();
|
||||||
|
g_signal_connect(submenus_[key], "activate", G_CALLBACK(handleGtkMenuEvent),
|
||||||
|
(gpointer)menuActionsMap_[key].c_str());
|
||||||
|
}
|
||||||
|
} catch (std::runtime_error& e) {
|
||||||
|
spdlog::warn("Error while creating the menu : {}. Menu popup not activated.", e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto AGraph::update() -> void {
|
||||||
|
graph_.queue_draw();
|
||||||
|
AModule::update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AGraph::handleGtkMenuEvent(GtkMenuItem* /*menuitem*/, gpointer data) {
|
||||||
|
waybar::util::command::res res = waybar::util::command::exec((char*)data, "GtkMenu");
|
||||||
|
}
|
||||||
|
|
||||||
|
void AGraph::addValue(const int n) {
|
||||||
|
if (datapoints_ > 0 && values_.size() >= datapoints_) {
|
||||||
|
values_.pop_front();
|
||||||
|
}
|
||||||
|
values_.push_back(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AGraph::onDraw(const Cairo::RefPtr<Cairo::Context>& cr) {
|
||||||
|
const int width = graph_.get_allocated_width();
|
||||||
|
const int height = graph_.get_allocated_height() - 1;
|
||||||
|
|
||||||
|
if (values_.empty() || width <= 0 || height <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto style_context = graph_.get_style_context();
|
||||||
|
Gdk::RGBA fg_color = style_context->get_color(Gtk::STATE_FLAG_NORMAL);
|
||||||
|
Gdk::RGBA bg_color = fg_color;
|
||||||
|
bg_color.set_alpha(0.3);
|
||||||
|
|
||||||
|
cr->set_line_width(1.0);
|
||||||
|
|
||||||
|
const double step_width = static_cast<double>(width) / datapoints_;
|
||||||
|
const int values_count = values_.size();
|
||||||
|
const int empty_space = datapoints_ - values_count;
|
||||||
|
|
||||||
|
std::vector<std::pair<double, double>> points;
|
||||||
|
points.reserve(values_count);
|
||||||
|
|
||||||
|
for (int i = empty_space; i < datapoints_; ++i) {
|
||||||
|
double x = i * step_width;
|
||||||
|
int value_index = i - empty_space;
|
||||||
|
int value = values_[value_index];
|
||||||
|
double y = height - (static_cast<double>(value) / 100.0 * height);
|
||||||
|
points.emplace_back(x, y);
|
||||||
|
}
|
||||||
|
if (!points.empty()) {
|
||||||
|
switch (graph_type_) {
|
||||||
|
case GraphType::LINE:
|
||||||
|
drawFilledArea(cr, points, height, bg_color);
|
||||||
|
drawLine(cr, points, fg_color);
|
||||||
|
break;
|
||||||
|
case GraphType::BAR:
|
||||||
|
drawBars(cr, width, height, values_.empty() ? 0 : values_.back(), fg_color);
|
||||||
|
break;
|
||||||
|
case GraphType::GAUGE:
|
||||||
|
drawGauge(cr, width, height, values_.empty() ? 0 : values_.back(), fg_color);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
void AGraph::drawFilledArea(const Cairo::RefPtr<Cairo::Context>& cr,
|
||||||
|
const std::vector<std::pair<double, double>>& points, double height,
|
||||||
|
const Gdk::RGBA& bg_color) {
|
||||||
|
if (points.empty()) return;
|
||||||
|
|
||||||
|
double first_x = points.front().first;
|
||||||
|
double last_x = points.back().first;
|
||||||
|
|
||||||
|
drawPath(cr, points);
|
||||||
|
|
||||||
|
cr->line_to(last_x, height);
|
||||||
|
cr->line_to(first_x, height);
|
||||||
|
cr->close_path();
|
||||||
|
|
||||||
|
cr->set_source_rgba(bg_color.get_red(), bg_color.get_green(), bg_color.get_blue(),
|
||||||
|
bg_color.get_alpha());
|
||||||
|
cr->fill();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AGraph::drawLine(const Cairo::RefPtr<Cairo::Context>& cr,
|
||||||
|
const std::vector<std::pair<double, double>>& points,
|
||||||
|
const Gdk::RGBA& fg_color) {
|
||||||
|
if (points.empty()) return;
|
||||||
|
|
||||||
|
cr->begin_new_path();
|
||||||
|
drawPath(cr, points);
|
||||||
|
|
||||||
|
cr->set_source_rgba(fg_color.get_red(), fg_color.get_green(), fg_color.get_blue(),
|
||||||
|
fg_color.get_alpha());
|
||||||
|
cr->stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AGraph::drawPath(const Cairo::RefPtr<Cairo::Context>& cr,
|
||||||
|
const std::vector<std::pair<double, double>>& points) {
|
||||||
|
if (points.empty()) return;
|
||||||
|
|
||||||
|
bool first_point = true;
|
||||||
|
for (const auto& point : points) {
|
||||||
|
if (first_point) {
|
||||||
|
cr->move_to(point.first, point.second);
|
||||||
|
first_point = false;
|
||||||
|
} else {
|
||||||
|
cr->line_to(point.first, point.second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AGraph::drawBars(const Cairo::RefPtr<Cairo::Context>& cr, double width, double height,
|
||||||
|
int current_value, const Gdk::RGBA& fg_color) {
|
||||||
|
current_value = std::min(100, std::max(0, current_value));
|
||||||
|
|
||||||
|
double green_height = height * (std::min(current_value, 40) / 100.0);
|
||||||
|
cr->set_source_rgba(fg_color.get_red(), fg_color.get_green(), fg_color.get_blue(), 0.5);
|
||||||
|
cr->rectangle(0, height - green_height, width, green_height);
|
||||||
|
cr->fill();
|
||||||
|
|
||||||
|
if (current_value > 40) {
|
||||||
|
double yellow_height = height * (std::min(current_value, 75) - 40) / 100.0;
|
||||||
|
cr->set_source_rgba(fg_color.get_red(), fg_color.get_green(), fg_color.get_blue(), 0.7);
|
||||||
|
cr->rectangle(0, height - green_height - yellow_height, width, yellow_height);
|
||||||
|
cr->fill();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current_value > 75) {
|
||||||
|
double orange_height = height * (std::min(current_value, 85) - 75) / 100.0;
|
||||||
|
cr->set_source_rgba(fg_color.get_red(), fg_color.get_green(), fg_color.get_blue(), 0.85);
|
||||||
|
double yellow_height = height * (std::min(current_value, 75) - 40) / 100.0;
|
||||||
|
cr->rectangle(0, height - green_height - yellow_height - orange_height, width, orange_height);
|
||||||
|
cr->fill();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current_value > 85) {
|
||||||
|
double red_height = height * (current_value - 85) / 100.0;
|
||||||
|
cr->set_source_rgba(fg_color.get_red(), fg_color.get_green(), fg_color.get_blue(), 1.0);
|
||||||
|
double yellow_height = height * (std::min(current_value, 75) - 40) / 100.0;
|
||||||
|
double orange_height = height * (std::min(current_value, 85) - 75) / 100.0;
|
||||||
|
cr->rectangle(0, height - green_height - yellow_height - orange_height - red_height, width,
|
||||||
|
red_height);
|
||||||
|
cr->fill();
|
||||||
|
}
|
||||||
|
|
||||||
|
double value_height = height * (current_value / 100.0);
|
||||||
|
cr->set_source_rgba(0.2, 0.2, 0.2, 0.8);
|
||||||
|
cr->rectangle(0, height - value_height, width, 2);
|
||||||
|
cr->fill();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AGraph::drawGauge(const Cairo::RefPtr<Cairo::Context>& cr, double width, double height,
|
||||||
|
int current_value, const Gdk::RGBA& fg_color) {
|
||||||
|
double center_x = width / 2.0;
|
||||||
|
double center_y = height;
|
||||||
|
double radius = height / 2.0;
|
||||||
|
|
||||||
|
cr->set_line_width(10.0);
|
||||||
|
|
||||||
|
double angle1 = M_PI;
|
||||||
|
double angle2 = angle1 + 0.3 * angle1;
|
||||||
|
|
||||||
|
// Green section (0-33%)
|
||||||
|
cr->set_source_rgba(fg_color.get_red(), fg_color.get_green(), fg_color.get_blue(), 0.5);
|
||||||
|
cr->arc(center_x, center_y, radius, angle1, angle2);
|
||||||
|
cr->stroke();
|
||||||
|
|
||||||
|
// Yellow section (33-66%)
|
||||||
|
angle1 = angle2;
|
||||||
|
angle2 = angle1 + 0.3 * angle1;
|
||||||
|
cr->set_source_rgba(fg_color.get_red(), fg_color.get_green(), fg_color.get_blue(), 0.75);
|
||||||
|
cr->arc(center_x, center_y, radius, angle1, angle2);
|
||||||
|
cr->stroke();
|
||||||
|
|
||||||
|
// Red section (66-100%)
|
||||||
|
angle1 = angle2;
|
||||||
|
angle2 = 0.0;
|
||||||
|
cr->set_source_rgba(fg_color.get_red(), fg_color.get_green(), fg_color.get_blue(), 1.0);
|
||||||
|
cr->arc(center_x, center_y, radius, angle1, angle2);
|
||||||
|
cr->stroke();
|
||||||
|
|
||||||
|
// Draw needle
|
||||||
|
double percentage = std::min(100, std::max(0, current_value)) / 100.0;
|
||||||
|
double needle_angle = M_PI * percentage;
|
||||||
|
double needle_length = radius;
|
||||||
|
|
||||||
|
double needle_x = center_x - needle_length * cos(needle_angle);
|
||||||
|
double needle_y = center_y - needle_length * sin(needle_angle);
|
||||||
|
|
||||||
|
cr->set_source_rgba(1.0, 1.0, 1.0, 1.0);
|
||||||
|
cr->set_line_width(2.0);
|
||||||
|
cr->begin_new_path();
|
||||||
|
cr->move_to(center_x, center_y);
|
||||||
|
cr->line_to(needle_x, needle_y);
|
||||||
|
cr->stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace waybar
|
||||||
@@ -3,10 +3,12 @@
|
|||||||
#include <gtk-layer-shell.h>
|
#include <gtk-layer-shell.h>
|
||||||
#include <spdlog/spdlog.h>
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include "gtkmm/icontheme.h"
|
#include "gtkmm/icontheme.h"
|
||||||
|
#include "ext-idle-notify-v1-client-protocol.h"
|
||||||
#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"
|
||||||
@@ -39,6 +41,12 @@ void waybar::Client::handleGlobal(void* data, struct wl_registry* registry, uint
|
|||||||
|
|
||||||
client->idle_inhibit_manager = static_cast<struct zwp_idle_inhibit_manager_v1*>(
|
client->idle_inhibit_manager = static_cast<struct zwp_idle_inhibit_manager_v1*>(
|
||||||
wl_registry_bind(registry, name, &zwp_idle_inhibit_manager_v1_interface, 1));
|
wl_registry_bind(registry, name, &zwp_idle_inhibit_manager_v1_interface, 1));
|
||||||
|
} else if (strcmp(interface, ext_idle_notifier_v1_interface.name) == 0) {
|
||||||
|
// Bind version 2 if available (for get_input_idle_notification), otherwise version 1
|
||||||
|
auto bind_version = std::min(version, 2u);
|
||||||
|
client->idle_notifier = static_cast<struct ext_idle_notifier_v1 *>(
|
||||||
|
wl_registry_bind(registry, name, &ext_idle_notifier_v1_interface, bind_version));
|
||||||
|
spdlog::debug("Bound ext-idle-notifier-v1 at version {}", bind_version);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+10
-2
@@ -43,11 +43,11 @@
|
|||||||
#include "modules/niri/workspaces.hpp"
|
#include "modules/niri/workspaces.hpp"
|
||||||
#endif
|
#endif
|
||||||
#ifdef HAVE_MANGO
|
#ifdef HAVE_MANGO
|
||||||
#include "modules/mango/language.hpp"
|
|
||||||
#include "modules/mango/keymode.hpp"
|
#include "modules/mango/keymode.hpp"
|
||||||
|
#include "modules/mango/language.hpp"
|
||||||
|
#include "modules/mango/layout.hpp"
|
||||||
#include "modules/mango/window.hpp"
|
#include "modules/mango/window.hpp"
|
||||||
#include "modules/mango/workspaces.hpp"
|
#include "modules/mango/workspaces.hpp"
|
||||||
#include "modules/mango/layout.hpp"
|
|
||||||
#endif
|
#endif
|
||||||
#ifdef HAVE_WAYFIRE
|
#ifdef HAVE_WAYFIRE
|
||||||
#include "modules/wayfire/window.hpp"
|
#include "modules/wayfire/window.hpp"
|
||||||
@@ -59,6 +59,7 @@
|
|||||||
#if defined(HAVE_CPU_LINUX) || defined(HAVE_CPU_BSD)
|
#if defined(HAVE_CPU_LINUX) || defined(HAVE_CPU_BSD)
|
||||||
#include "modules/cpu.hpp"
|
#include "modules/cpu.hpp"
|
||||||
#include "modules/cpu_frequency.hpp"
|
#include "modules/cpu_frequency.hpp"
|
||||||
|
#include "modules/cpu_graph.hpp"
|
||||||
#include "modules/cpu_usage.hpp"
|
#include "modules/cpu_usage.hpp"
|
||||||
#include "modules/load.hpp"
|
#include "modules/load.hpp"
|
||||||
#endif
|
#endif
|
||||||
@@ -124,6 +125,7 @@
|
|||||||
#include "modules/cava/cava_frontend.hpp"
|
#include "modules/cava/cava_frontend.hpp"
|
||||||
#include "modules/cffi.hpp"
|
#include "modules/cffi.hpp"
|
||||||
#include "modules/custom.hpp"
|
#include "modules/custom.hpp"
|
||||||
|
#include "modules/custom_graph.hpp"
|
||||||
#include "modules/image.hpp"
|
#include "modules/image.hpp"
|
||||||
#include "modules/temperature.hpp"
|
#include "modules/temperature.hpp"
|
||||||
#include "modules/user.hpp"
|
#include "modules/user.hpp"
|
||||||
@@ -275,6 +277,9 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name,
|
|||||||
if (ref == "cpu") {
|
if (ref == "cpu") {
|
||||||
return new waybar::modules::Cpu(id, config_[name]);
|
return new waybar::modules::Cpu(id, config_[name]);
|
||||||
}
|
}
|
||||||
|
if (ref == "cpu_graph") {
|
||||||
|
return new waybar::modules::CpuGraph(id, config_[name]);
|
||||||
|
}
|
||||||
#if defined(HAVE_CPU_LINUX)
|
#if defined(HAVE_CPU_LINUX)
|
||||||
if (ref == "cpu_frequency") {
|
if (ref == "cpu_frequency") {
|
||||||
return new waybar::modules::CpuFrequency(id, config_[name]);
|
return new waybar::modules::CpuFrequency(id, config_[name]);
|
||||||
@@ -382,6 +387,9 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name,
|
|||||||
if (ref.compare(0, 7, "custom/") == 0 && ref.size() > 7) {
|
if (ref.compare(0, 7, "custom/") == 0 && ref.size() > 7) {
|
||||||
return new waybar::modules::Custom(ref.substr(7), id, config_[name], bar_.output->name);
|
return new waybar::modules::Custom(ref.substr(7), id, config_[name], bar_.output->name);
|
||||||
}
|
}
|
||||||
|
if (ref.compare(0, 13, "custom-graph/") == 0 && ref.size() > 13) {
|
||||||
|
return new waybar::modules::CustomGraph(ref.substr(13), id, config_[name], bar_.output->name);
|
||||||
|
}
|
||||||
if (ref.compare(0, 5, "cffi/") == 0 && ref.size() > 5) {
|
if (ref.compare(0, 5, "cffi/") == 0 && ref.size() > 5) {
|
||||||
return new waybar::modules::CFFI(ref.substr(5), id, config_[name]);
|
return new waybar::modules::CFFI(ref.substr(5), id, config_[name]);
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-4
@@ -62,6 +62,10 @@ Group::Group(const std::string& name, const std::string& id, const Json::Value&
|
|||||||
const bool left_to_right = (drawer_config["transition-left-to-right"].isBool()
|
const bool left_to_right = (drawer_config["transition-left-to-right"].isBool()
|
||||||
? drawer_config["transition-left-to-right"].asBool()
|
? drawer_config["transition-left-to-right"].asBool()
|
||||||
: true);
|
: true);
|
||||||
|
const bool reveal_by_default =
|
||||||
|
(drawer_config["reveal-by-default"].isBool() ? drawer_config["reveal-by-default"].asBool()
|
||||||
|
: false);
|
||||||
|
|
||||||
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();
|
reveal_delay = drawer_config["reveal-delay"].asInt();
|
||||||
|
|
||||||
@@ -73,10 +77,11 @@ Group::Group(const std::string& name, const std::string& id, const Json::Value&
|
|||||||
|
|
||||||
revealer.set_transition_type(transition_type);
|
revealer.set_transition_type(transition_type);
|
||||||
revealer.set_transition_duration(transition_duration);
|
revealer.set_transition_duration(transition_duration);
|
||||||
revealer.set_reveal_child(start_expanded);
|
if ((click_to_reveal && reveal_by_default) || start_expanded) {
|
||||||
|
|
||||||
if (start_expanded) {
|
|
||||||
box.set_state_flags(Gtk::StateFlags::STATE_FLAG_PRELIGHT);
|
box.set_state_flags(Gtk::StateFlags::STATE_FLAG_PRELIGHT);
|
||||||
|
revealer.set_reveal_child(true);
|
||||||
|
} else {
|
||||||
|
revealer.set_reveal_child(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
revealer.get_style_context()->add_class("drawer");
|
revealer.get_style_context()->add_class("drawer");
|
||||||
@@ -113,7 +118,10 @@ bool Group::handleMouseEnter(GdkEventCrossing* const& e) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
reveal_timeout_ = Glib::signal_timeout().connect(
|
reveal_timeout_ = Glib::signal_timeout().connect(
|
||||||
[this]() { show_group(); return false; },
|
[this]() {
|
||||||
|
show_group();
|
||||||
|
return false;
|
||||||
|
},
|
||||||
reveal_delay);
|
reveal_delay);
|
||||||
} else {
|
} else {
|
||||||
show_group();
|
show_group();
|
||||||
|
|||||||
+151
-3
@@ -83,6 +83,68 @@ auto getUcharProperty(GDBusProxy* proxy, const char* property_name) -> unsigned
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto isChildPath(const std::string& child, const std::string& parent) -> bool {
|
||||||
|
return child.starts_with(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto readBatteryCharacteristicValue(GDBusProxy* proxy_char) -> std::optional<unsigned char> {
|
||||||
|
GVariantBuilder builder;
|
||||||
|
g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}"));
|
||||||
|
|
||||||
|
GError* error = nullptr;
|
||||||
|
GVariant* gvar =
|
||||||
|
g_dbus_proxy_call_sync(proxy_char, "ReadValue", g_variant_new("(a{sv})", &builder),
|
||||||
|
G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &error);
|
||||||
|
if (error != nullptr) {
|
||||||
|
g_error_free(error);
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
if (gvar == nullptr) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
GVariant* value_array = g_variant_get_child_value(gvar, 0);
|
||||||
|
gsize n_elements;
|
||||||
|
const auto* data = static_cast<const guchar*>(
|
||||||
|
g_variant_get_fixed_array(value_array, &n_elements, sizeof(guchar)));
|
||||||
|
|
||||||
|
std::optional<unsigned char> result;
|
||||||
|
if (data != nullptr && n_elements > 0) {
|
||||||
|
result = data[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
g_variant_unref(value_array);
|
||||||
|
g_variant_unref(gvar);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto hasUserDescriptionDescriptor(GList* objects, const std::string& char_path,
|
||||||
|
const std::string& user_description_uuid) -> bool {
|
||||||
|
for (GList* n = objects; n != nullptr; n = n->next) {
|
||||||
|
GDBusObject* desc_object = G_DBUS_OBJECT(n->data);
|
||||||
|
std::string desc_path = g_dbus_object_get_object_path(desc_object);
|
||||||
|
|
||||||
|
if (!isChildPath(desc_path, char_path)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
GDBusProxy* proxy_desc =
|
||||||
|
G_DBUS_PROXY(g_dbus_object_get_interface(desc_object, "org.bluez.GattDescriptor1"));
|
||||||
|
if (proxy_desc == nullptr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto desc_uuid = getOptionalStringProperty(proxy_desc, "UUID");
|
||||||
|
g_object_unref(proxy_desc);
|
||||||
|
|
||||||
|
if (desc_uuid.has_value() &&
|
||||||
|
desc_uuid.value().find(user_description_uuid) != std::string::npos) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
waybar::modules::Bluetooth::Bluetooth(const std::string& id, const Json::Value& config)
|
waybar::modules::Bluetooth::Bluetooth(const std::string& id, const Json::Value& config)
|
||||||
@@ -232,8 +294,9 @@ auto waybar::modules::Bluetooth::update() -> void {
|
|||||||
fmt::arg("device_address", cur_focussed_device_.address),
|
fmt::arg("device_address", cur_focussed_device_.address),
|
||||||
fmt::arg("device_address_type", cur_focussed_device_.address_type),
|
fmt::arg("device_address_type", cur_focussed_device_.address_type),
|
||||||
fmt::arg("device_alias", cur_focussed_device_.alias), fmt::arg("icon", icon_label),
|
fmt::arg("device_alias", cur_focussed_device_.alias), fmt::arg("icon", icon_label),
|
||||||
fmt::arg("device_battery_percentage",
|
fmt::arg("device_battery_percentage", cur_focussed_device_.battery_percentage.value_or(0)),
|
||||||
cur_focussed_device_.battery_percentage.value_or(0))));
|
fmt::arg("device_battery_percentage_peripheral",
|
||||||
|
cur_focussed_device_.battery_percentage_peripheral.value_or(0))));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tooltipEnabled()) {
|
if (tooltipEnabled()) {
|
||||||
@@ -258,7 +321,9 @@ auto waybar::modules::Bluetooth::update() -> void {
|
|||||||
fmt::runtime(enumerate_format), fmt::arg("device_address", dev.address),
|
fmt::runtime(enumerate_format), fmt::arg("device_address", dev.address),
|
||||||
fmt::arg("device_address_type", dev.address_type),
|
fmt::arg("device_address_type", dev.address_type),
|
||||||
fmt::arg("device_alias", dev.alias), fmt::arg("icon", enumerate_icon),
|
fmt::arg("device_alias", dev.alias), fmt::arg("icon", enumerate_icon),
|
||||||
fmt::arg("device_battery_percentage", dev.battery_percentage.value_or(0)));
|
fmt::arg("device_battery_percentage", dev.battery_percentage.value_or(0)),
|
||||||
|
fmt::arg("device_battery_percentage_peripheral",
|
||||||
|
dev.battery_percentage_peripheral.value_or(0)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
device_enumerate_ = ss.str();
|
device_enumerate_ = ss.str();
|
||||||
@@ -278,6 +343,8 @@ auto waybar::modules::Bluetooth::update() -> void {
|
|||||||
fmt::arg("device_address_type", cur_focussed_device_.address_type),
|
fmt::arg("device_address_type", cur_focussed_device_.address_type),
|
||||||
fmt::arg("device_alias", cur_focussed_device_.alias), fmt::arg("icon", icon_tooltip),
|
fmt::arg("device_alias", cur_focussed_device_.alias), fmt::arg("icon", icon_tooltip),
|
||||||
fmt::arg("device_battery_percentage", cur_focussed_device_.battery_percentage.value_or(0)),
|
fmt::arg("device_battery_percentage", cur_focussed_device_.battery_percentage.value_or(0)),
|
||||||
|
fmt::arg("device_battery_percentage_peripheral",
|
||||||
|
cur_focussed_device_.battery_percentage_peripheral.value_or(0)),
|
||||||
fmt::arg("device_enumerate", device_enumerate_)));
|
fmt::arg("device_enumerate", device_enumerate_)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -398,6 +465,85 @@ auto waybar::modules::Bluetooth::getDeviceBatteryPercentage(GDBusObject* object)
|
|||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto waybar::modules::Bluetooth::getDeviceGattBatteryLevels(
|
||||||
|
GDBusObject* device_object, std::optional<unsigned char>& central_battery,
|
||||||
|
std::optional<unsigned char>& peripheral_battery) -> void {
|
||||||
|
const std::string BATTERY_SERVICE_UUID = "0000180f-0000-1000-8000-00805f9b34fb";
|
||||||
|
const std::string BATTERY_LEVEL_UUID = "00002a19-0000-1000-8000-00805f9b34fb";
|
||||||
|
const std::string USER_DESCRIPTION_UUID = "00002901-0000-1000-8000-00805f9b34fb";
|
||||||
|
|
||||||
|
GList* objects = g_dbus_object_manager_get_objects(manager_.get());
|
||||||
|
std::string device_path = g_dbus_object_get_object_path(device_object);
|
||||||
|
|
||||||
|
for (GList* l = objects; l != nullptr; l = l->next) {
|
||||||
|
GDBusObject* service_object = G_DBUS_OBJECT(l->data);
|
||||||
|
std::string service_path = g_dbus_object_get_object_path(service_object);
|
||||||
|
|
||||||
|
if (!isChildPath(service_path, device_path)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
GDBusProxy* proxy_service =
|
||||||
|
G_DBUS_PROXY(g_dbus_object_get_interface(service_object, "org.bluez.GattService1"));
|
||||||
|
if (proxy_service == nullptr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto service_uuid = getOptionalStringProperty(proxy_service, "UUID");
|
||||||
|
g_object_unref(proxy_service);
|
||||||
|
|
||||||
|
if (!service_uuid.has_value() ||
|
||||||
|
service_uuid.value().find(BATTERY_SERVICE_UUID) == std::string::npos) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
processBatteryServiceCharacteristics(objects, service_path, BATTERY_LEVEL_UUID,
|
||||||
|
USER_DESCRIPTION_UUID, central_battery,
|
||||||
|
peripheral_battery);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_list_free_full(objects, g_object_unref);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto waybar::modules::Bluetooth::processBatteryServiceCharacteristics(
|
||||||
|
GList* objects, const std::string& service_path, const std::string& battery_level_uuid,
|
||||||
|
const std::string& user_description_uuid, std::optional<unsigned char>& central_battery,
|
||||||
|
std::optional<unsigned char>& peripheral_battery) -> void {
|
||||||
|
for (GList* m = objects; m != nullptr; m = m->next) {
|
||||||
|
GDBusObject* char_object = G_DBUS_OBJECT(m->data);
|
||||||
|
std::string char_path = g_dbus_object_get_object_path(char_object);
|
||||||
|
|
||||||
|
if (!isChildPath(char_path, service_path)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
GDBusProxy* proxy_char =
|
||||||
|
G_DBUS_PROXY(g_dbus_object_get_interface(char_object, "org.bluez.GattCharacteristic1"));
|
||||||
|
if (proxy_char == nullptr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto char_uuid = getOptionalStringProperty(proxy_char, "UUID");
|
||||||
|
if (!char_uuid.has_value() || char_uuid.value().find(battery_level_uuid) == std::string::npos) {
|
||||||
|
g_object_unref(proxy_char);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto battery_value = readBatteryCharacteristicValue(proxy_char);
|
||||||
|
g_object_unref(proxy_char);
|
||||||
|
|
||||||
|
if (!battery_value.has_value()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasUserDescriptionDescriptor(objects, char_path, user_description_uuid)) {
|
||||||
|
peripheral_battery = battery_value.value();
|
||||||
|
} else {
|
||||||
|
central_battery = battery_value.value();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
auto waybar::modules::Bluetooth::getDeviceProperties(GDBusObject* object, DeviceInfo& device_info)
|
auto waybar::modules::Bluetooth::getDeviceProperties(GDBusObject* object, DeviceInfo& device_info)
|
||||||
-> bool {
|
-> bool {
|
||||||
GDBusProxy* proxy_device = G_DBUS_PROXY(g_dbus_object_get_interface(object, "org.bluez.Device1"));
|
GDBusProxy* proxy_device = G_DBUS_PROXY(g_dbus_object_get_interface(object, "org.bluez.Device1"));
|
||||||
@@ -418,6 +564,8 @@ auto waybar::modules::Bluetooth::getDeviceProperties(GDBusObject* object, Device
|
|||||||
g_object_unref(proxy_device);
|
g_object_unref(proxy_device);
|
||||||
|
|
||||||
device_info.battery_percentage = getDeviceBatteryPercentage(object);
|
device_info.battery_percentage = getDeviceBatteryPercentage(object);
|
||||||
|
getDeviceGattBatteryLevels(object, device_info.battery_percentage,
|
||||||
|
device_info.battery_percentage_peripheral);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include <regex>
|
#include <regex>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
|
#include "util/command.hpp"
|
||||||
#include "util/ustring_clen.hpp"
|
#include "util/ustring_clen.hpp"
|
||||||
|
|
||||||
#ifdef HAVE_LANGINFO_1STDAY
|
#ifdef HAVE_LANGINFO_1STDAY
|
||||||
@@ -516,6 +517,8 @@ auto waybar::modules::Clock::local_zone() -> const time_zone* {
|
|||||||
auto waybar::modules::Clock::doAction(const std::string& name) -> void {
|
auto waybar::modules::Clock::doAction(const std::string& name) -> void {
|
||||||
if (actionMap_[name]) {
|
if (actionMap_[name]) {
|
||||||
(this->*actionMap_[name])();
|
(this->*actionMap_[name])();
|
||||||
|
} else if (auto key = name.substr(0, name.find(" ")); actionWithArgsMap_[key]) {
|
||||||
|
(this->*actionWithArgsMap_[key])(name);
|
||||||
} else
|
} else
|
||||||
spdlog::error("Clock. Unsupported action \"{0}\"", name);
|
spdlog::error("Clock. Unsupported action \"{0}\"", name);
|
||||||
}
|
}
|
||||||
@@ -542,6 +545,10 @@ void waybar::modules::Clock::tz_down() {
|
|||||||
if (tzSize == 1) return;
|
if (tzSize == 1) return;
|
||||||
tzCurrIdx_ = (tzCurrIdx_ == 0) ? tzSize - 1 : tzCurrIdx_ - 1;
|
tzCurrIdx_ = (tzCurrIdx_ == 0) ? tzSize - 1 : tzCurrIdx_ - 1;
|
||||||
}
|
}
|
||||||
|
void waybar::modules::Clock::action_exec(const std::string& action) {
|
||||||
|
auto cmd = action.substr(strlen("exec "));
|
||||||
|
pid_children_.push_back(util::command::forkExec(cmd));
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef HAVE_LANGINFO_1STDAY
|
#ifdef HAVE_LANGINFO_1STDAY
|
||||||
template <auto fn>
|
template <auto fn>
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
#include "modules/cpu_graph.hpp"
|
||||||
|
|
||||||
|
#include "modules/cpu_frequency.hpp"
|
||||||
|
#include "modules/cpu_usage.hpp"
|
||||||
|
#include "modules/load.hpp"
|
||||||
|
|
||||||
|
// In the 80000 version of fmt library authors decided to optimize imports
|
||||||
|
// and moved declarations required for fmt::dynamic_format_arg_store in new
|
||||||
|
// header fmt/args.h
|
||||||
|
#if (FMT_VERSION >= 80000)
|
||||||
|
#include <fmt/args.h>
|
||||||
|
#else
|
||||||
|
#include <fmt/core.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
waybar::modules::CpuGraph::CpuGraph(const std::string& id, const Json::Value& config)
|
||||||
|
: AGraph(config, "cpu_graph", id, 5) {
|
||||||
|
thread_ = [this] {
|
||||||
|
dp.emit();
|
||||||
|
thread_.sleep_for(interval_);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto waybar::modules::CpuGraph::update() -> void {
|
||||||
|
// TODO: as creating dynamic fmt::arg arrays is buggy we have to calc both
|
||||||
|
auto [cpu_usage, tooltip] = CpuUsage::getCpuUsage(prev_times_);
|
||||||
|
if (tooltipEnabled()) {
|
||||||
|
graph_.set_tooltip_text(tooltip);
|
||||||
|
}
|
||||||
|
auto total_usage = cpu_usage.empty() ? 0 : cpu_usage[0];
|
||||||
|
addValue(total_usage);
|
||||||
|
|
||||||
|
graph_.get_style_context()->remove_class(MODERATE_CLASS);
|
||||||
|
graph_.get_style_context()->remove_class(HIGH_CLASS);
|
||||||
|
graph_.get_style_context()->remove_class(INTENSIVE_CLASS);
|
||||||
|
|
||||||
|
if (total_usage > 90) {
|
||||||
|
graph_.get_style_context()->add_class(INTENSIVE_CLASS);
|
||||||
|
} else if (total_usage > 70) {
|
||||||
|
graph_.get_style_context()->add_class(HIGH_CLASS);
|
||||||
|
} else if (total_usage > 30) {
|
||||||
|
graph_.get_style_context()->add_class(MODERATE_CLASS);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call parent update
|
||||||
|
AGraph::update();
|
||||||
|
}
|
||||||
+26
-3
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
waybar::modules::Custom::Custom(const std::string& name, const std::string& id,
|
waybar::modules::Custom::Custom(const std::string& name, const std::string& id,
|
||||||
const Json::Value& config, const std::string& output_name)
|
const Json::Value& config, const std::string& output_name)
|
||||||
: ALabel(config, "custom-" + name, id, "{}"),
|
: AIconLabel(config, "custom-" + name, id, "{}"),
|
||||||
name_(name),
|
name_(name),
|
||||||
output_name_(output_name),
|
output_name_(output_name),
|
||||||
id_(id),
|
id_(id),
|
||||||
@@ -28,6 +28,15 @@ waybar::modules::Custom::Custom(const std::string& name, const std::string& id,
|
|||||||
} else if (config_["exec"].isString()) {
|
} else if (config_["exec"].isString()) {
|
||||||
continuousWorker();
|
continuousWorker();
|
||||||
}
|
}
|
||||||
|
if (config_["image-path"].isString()) {
|
||||||
|
image_path_ = config_["image-path"].asString();
|
||||||
|
}
|
||||||
|
if (config_["image-name"].isString()) {
|
||||||
|
image_name_ = config_["image-name"].asString();
|
||||||
|
}
|
||||||
|
if (config["icon-size"].isUInt()) {
|
||||||
|
app_icon_size_ = config["icon-size"].asUInt();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
waybar::modules::Custom::~Custom() {
|
waybar::modules::Custom::~Custom() {
|
||||||
@@ -184,7 +193,8 @@ auto waybar::modules::Custom::update() -> void {
|
|||||||
auto str = fmt::format(fmt::runtime(format_), fmt::arg("text", text_), fmt::arg("alt", alt_),
|
auto str = fmt::format(fmt::runtime(format_), fmt::arg("text", text_), fmt::arg("alt", alt_),
|
||||||
fmt::arg("icon", getIcon(percentage_, alt_)),
|
fmt::arg("icon", getIcon(percentage_, alt_)),
|
||||||
fmt::arg("percentage", percentage_));
|
fmt::arg("percentage", percentage_));
|
||||||
if ((config_["hide-empty-text"].asBool() && text_.empty()) || str.empty()) {
|
if ((config_["hide-empty-text"].asBool() && text_.empty()) ||
|
||||||
|
(str.empty() && image_path_.empty() && image_name_.empty())) {
|
||||||
event_box_.hide();
|
event_box_.hide();
|
||||||
} else {
|
} else {
|
||||||
setLabelMarkup(str);
|
setLabelMarkup(str);
|
||||||
@@ -216,7 +226,19 @@ auto waybar::modules::Custom::update() -> void {
|
|||||||
style->add_class("flat");
|
style->add_class("flat");
|
||||||
style->add_class("text-button");
|
style->add_class("text-button");
|
||||||
style->add_class(MODULE_CLASS);
|
style->add_class(MODULE_CLASS);
|
||||||
|
auto image_style = image_.get_style_context();
|
||||||
|
image_style->add_class("image-button");
|
||||||
event_box_.show();
|
event_box_.show();
|
||||||
|
if (!image_path_.empty()) {
|
||||||
|
auto pixbuf = Gdk::Pixbuf::create_from_file(image_path_, app_icon_size_, app_icon_size_);
|
||||||
|
image_.set(pixbuf);
|
||||||
|
} else if (!image_name_.empty()) {
|
||||||
|
image_.set_from_icon_name(image_name_, Gtk::ICON_SIZE_INVALID);
|
||||||
|
image_.set_pixel_size(app_icon_size_);
|
||||||
|
}
|
||||||
|
|
||||||
|
image_.set_visible(!image_name_.empty() || !image_path_.empty());
|
||||||
|
label_.set_visible(!str.empty());
|
||||||
}
|
}
|
||||||
} catch (const fmt::format_error& e) {
|
} catch (const fmt::format_error& e) {
|
||||||
if (std::strcmp(e.what(), "cannot switch from manual to automatic argument indexing") != 0)
|
if (std::strcmp(e.what(), "cannot switch from manual to automatic argument indexing") != 0)
|
||||||
@@ -228,7 +250,7 @@ auto waybar::modules::Custom::update() -> void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Call parent update
|
// Call parent update
|
||||||
ALabel::update();
|
AIconLabel::update();
|
||||||
}
|
}
|
||||||
|
|
||||||
void waybar::modules::Custom::parseOutputRaw() {
|
void waybar::modules::Custom::parseOutputRaw() {
|
||||||
@@ -294,6 +316,7 @@ void waybar::modules::Custom::parseOutputJson() {
|
|||||||
class_.push_back(c.asString());
|
class_.push_back(c.asString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!parsed["percentage"].asString().empty() && parsed["percentage"].isNumeric()) {
|
if (!parsed["percentage"].asString().empty() && parsed["percentage"].isNumeric()) {
|
||||||
percentage_ = (int)lround(parsed["percentage"].asFloat());
|
percentage_ = (int)lround(parsed["percentage"].asFloat());
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -0,0 +1,281 @@
|
|||||||
|
#include "modules/custom_graph.hpp"
|
||||||
|
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
|
#include "util/scope_guard.hpp"
|
||||||
|
|
||||||
|
waybar::modules::CustomGraph::CustomGraph(const std::string& name, const std::string& id,
|
||||||
|
const Json::Value& config, const std::string& output_name)
|
||||||
|
: AGraph(config, "custom-graph-" + name, id),
|
||||||
|
name_(name),
|
||||||
|
output_name_(output_name),
|
||||||
|
id_(id),
|
||||||
|
tooltip_format_enabled_{config_["tooltip-format"].isString()},
|
||||||
|
percentage_(0),
|
||||||
|
fp_(nullptr),
|
||||||
|
pid_(-1) {
|
||||||
|
if (config.isNull()) {
|
||||||
|
spdlog::warn("There is no configuration for 'custom-graph/{}', element will be hidden", name);
|
||||||
|
}
|
||||||
|
dp.emit();
|
||||||
|
if (!config_["signal"].empty() && config_["interval"].empty() &&
|
||||||
|
config_["restart-interval"].empty()) {
|
||||||
|
waitingWorker();
|
||||||
|
} else if (interval_.count() > 0) {
|
||||||
|
delayWorker();
|
||||||
|
} else if (config_["exec"].isString()) {
|
||||||
|
continuousWorker();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
waybar::modules::CustomGraph::~CustomGraph() {
|
||||||
|
if (pid_ != -1) {
|
||||||
|
killpg(pid_, SIGTERM);
|
||||||
|
waitpid(pid_, NULL, 0);
|
||||||
|
pid_ = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void waybar::modules::CustomGraph::delayWorker() {
|
||||||
|
thread_ = [this] {
|
||||||
|
for (int i : this->pid_children_) {
|
||||||
|
int status;
|
||||||
|
waitpid(i, &status, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
this->pid_children_.clear();
|
||||||
|
|
||||||
|
bool can_update = true;
|
||||||
|
if (config_["exec-if"].isString()) {
|
||||||
|
output_ = util::command::execNoRead(config_["exec-if"].asString());
|
||||||
|
if (output_.exit_code != 0) {
|
||||||
|
can_update = false;
|
||||||
|
dp.emit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (can_update) {
|
||||||
|
if (config_["exec"].isString()) {
|
||||||
|
output_ = util::command::exec(config_["exec"].asString(), output_name_);
|
||||||
|
}
|
||||||
|
dp.emit();
|
||||||
|
}
|
||||||
|
thread_.sleep_for(interval_);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void waybar::modules::CustomGraph::continuousWorker() {
|
||||||
|
auto cmd = config_["exec"].asString();
|
||||||
|
pid_ = -1;
|
||||||
|
fp_ = util::command::open(cmd, pid_, output_name_);
|
||||||
|
if (!fp_) {
|
||||||
|
throw std::runtime_error("Unable to open " + cmd);
|
||||||
|
}
|
||||||
|
thread_ = [this, cmd] {
|
||||||
|
char* buff = nullptr;
|
||||||
|
waybar::util::ScopeGuard buff_deleter([&buff]() {
|
||||||
|
if (buff) {
|
||||||
|
free(buff);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
size_t len = 0;
|
||||||
|
if (getline(&buff, &len, fp_) == -1) {
|
||||||
|
int exit_code = 1;
|
||||||
|
if (fp_) {
|
||||||
|
exit_code = WEXITSTATUS(util::command::close(fp_, pid_));
|
||||||
|
fp_ = nullptr;
|
||||||
|
}
|
||||||
|
if (exit_code != 0) {
|
||||||
|
output_ = {exit_code, ""};
|
||||||
|
dp.emit();
|
||||||
|
spdlog::error("{} stopped unexpectedly, is it endless?", name_);
|
||||||
|
}
|
||||||
|
if (config_["restart-interval"].isUInt()) {
|
||||||
|
pid_ = -1;
|
||||||
|
thread_.sleep_for(std::chrono::seconds(config_["restart-interval"].asUInt()));
|
||||||
|
fp_ = util::command::open(cmd, pid_, output_name_);
|
||||||
|
if (!fp_) {
|
||||||
|
throw std::runtime_error("Unable to open " + cmd);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
thread_.stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
std::string output = buff;
|
||||||
|
|
||||||
|
// Remove last newline
|
||||||
|
if (!output.empty() && output[output.length() - 1] == '\n') {
|
||||||
|
output.erase(output.length() - 1);
|
||||||
|
}
|
||||||
|
output_ = {0, output};
|
||||||
|
dp.emit();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void waybar::modules::CustomGraph::waitingWorker() {
|
||||||
|
thread_ = [this] {
|
||||||
|
bool can_update = true;
|
||||||
|
if (config_["exec-if"].isString()) {
|
||||||
|
output_ = util::command::execNoRead(config_["exec-if"].asString());
|
||||||
|
if (output_.exit_code != 0) {
|
||||||
|
can_update = false;
|
||||||
|
dp.emit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (can_update) {
|
||||||
|
if (config_["exec"].isString()) {
|
||||||
|
output_ = util::command::exec(config_["exec"].asString(), output_name_);
|
||||||
|
}
|
||||||
|
dp.emit();
|
||||||
|
}
|
||||||
|
thread_.sleep();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void waybar::modules::CustomGraph::refresh(int sig) {
|
||||||
|
if (sig == SIGRTMIN + config_["signal"].asInt()) {
|
||||||
|
thread_.wake_up();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void waybar::modules::CustomGraph::handleEvent() {
|
||||||
|
if (!config_["exec-on-event"].isBool() || config_["exec-on-event"].asBool()) {
|
||||||
|
thread_.wake_up();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool waybar::modules::CustomGraph::handleScroll(GdkEventScroll* e) {
|
||||||
|
auto ret = AGraph::handleScroll(e);
|
||||||
|
handleEvent();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool waybar::modules::CustomGraph::handleToggle(GdkEventButton* const& e) {
|
||||||
|
auto ret = AGraph::handleToggle(e);
|
||||||
|
handleEvent();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto waybar::modules::CustomGraph::update() -> void {
|
||||||
|
// Hide label if output is empty
|
||||||
|
if ((config_["exec"].isString() || config_["exec-if"].isString()) &&
|
||||||
|
(output_.out.empty() || output_.exit_code != 0)) {
|
||||||
|
event_box_.hide();
|
||||||
|
} else {
|
||||||
|
if (config_["return-type"].asString() == "json") {
|
||||||
|
parseOutputJson();
|
||||||
|
} else {
|
||||||
|
parseOutputRaw();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
addValue(percentage_);
|
||||||
|
|
||||||
|
if (tooltipEnabled()) {
|
||||||
|
if (tooltip_format_enabled_) {
|
||||||
|
auto tooltip = config_["tooltip-format"].asString();
|
||||||
|
tooltip = fmt::format(fmt::runtime(tooltip), fmt::arg("text", text_),
|
||||||
|
fmt::arg("alt", alt_), fmt::arg("percentage", percentage_));
|
||||||
|
graph_.set_tooltip_markup(tooltip);
|
||||||
|
} else {
|
||||||
|
if (graph_.get_tooltip_markup() != tooltip_) {
|
||||||
|
graph_.set_tooltip_markup(tooltip_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
auto style = graph_.get_style_context();
|
||||||
|
auto classes = style->list_classes();
|
||||||
|
for (auto const& c : classes) {
|
||||||
|
if (c == id_) continue;
|
||||||
|
style->remove_class(c);
|
||||||
|
}
|
||||||
|
for (auto const& c : class_) {
|
||||||
|
style->add_class(c);
|
||||||
|
}
|
||||||
|
style->add_class("flat");
|
||||||
|
style->add_class(MODULE_CLASS);
|
||||||
|
event_box_.show();
|
||||||
|
} catch (const fmt::format_error& e) {
|
||||||
|
if (std::strcmp(e.what(), "cannot switch from manual to automatic argument indexing") != 0)
|
||||||
|
throw;
|
||||||
|
|
||||||
|
throw fmt::format_error(
|
||||||
|
"mixing manual and automatic argument indexing is no longer supported; "
|
||||||
|
"try replacing \"{}\" with \"{text}\" in your format specifier");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Call parent update
|
||||||
|
AGraph::update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void waybar::modules::CustomGraph::parseOutputRaw() {
|
||||||
|
std::istringstream output(output_.out);
|
||||||
|
std::string line;
|
||||||
|
int i = 0;
|
||||||
|
while (getline(output, line)) {
|
||||||
|
Glib::ustring validated_line = line;
|
||||||
|
if (!validated_line.validate()) {
|
||||||
|
validated_line = validated_line.make_valid();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i == 0) {
|
||||||
|
if (config_["escape"].isBool() && config_["escape"].asBool()) {
|
||||||
|
text_ = Glib::Markup::escape_text(validated_line);
|
||||||
|
tooltip_ = Glib::Markup::escape_text(validated_line);
|
||||||
|
} else {
|
||||||
|
text_ = validated_line;
|
||||||
|
tooltip_ = validated_line;
|
||||||
|
}
|
||||||
|
class_.clear();
|
||||||
|
} else if (i == 1) {
|
||||||
|
if (config_["escape"].isBool() && config_["escape"].asBool()) {
|
||||||
|
tooltip_ = Glib::Markup::escape_text(validated_line);
|
||||||
|
} else {
|
||||||
|
tooltip_ = validated_line;
|
||||||
|
}
|
||||||
|
} else if (i == 2) {
|
||||||
|
class_.push_back(validated_line);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void waybar::modules::CustomGraph::parseOutputJson() {
|
||||||
|
std::istringstream output(output_.out);
|
||||||
|
std::string line;
|
||||||
|
class_.clear();
|
||||||
|
while (getline(output, line)) {
|
||||||
|
auto parsed = parser_.parse(line);
|
||||||
|
if (config_["escape"].isBool() && config_["escape"].asBool()) {
|
||||||
|
text_ = Glib::Markup::escape_text(parsed["text"].asString());
|
||||||
|
} else {
|
||||||
|
text_ = parsed["text"].asString();
|
||||||
|
}
|
||||||
|
if (config_["escape"].isBool() && config_["escape"].asBool()) {
|
||||||
|
alt_ = Glib::Markup::escape_text(parsed["alt"].asString());
|
||||||
|
} else {
|
||||||
|
alt_ = parsed["alt"].asString();
|
||||||
|
}
|
||||||
|
if (config_["escape"].isBool() && config_["escape"].asBool()) {
|
||||||
|
tooltip_ = Glib::Markup::escape_text(parsed["tooltip"].asString());
|
||||||
|
} else {
|
||||||
|
tooltip_ = parsed["tooltip"].asString();
|
||||||
|
}
|
||||||
|
if (parsed["class"].isString()) {
|
||||||
|
class_.push_back(parsed["class"].asString());
|
||||||
|
} else if (parsed["class"].isArray()) {
|
||||||
|
for (auto const& c : parsed["class"]) {
|
||||||
|
class_.push_back(c.asString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!parsed["percentage"].asString().empty() && parsed["percentage"].isNumeric()) {
|
||||||
|
percentage_ = (int)lround(parsed["percentage"].asFloat());
|
||||||
|
} else {
|
||||||
|
percentage_ = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
|
#include <glibmm/main.h>
|
||||||
#include <json/value.h>
|
#include <json/value.h>
|
||||||
#include <spdlog/spdlog.h>
|
#include <spdlog/spdlog.h>
|
||||||
#include <glibmm/main.h>
|
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -152,8 +153,7 @@ bool Workspace::pointerInsideButton() {
|
|||||||
const int buttonHeight = allocation.get_height();
|
const int buttonHeight = allocation.get_height();
|
||||||
|
|
||||||
return pointerRootX >= buttonRootX && pointerRootY >= buttonRootY &&
|
return pointerRootX >= buttonRootX && pointerRootY >= buttonRootY &&
|
||||||
pointerRootX < buttonRootX + buttonWidth &&
|
pointerRootX < buttonRootX + buttonWidth && pointerRootY < buttonRootY + buttonHeight;
|
||||||
pointerRootY < buttonRootY + buttonHeight;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Workspace::syncHoverClass() {
|
bool Workspace::syncHoverClass() {
|
||||||
@@ -174,9 +174,8 @@ void Workspace::startHoverCheck() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_hoverCheckConnection = Glib::signal_timeout().connect(
|
m_hoverCheckConnection =
|
||||||
sigc::mem_fun(*this, &Workspace::syncHoverClass),
|
Glib::signal_timeout().connect(sigc::mem_fun(*this, &Workspace::syncHoverClass), 50);
|
||||||
50);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Workspace::stopHoverCheck() {
|
void Workspace::stopHoverCheck() {
|
||||||
@@ -252,8 +251,7 @@ void Workspace::setActiveWindow(WindowAddress const& addr) {
|
|||||||
|
|
||||||
auto activeWindowPos = m_workspaceManager.activeWindowPosition();
|
auto activeWindowPos = m_workspaceManager.activeWindowPosition();
|
||||||
const bool has_active_window =
|
const bool has_active_window =
|
||||||
activeIdx.has_value() &&
|
activeIdx.has_value() && activeWindowPos != Workspaces::ActiveWindowPosition::NONE;
|
||||||
activeWindowPos != Workspaces::ActiveWindowPosition::NONE;
|
|
||||||
|
|
||||||
if (has_active_window) {
|
if (has_active_window) {
|
||||||
auto window = std::move(m_windowMap[*activeIdx]);
|
auto window = std::move(m_windowMap[*activeIdx]);
|
||||||
@@ -270,8 +268,7 @@ 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);
|
||||||
|
|
||||||
const bool should_display =
|
const bool should_display = !repr.empty() || m_workspaceManager.enableTaskbar();
|
||||||
!repr.empty() || m_workspaceManager.enableTaskbar();
|
|
||||||
|
|
||||||
if (should_display) {
|
if (should_display) {
|
||||||
auto addr = create_window_payload.getAddress();
|
auto addr = create_window_payload.getAddress();
|
||||||
@@ -378,7 +375,6 @@ std::string& Workspace::selectIcon(std::map<std::string, std::string>& icons_map
|
|||||||
return m_name;
|
return m_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void Workspace::update(const std::string& workspace_icon) {
|
void Workspace::update(const std::string& workspace_icon) {
|
||||||
if (this->m_workspaceManager.persistentOnly() && !this->isPersistent()) {
|
if (this->m_workspaceManager.persistentOnly() && !this->isPersistent()) {
|
||||||
m_button.hide();
|
m_button.hide();
|
||||||
@@ -436,18 +432,58 @@ void Workspace::update(const std::string& workspace_icon) {
|
|||||||
// need to compute this if enableTaskbar() is true
|
// need to compute this if enableTaskbar() is true
|
||||||
if (!m_workspaceManager.enableTaskbar()) {
|
if (!m_workspaceManager.enableTaskbar()) {
|
||||||
auto windowSeparator = m_workspaceManager.getWindowSeparator();
|
auto windowSeparator = m_workspaceManager.getWindowSeparator();
|
||||||
|
auto groupThreshold = m_workspaceManager.windowRewriteGroupThreshold();
|
||||||
|
|
||||||
bool isNotFirst = false;
|
auto end_it = m_workspaceManager.maxWindows() == 0
|
||||||
auto end_it = m_workspaceManager.maxWindows() == 0 ? m_windowMap.end() : m_windowMap.begin() + m_workspaceManager.maxWindows();
|
? m_windowMap.end()
|
||||||
|
: m_windowMap.begin() + m_workspaceManager.maxWindows();
|
||||||
|
|
||||||
|
if (groupThreshold > 0) {
|
||||||
|
// Build ordered counts of each unique icon (including singular ones when threshold set to 1)
|
||||||
|
std::vector<std::pair<std::string, int>> iconCounts;
|
||||||
for (auto it = m_windowMap.begin(); it != end_it; ++it) {
|
for (auto it = m_windowMap.begin(); it != end_it; ++it) {
|
||||||
if (isNotFirst) {
|
const auto& window_repr = *it;
|
||||||
windows.append(windowSeparator);
|
auto found = std::ranges::find_if(
|
||||||
|
iconCounts, [&](const auto& p) { return p.first == window_repr.repr_rewrite; });
|
||||||
|
if (found != iconCounts.end()) {
|
||||||
|
found->second++;
|
||||||
|
} else {
|
||||||
|
iconCounts.emplace_back(window_repr.repr_rewrite, 1);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format the group string
|
||||||
|
auto groupFormat = m_workspaceManager.getWindowRewriteGroupFormat();
|
||||||
|
bool isNotFirst = false;
|
||||||
|
for (const auto& [icon, count] : iconCounts) {
|
||||||
|
if (count >= groupThreshold) {
|
||||||
|
if (isNotFirst) windows.append(windowSeparator);
|
||||||
|
isNotFirst = true;
|
||||||
|
try {
|
||||||
|
windows.append(fmt::format(fmt::runtime(groupFormat), fmt::arg("icon", icon),
|
||||||
|
fmt::arg("count", count)));
|
||||||
|
} catch (const fmt::format_error& e) {
|
||||||
|
spdlog::warn("Formatting window-rewrite-group-format error: {}", e.what());
|
||||||
|
windows.append(icon);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (int i = 0; i < count; ++i) {
|
||||||
|
if (isNotFirst) windows.append(windowSeparator);
|
||||||
|
isNotFirst = true;
|
||||||
|
windows.append(icon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Not grouping icons
|
||||||
|
bool isNotFirst = false;
|
||||||
|
for (auto it = m_windowMap.begin(); it != end_it; ++it) {
|
||||||
|
if (isNotFirst) windows.append(windowSeparator);
|
||||||
isNotFirst = true;
|
isNotFirst = true;
|
||||||
windows.append(it->repr_rewrite);
|
windows.append(it->repr_rewrite);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
auto formatBefore = m_workspaceManager.formatBefore();
|
auto formatBefore = m_workspaceManager.formatBefore();
|
||||||
m_labelBefore.set_markup(fmt::format(fmt::runtime(formatBefore), fmt::arg("id", id()),
|
m_labelBefore.set_markup(fmt::format(fmt::runtime(formatBefore), fmt::arg("id", id()),
|
||||||
@@ -505,8 +541,7 @@ void Workspace::updateTaskbar(const std::string& workspace_icon) {
|
|||||||
}
|
}
|
||||||
if (m_workspaceManager.onClickWindow() != "") {
|
if (m_workspaceManager.onClickWindow() != "") {
|
||||||
button->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);
|
||||||
false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto text_before = fmt::format(fmt::runtime(m_workspaceManager.taskbarFormatBefore()),
|
auto text_before = fmt::format(fmt::runtime(m_workspaceManager.taskbarFormatBefore()),
|
||||||
@@ -536,13 +571,17 @@ void Workspace::updateTaskbar(const std::string& workspace_icon) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (m_workspaceManager.taskbarReverseDirection()) {
|
if (m_workspaceManager.taskbarReverseDirection()) {
|
||||||
auto rend_it = m_workspaceManager.maxWindows() == 0 ? m_windowMap.rend() : m_windowMap.rbegin() + m_workspaceManager.maxWindows();
|
auto rend_it = m_workspaceManager.maxWindows() == 0
|
||||||
|
? m_windowMap.rend()
|
||||||
|
: m_windowMap.rbegin() + m_workspaceManager.maxWindows();
|
||||||
|
|
||||||
for (auto it = m_windowMap.rbegin(); it != rend_it; ++it) {
|
for (auto it = m_windowMap.rbegin(); it != rend_it; ++it) {
|
||||||
processWindow(*it);
|
processWindow(*it);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
auto end_it = m_workspaceManager.maxWindows() == 0 ? m_windowMap.end() : m_windowMap.begin() + m_workspaceManager.maxWindows();
|
auto end_it = m_workspaceManager.maxWindows() == 0
|
||||||
|
? m_windowMap.end()
|
||||||
|
: m_windowMap.begin() + m_workspaceManager.maxWindows();
|
||||||
|
|
||||||
for (auto it = m_windowMap.begin(); it != end_it; ++it) {
|
for (auto it = m_windowMap.begin(); it != end_it; ++it) {
|
||||||
processWindow(*it);
|
processWindow(*it);
|
||||||
|
|||||||
@@ -670,6 +670,16 @@ auto Workspaces::parseConfig(const Json::Value& config) -> void {
|
|||||||
populateSortByConfig(config);
|
populateSortByConfig(config);
|
||||||
populateIgnoreWorkspacesConfig(config);
|
populateIgnoreWorkspacesConfig(config);
|
||||||
populateFormatWindowSeparatorConfig(config);
|
populateFormatWindowSeparatorConfig(config);
|
||||||
|
|
||||||
|
const auto& groupThreshold = config["window-rewrite-group-threshold"];
|
||||||
|
if (groupThreshold.isInt()) {
|
||||||
|
m_windowRewriteGroupThreshold = groupThreshold.asInt();
|
||||||
|
}
|
||||||
|
const auto& groupFormat = config["window-rewrite-group-format"];
|
||||||
|
if (groupFormat.isString()) {
|
||||||
|
m_windowRewriteGroupFormat = groupFormat.asString();
|
||||||
|
}
|
||||||
|
|
||||||
populateWindowRewriteConfig(config);
|
populateWindowRewriteConfig(config);
|
||||||
populateMaxWindowsConfig(config);
|
populateMaxWindowsConfig(config);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "modules/idle_inhibitor.hpp"
|
#include "modules/idle_inhibitor.hpp"
|
||||||
|
|
||||||
|
#include "ext-idle-notify-v1-client-protocol.h"
|
||||||
#include "idle-inhibit-unstable-v1-client-protocol.h"
|
#include "idle-inhibit-unstable-v1-client-protocol.h"
|
||||||
#include "util/command.hpp"
|
#include "util/command.hpp"
|
||||||
|
|
||||||
@@ -11,11 +12,24 @@ waybar::modules::IdleInhibitor::IdleInhibitor(const std::string& id, const Bar&
|
|||||||
: ALabel(config, "idle_inhibitor", id, "{status}", 0, false, true),
|
: ALabel(config, "idle_inhibitor", id, "{status}", 0, false, true),
|
||||||
bar_(bar),
|
bar_(bar),
|
||||||
idle_inhibitor_(nullptr),
|
idle_inhibitor_(nullptr),
|
||||||
pid_(-1) {
|
idle_notification_(nullptr),
|
||||||
|
idle_timeout_ms_(0),
|
||||||
|
pid_(-1),
|
||||||
|
wait_for_activity_(false) {
|
||||||
if (waybar::Client::inst()->idle_inhibit_manager == nullptr) {
|
if (waybar::Client::inst()->idle_inhibit_manager == nullptr) {
|
||||||
throw std::runtime_error("idle-inhibit not available");
|
throw std::runtime_error("idle-inhibit not available");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read the wait-for-activity config option
|
||||||
|
if (config_["wait-for-activity"].isBool()) {
|
||||||
|
wait_for_activity_ = config_["wait-for-activity"].asBool();
|
||||||
|
|
||||||
|
// Check if ext-idle-notify protocol is available when wait-for-activity is enabled
|
||||||
|
if (wait_for_activity_ && waybar::Client::inst()->idle_notifier == nullptr) {
|
||||||
|
throw std::runtime_error("wait-for-activity requires ext-idle-notify-v1 protocol support");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (waybar::modules::IdleInhibitor::modules.empty() && config_["start-activated"].isBool() &&
|
if (waybar::modules::IdleInhibitor::modules.empty() && config_["start-activated"].isBool() &&
|
||||||
config_["start-activated"].asBool() != status) {
|
config_["start-activated"].asBool() != status) {
|
||||||
toggleStatus();
|
toggleStatus();
|
||||||
@@ -32,6 +46,8 @@ waybar::modules::IdleInhibitor::IdleInhibitor(const std::string& id, const Bar&
|
|||||||
}
|
}
|
||||||
|
|
||||||
waybar::modules::IdleInhibitor::~IdleInhibitor() {
|
waybar::modules::IdleInhibitor::~IdleInhibitor() {
|
||||||
|
teardownIdleNotification();
|
||||||
|
|
||||||
if (idle_inhibitor_ != nullptr) {
|
if (idle_inhibitor_ != nullptr) {
|
||||||
zwp_idle_inhibitor_v1_destroy(idle_inhibitor_);
|
zwp_idle_inhibitor_v1_destroy(idle_inhibitor_);
|
||||||
idle_inhibitor_ = nullptr;
|
idle_inhibitor_ = nullptr;
|
||||||
@@ -70,6 +86,17 @@ auto waybar::modules::IdleInhibitor::update() -> void {
|
|||||||
ALabel::update();
|
ALabel::update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto waybar::modules::IdleInhibitor::refresh(int sig) -> void {
|
||||||
|
if (config_["signal"].isInt() && sig == SIGRTMIN + config_["signal"].asInt()) {
|
||||||
|
toggleStatus();
|
||||||
|
|
||||||
|
// Make all other idle inhibitor modules update
|
||||||
|
for (auto const& module : waybar::modules::IdleInhibitor::modules) {
|
||||||
|
module->update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void waybar::modules::IdleInhibitor::toggleStatus() {
|
void waybar::modules::IdleInhibitor::toggleStatus() {
|
||||||
status = !status;
|
status = !status;
|
||||||
|
|
||||||
@@ -81,7 +108,16 @@ void waybar::modules::IdleInhibitor::toggleStatus() {
|
|||||||
if (status && config_["timeout"].isNumeric()) {
|
if (status && config_["timeout"].isNumeric()) {
|
||||||
auto timeoutMins = config_["timeout"].asDouble();
|
auto timeoutMins = config_["timeout"].asDouble();
|
||||||
int timeoutSecs = timeoutMins * 60;
|
int timeoutSecs = timeoutMins * 60;
|
||||||
|
idle_timeout_ms_ = timeoutSecs * 1000;
|
||||||
|
|
||||||
|
// If wait-for-activity is enabled, set up idle notification
|
||||||
|
if (wait_for_activity_) {
|
||||||
|
spdlog::debug("idle_inhibitor: wait-for-activity enabled, timeout: {} ms", idle_timeout_ms_);
|
||||||
|
// Tear down any existing notification first to ensure fresh setup
|
||||||
|
teardownIdleNotification();
|
||||||
|
setupIdleNotification();
|
||||||
|
} else {
|
||||||
|
// Original behavior: simple timeout
|
||||||
timeout_ = Glib::signal_timeout().connect_seconds(
|
timeout_ = Glib::signal_timeout().connect_seconds(
|
||||||
[]() {
|
[]() {
|
||||||
/* intentionally not tied to a module instance lifetime
|
/* intentionally not tied to a module instance lifetime
|
||||||
@@ -97,6 +133,10 @@ void waybar::modules::IdleInhibitor::toggleStatus() {
|
|||||||
},
|
},
|
||||||
timeoutSecs);
|
timeoutSecs);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// When deactivated, tear down idle notification
|
||||||
|
teardownIdleNotification();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool waybar::modules::IdleInhibitor::handleToggle(GdkEventButton* const& e) {
|
bool waybar::modules::IdleInhibitor::handleToggle(GdkEventButton* const& e) {
|
||||||
@@ -114,3 +154,96 @@ bool waybar::modules::IdleInhibitor::handleToggle(GdkEventButton* const& e) {
|
|||||||
ALabel::handleToggle(e);
|
ALabel::handleToggle(e);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void waybar::modules::IdleInhibitor::handleIdled(void* data,
|
||||||
|
ext_idle_notification_v1* /*notification*/) {
|
||||||
|
spdlog::info("deactivating idle_inhibitor due to user inactivity");
|
||||||
|
status = false;
|
||||||
|
|
||||||
|
// Clean up the notification since we're deactivating
|
||||||
|
auto* self = static_cast<IdleInhibitor*>(data);
|
||||||
|
if (self != nullptr) {
|
||||||
|
self->teardownIdleNotification();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto const& module : waybar::modules::IdleInhibitor::modules) {
|
||||||
|
module->update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void waybar::modules::IdleInhibitor::handleResumed(void* data,
|
||||||
|
ext_idle_notification_v1* /*notification*/) {
|
||||||
|
// User became active again - notification will continue monitoring
|
||||||
|
spdlog::debug("user activity detected, idle_inhibitor still active");
|
||||||
|
}
|
||||||
|
|
||||||
|
void waybar::modules::IdleInhibitor::setupIdleNotification() {
|
||||||
|
spdlog::debug("idle_inhibitor: setting up idle notification");
|
||||||
|
|
||||||
|
// Clean up any existing notification first
|
||||||
|
if (idle_notification_ != nullptr) {
|
||||||
|
spdlog::debug("idle_inhibitor: cleaning up existing notification before setup");
|
||||||
|
teardownIdleNotification();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* client = waybar::Client::inst();
|
||||||
|
if (client->idle_notifier == nullptr) {
|
||||||
|
spdlog::error("ext-idle-notify protocol not available");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the wayland seat from the display
|
||||||
|
auto* gdk_seat = gdk_display_get_default_seat(client->gdk_display->gobj());
|
||||||
|
if (gdk_seat == nullptr) {
|
||||||
|
spdlog::error("failed to get default seat");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto* wl_seat = gdk_wayland_seat_get_wl_seat(gdk_seat);
|
||||||
|
|
||||||
|
// Check protocol version to determine which function to use
|
||||||
|
uint32_t version =
|
||||||
|
wl_proxy_get_version(reinterpret_cast<struct wl_proxy*>(client->idle_notifier));
|
||||||
|
|
||||||
|
spdlog::debug("idle_inhibitor: creating notification with timeout {} ms (protocol version {})",
|
||||||
|
idle_timeout_ms_, version);
|
||||||
|
|
||||||
|
if (version >= 2) {
|
||||||
|
// Version 2+: Use get_input_idle_notification which ignores idle inhibitors
|
||||||
|
// This allows us to detect actual user inactivity even while the inhibitor is active
|
||||||
|
spdlog::debug("idle_inhibitor: using get_input_idle_notification (ignores inhibitors)");
|
||||||
|
idle_notification_ = ext_idle_notifier_v1_get_input_idle_notification(
|
||||||
|
client->idle_notifier, idle_timeout_ms_, wl_seat);
|
||||||
|
} else {
|
||||||
|
// Version 1: Fall back to get_idle_notification
|
||||||
|
// WARNING: This respects idle inhibitors, so it won't fire while inhibitor is active
|
||||||
|
spdlog::warn(
|
||||||
|
"idle_inhibitor: ext-idle-notifier-v1 version {} doesn't support "
|
||||||
|
"get_input_idle_notification, "
|
||||||
|
"wait-for-activity may not work correctly",
|
||||||
|
version);
|
||||||
|
idle_notification_ = ext_idle_notifier_v1_get_idle_notification(client->idle_notifier,
|
||||||
|
idle_timeout_ms_, wl_seat);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (idle_notification_ == nullptr) {
|
||||||
|
spdlog::error("idle_inhibitor: failed to create idle notification");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct ext_idle_notification_v1_listener idle_notification_listener = {
|
||||||
|
.idled = &IdleInhibitor::handleIdled,
|
||||||
|
.resumed = &IdleInhibitor::handleResumed,
|
||||||
|
};
|
||||||
|
|
||||||
|
ext_idle_notification_v1_add_listener(idle_notification_, &idle_notification_listener, this);
|
||||||
|
wl_display_roundtrip(client->wl_display);
|
||||||
|
spdlog::debug("idle_inhibitor: idle notification setup complete");
|
||||||
|
}
|
||||||
|
|
||||||
|
void waybar::modules::IdleInhibitor::teardownIdleNotification() {
|
||||||
|
if (idle_notification_ != nullptr) {
|
||||||
|
spdlog::debug("idle_inhibitor: tearing down idle notification");
|
||||||
|
ext_idle_notification_v1_destroy(idle_notification_);
|
||||||
|
idle_notification_ = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ waybar::modules::MPD::MPD(const std::string& id, const Json::Value& config)
|
|||||||
port_(config_["port"].isUInt() ? config["port"].asUInt() : 0),
|
port_(config_["port"].isUInt() ? config["port"].asUInt() : 0),
|
||||||
password_(config_["password"].empty() ? "" : config_["password"].asString()),
|
password_(config_["password"].empty() ? "" : config_["password"].asString()),
|
||||||
timeout_(config_["timeout"].isUInt() ? config_["timeout"].asUInt() * 1'000 : 30'000),
|
timeout_(config_["timeout"].isUInt() ? config_["timeout"].asUInt() * 1'000 : 30'000),
|
||||||
|
playing_interval_(config_["playing-interval"].isUInt() ? config_["playing-interval"].asUInt()
|
||||||
|
: 1'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),
|
||||||
@@ -35,6 +37,10 @@ waybar::modules::MPD::MPD(const std::string& id, const Json::Value& config)
|
|||||||
spdlog::warn("{}: `timeout` configuration should be an unsigned int", module_name_);
|
spdlog::warn("{}: `timeout` configuration should be an unsigned int", module_name_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!config_["playing-interval"].isNull() && !config_["playing-interval"].isUInt()) {
|
||||||
|
spdlog::warn("{}: `playing-interval` configuration should be an unsigned int", module_name_);
|
||||||
|
}
|
||||||
|
|
||||||
if (!config["server"].isNull()) {
|
if (!config["server"].isNull()) {
|
||||||
if (!config_["server"].isString()) {
|
if (!config_["server"].isString()) {
|
||||||
spdlog::warn("{}:`server` configuration should be a string", module_name_);
|
spdlog::warn("{}:`server` configuration should be a string", module_name_);
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ bool Idle::on_io(Glib::IOCondition const&) {
|
|||||||
void Playing::entry() noexcept {
|
void Playing::entry() noexcept {
|
||||||
timer();
|
timer();
|
||||||
idle();
|
idle();
|
||||||
spdlog::debug("mpd: Playing: enabled 1 second periodic timer.");
|
spdlog::debug("mpd: Playing: enabled {}ms periodic timer.", ctx_->playing_interval());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Playing::exit() noexcept {
|
void Playing::exit() noexcept {
|
||||||
@@ -126,7 +126,7 @@ void Playing::exit() noexcept {
|
|||||||
|
|
||||||
if (timer_connection_.connected()) {
|
if (timer_connection_.connected()) {
|
||||||
timer_connection_.disconnect();
|
timer_connection_.disconnect();
|
||||||
spdlog::debug("mpd: Playing: disabled 1 second periodic timer.");
|
spdlog::debug("mpd: Playing: disabled {}ms periodic timer.", ctx_->playing_interval());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,7 +136,7 @@ void Playing::timer() noexcept {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sigc::slot<bool> timer_slot = sigc::mem_fun(*this, &Playing::on_timer);
|
sigc::slot<bool> timer_slot = sigc::mem_fun(*this, &Playing::on_timer);
|
||||||
timer_connection_ = Glib::signal_timeout().connect_seconds(timer_slot, 1);
|
timer_connection_ = Glib::signal_timeout().connect(timer_slot, ctx_->playing_interval());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Playing::idle() noexcept {
|
void Playing::idle() noexcept {
|
||||||
|
|||||||
@@ -315,19 +315,26 @@ auto waybar::modules::Network::update() -> void {
|
|||||||
elapsed_seconds = std::chrono::duration<double>(interval_).count();
|
elapsed_seconds = std::chrono::duration<double>(interval_).count();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto threshold_state = getState(signal_strength_);
|
||||||
|
|
||||||
if (!alt_) {
|
if (!alt_) {
|
||||||
auto state = getNetworkState();
|
auto state = getNetworkState();
|
||||||
if (!state_.empty() && label_.get_style_context()->has_class(state_)) {
|
if (!state_.empty() && label_.get_style_context()->has_class(state_)) {
|
||||||
label_.get_style_context()->remove_class(state_);
|
label_.get_style_context()->remove_class(state_);
|
||||||
}
|
}
|
||||||
if (config_["format-" + state].isString()) {
|
if (!threshold_state.empty() && config_["format-" + state + "-" + threshold_state].isString()) {
|
||||||
|
default_format_ = config_["format-" + state + "-" + threshold_state].asString();
|
||||||
|
} else if (config_["format-" + state].isString()) {
|
||||||
default_format_ = config_["format-" + state].asString();
|
default_format_ = config_["format-" + state].asString();
|
||||||
} else if (config_["format"].isString()) {
|
} else if (config_["format"].isString()) {
|
||||||
default_format_ = config_["format"].asString();
|
default_format_ = config_["format"].asString();
|
||||||
} else {
|
} else {
|
||||||
default_format_ = DEFAULT_FORMAT;
|
default_format_ = DEFAULT_FORMAT;
|
||||||
}
|
}
|
||||||
if (config_["tooltip-format-" + state].isString()) {
|
if (!threshold_state.empty() &&
|
||||||
|
config_["tooltip-format-" + state + "-" + threshold_state].isString()) {
|
||||||
|
tooltip_format = config_["tooltip-format-" + state + "-" + threshold_state].asString();
|
||||||
|
} else if (config_["tooltip-format-" + state].isString()) {
|
||||||
tooltip_format = config_["tooltip-format-" + state].asString();
|
tooltip_format = config_["tooltip-format-" + state].asString();
|
||||||
}
|
}
|
||||||
if (!label_.get_style_context()->has_class(state)) {
|
if (!label_.get_style_context()->has_class(state)) {
|
||||||
@@ -336,7 +343,6 @@ auto waybar::modules::Network::update() -> void {
|
|||||||
format_ = default_format_;
|
format_ = default_format_;
|
||||||
state_ = state;
|
state_ = state;
|
||||||
}
|
}
|
||||||
getState(signal_strength_);
|
|
||||||
|
|
||||||
std::string final_ipaddr_;
|
std::string final_ipaddr_;
|
||||||
if (addr_pref_ == ip_addr_pref::IPV4) {
|
if (addr_pref_ == ip_addr_pref::IPV4) {
|
||||||
|
|||||||
@@ -46,6 +46,12 @@ Workspaces::Workspaces(const std::string& id, const Bar& bar, const Json::Value&
|
|||||||
gIPC->registerForIPC("WorkspaceActiveWindowChanged", this);
|
gIPC->registerForIPC("WorkspaceActiveWindowChanged", this);
|
||||||
gIPC->registerForIPC("WorkspaceUrgencyChanged", this);
|
gIPC->registerForIPC("WorkspaceUrgencyChanged", this);
|
||||||
|
|
||||||
|
if (config["enable-bar-scroll"].asBool()) {
|
||||||
|
auto& window = const_cast<Bar&>(bar_).window;
|
||||||
|
window.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);
|
||||||
|
window.signal_scroll_event().connect(sigc::mem_fun(*this, &Workspaces::handleScroll));
|
||||||
|
}
|
||||||
|
|
||||||
dp.emit();
|
dp.emit();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,10 +129,10 @@ void Workspaces::doUpdate() {
|
|||||||
|
|
||||||
if (config_["format"].isString()) {
|
if (config_["format"].isString()) {
|
||||||
auto format = config_["format"].asString();
|
auto format = config_["format"].asString();
|
||||||
name = fmt::format(fmt::runtime(format), fmt::arg("icon", getIcon(name, ws)),
|
name = fmt::format(
|
||||||
fmt::arg("value", name), fmt::arg("name", ws["name"].asString()),
|
fmt::runtime(format), fmt::arg("icon", getIcon(name, ws)), fmt::arg("value", name),
|
||||||
fmt::arg("index", ws["idx"].asUInt()),
|
fmt::arg("name", ws["name"].asString()), fmt::arg("index", ws["idx"].asUInt()),
|
||||||
fmt::arg("output", ws["output"].asString()));
|
fmt::arg("output", ws["output"].asString()), fmt::arg("total", my_workspaces.size()));
|
||||||
}
|
}
|
||||||
if (!config_["disable-markup"].asBool()) {
|
if (!config_["disable-markup"].asBool()) {
|
||||||
auto* child = gtk_bin_get_child(GTK_BIN(button.gobj()));
|
auto* child = gtk_bin_get_child(GTK_BIN(button.gobj()));
|
||||||
@@ -225,6 +231,45 @@ std::string Workspaces::getIcon(const std::string& value, const Json::Value& ws)
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Workspaces::handleScroll(GdkEventScroll* e) {
|
||||||
|
if (gdk_event_get_pointer_emulated((GdkEvent*)e) != 0) {
|
||||||
|
/**
|
||||||
|
* Ignore emulated scroll events on window
|
||||||
|
*/
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto dir = AModule::getScrollDir(e);
|
||||||
|
if (dir == SCROLL_DIR::NONE) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Json::Value request(Json::objectValue);
|
||||||
|
auto& action = (request["Action"] = Json::Value(Json::objectValue));
|
||||||
|
|
||||||
|
std::string action_name;
|
||||||
|
|
||||||
|
if (dir == SCROLL_DIR::DOWN || dir == SCROLL_DIR::RIGHT) {
|
||||||
|
action_name = "FocusWorkspaceDown";
|
||||||
|
} else if (dir == SCROLL_DIR::UP || dir == SCROLL_DIR::LEFT) {
|
||||||
|
action_name = "FocusWorkspaceUp";
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
action[action_name] = Json::Value(Json::objectValue);
|
||||||
|
|
||||||
|
IPC::send(request);
|
||||||
|
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
spdlog::error("Workspaces: {}", e.what());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void Workspaces::sortWorkspaces(std::vector<Json::Value>& workspaces) const {
|
void Workspaces::sortWorkspaces(std::vector<Json::Value>& workspaces) const {
|
||||||
auto get_name = [](const Json::Value& ws) -> std::string {
|
auto get_name = [](const Json::Value& ws) -> std::string {
|
||||||
if (ws["name"]) return ws["name"].asString();
|
if (ws["name"]) return ws["name"].asString();
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ waybar::modules::Pulseaudio::Pulseaudio(const std::string& id, const Json::Value
|
|||||||
|
|
||||||
backend = util::AudioBackend::getInstance([this] { this->dp.emit(); });
|
backend = util::AudioBackend::getInstance([this] { this->dp.emit(); });
|
||||||
backend->setIgnoredSinks(config_["ignored-sinks"]);
|
backend->setIgnoredSinks(config_["ignored-sinks"]);
|
||||||
|
backend->setSinkMapping(config_["sink-mapping"]);
|
||||||
|
|
||||||
if (config_["target"].isString() && config_["target"].asString() == "source") {
|
if (config_["target"].isString() && config_["target"].asString() == "source") {
|
||||||
target = util::PulseaudioTarget::Source;
|
target = util::PulseaudioTarget::Source;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ PulseaudioSlider::PulseaudioSlider(const std::string& id, const Json::Value& con
|
|||||||
: ASlider(config, "pulseaudio-slider", id) {
|
: ASlider(config, "pulseaudio-slider", id) {
|
||||||
backend = util::AudioBackend::getInstance([this] { this->dp.emit(); });
|
backend = util::AudioBackend::getInstance([this] { this->dp.emit(); });
|
||||||
backend->setIgnoredSinks(config_["ignored-sinks"]);
|
backend->setIgnoredSinks(config_["ignored-sinks"]);
|
||||||
|
backend->setSinkMapping(config_["sink-mapping"]);
|
||||||
|
|
||||||
if (config_["target"].isString()) {
|
if (config_["target"].isString()) {
|
||||||
std::string target = config_["target"].asString();
|
std::string target = config_["target"].asString();
|
||||||
|
|||||||
@@ -183,6 +183,7 @@ Tags::Tags(const std::string& id, const waybar::Bar& bar, const Json::Value& con
|
|||||||
button.signal_button_press_event().connect(
|
button.signal_button_press_event().connect(
|
||||||
sigc::bind(sigc::mem_fun(*this, &Tags::handle_button_press), (1 << tag)));
|
sigc::bind(sigc::mem_fun(*this, &Tags::handle_button_press), (1 << tag)));
|
||||||
}
|
}
|
||||||
|
button.get_style_context()->add_class("tag-" + std::to_string(tag + 1));
|
||||||
button.show();
|
button.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+67
-10
@@ -28,7 +28,8 @@ waybar::modules::Wireplumber::Wireplumber(const std::string& id, const Json::Val
|
|||||||
source_node_id_(0),
|
source_node_id_(0),
|
||||||
source_muted_(false),
|
source_muted_(false),
|
||||||
source_volume_(0.0),
|
source_volume_(0.0),
|
||||||
default_source_name_(nullptr) {
|
default_source_name_(nullptr),
|
||||||
|
form_factor_("") {
|
||||||
waybar::modules::Wireplumber::modules.push_back(this);
|
waybar::modules::Wireplumber::modules.push_back(this);
|
||||||
|
|
||||||
wp_init(WP_INIT_PIPEWIRE);
|
wp_init(WP_INIT_PIPEWIRE);
|
||||||
@@ -111,6 +112,21 @@ void waybar::modules::Wireplumber::updateNodeName(waybar::modules::Wireplumber*
|
|||||||
: description != nullptr ? description
|
: description != nullptr ? description
|
||||||
: "Unknown node name";
|
: "Unknown node name";
|
||||||
spdlog::debug("[{}]: Updating '{}' node name to: {}", self->name_, self->type_, self->node_name_);
|
spdlog::debug("[{}]: Updating '{}' node name to: {}", self->name_, self->type_, self->node_name_);
|
||||||
|
|
||||||
|
// find form-factor
|
||||||
|
const auto* devid = wp_properties_get(properties, "device.id");
|
||||||
|
spdlog::debug("[{}]: '{}' device.id is {}", self->name_, self->type_, devid);
|
||||||
|
|
||||||
|
auto* dev = static_cast<WpDevice*>(wp_object_manager_lookup(
|
||||||
|
self->om_, WP_TYPE_DEVICE, WP_CONSTRAINT_TYPE_G_PROPERTY, "bound-id", "=s", devid, nullptr));
|
||||||
|
|
||||||
|
if (const auto* ff =
|
||||||
|
wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(dev), "device.form-factor")) {
|
||||||
|
self->form_factor_ = ff;
|
||||||
|
spdlog::debug("[{}]: Updating node form factor to: {}", self->name_, self->form_factor_);
|
||||||
|
} else {
|
||||||
|
self->form_factor_ = "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void waybar::modules::Wireplumber::updateSourceName(waybar::modules::Wireplumber* self,
|
void waybar::modules::Wireplumber::updateSourceName(waybar::modules::Wireplumber* self,
|
||||||
@@ -373,6 +389,8 @@ void waybar::modules::Wireplumber::prepare(waybar::modules::Wireplumber* self) {
|
|||||||
"=s", self->type_, nullptr);
|
"=s", self->type_, nullptr);
|
||||||
wp_object_manager_add_interest(om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_PW_PROPERTY, "media.class",
|
wp_object_manager_add_interest(om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_PW_PROPERTY, "media.class",
|
||||||
"=s", "Audio/Source", nullptr);
|
"=s", "Audio/Source", nullptr);
|
||||||
|
wp_object_manager_add_interest(om_, WP_TYPE_DEVICE, WP_CONSTRAINT_TYPE_PW_PROPERTY, "media.class",
|
||||||
|
"=s", "Audio/Device", nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
void waybar::modules::Wireplumber::onDefaultNodesApiLoaded(WpObject* p, GAsyncResult* res,
|
void waybar::modules::Wireplumber::onDefaultNodesApiLoaded(WpObject* p, GAsyncResult* res,
|
||||||
@@ -430,12 +448,54 @@ void waybar::modules::Wireplumber::asyncLoadRequiredApiModules() {
|
|||||||
this);
|
this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const std::array<std::string, 7> ports = {
|
||||||
|
"headphone", "speaker", "headset", "hands-free", "portable", "car", "hifi",
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<std::string> waybar::modules::Wireplumber::getWPIcon() {
|
||||||
|
std::vector<std::string> res;
|
||||||
|
if (muted_) {
|
||||||
|
res.emplace_back(node_name_ + "-muted");
|
||||||
|
}
|
||||||
|
res.push_back(node_name_);
|
||||||
|
res.push_back(source_name_);
|
||||||
|
std::transform(form_factor_.begin(), form_factor_.end(), form_factor_.begin(), ::tolower);
|
||||||
|
for (auto const& port : ports) {
|
||||||
|
if (form_factor_.find(port) != std::string::npos) {
|
||||||
|
if (muted_) {
|
||||||
|
res.emplace_back(port + "-muted");
|
||||||
|
}
|
||||||
|
res.push_back(port);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (muted_) {
|
||||||
|
res.emplace_back("default-muted");
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
auto waybar::modules::Wireplumber::update() -> void {
|
auto waybar::modules::Wireplumber::update() -> void {
|
||||||
auto format = format_;
|
auto format = format_;
|
||||||
|
std::string format_name = "format";
|
||||||
|
|
||||||
|
// Handle sink bluetooth state
|
||||||
|
const std::string name = default_node_name_ != nullptr ? default_node_name_ : "";
|
||||||
|
|
||||||
|
auto bt = name.find("bluez") != std::string::npos || name.find("a2dp-sink") != std::string::npos;
|
||||||
|
if (bt) {
|
||||||
|
format_name += "-bluetooth";
|
||||||
|
label_.get_style_context()->add_class("bluetooth");
|
||||||
|
} else {
|
||||||
|
label_.get_style_context()->remove_class("bluetooth");
|
||||||
|
}
|
||||||
|
|
||||||
// Handle sink mute state
|
// Handle sink mute state
|
||||||
if (muted_) {
|
if (muted_) {
|
||||||
format = config_["format-muted"].isString() ? config_["format-muted"].asString() : format;
|
// Check muted bluetooth format exists, otherwise fall back to default muted format.
|
||||||
|
if (format_name != "format" && !config_[format_name + "-muted"].isString())
|
||||||
|
format_name = "format";
|
||||||
|
format_name += "-muted";
|
||||||
label_.get_style_context()->add_class("muted");
|
label_.get_style_context()->add_class("muted");
|
||||||
label_.get_style_context()->add_class("sink-muted");
|
label_.get_style_context()->add_class("sink-muted");
|
||||||
} else {
|
} else {
|
||||||
@@ -461,13 +521,10 @@ auto waybar::modules::Wireplumber::update() -> void {
|
|||||||
|
|
||||||
// Get the state and apply state-specific format if available
|
// Get the state and apply state-specific format if available
|
||||||
auto state = getState(vol);
|
auto state = getState(vol);
|
||||||
if (!state.empty()) {
|
if (!state.empty() && config_[format_name + "-" + state].isString())
|
||||||
std::string format_name = muted_ ? "format-muted" : "format";
|
format = config_[format_name + "-" + state].asString();
|
||||||
std::string state_format_name = format_name + "-" + state;
|
else if (config_[format_name].isString())
|
||||||
if (config_[state_format_name].isString()) {
|
format = config_[format_name].asString();
|
||||||
format = config_[state_format_name].asString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare source format string (similar to PulseAudio)
|
// Prepare source format string (similar to PulseAudio)
|
||||||
std::string format_source = "{volume}%";
|
std::string format_source = "{volume}%";
|
||||||
@@ -488,7 +545,7 @@ auto waybar::modules::Wireplumber::update() -> void {
|
|||||||
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||||
store.push_back(fmt::arg("node_name", node_name_));
|
store.push_back(fmt::arg("node_name", node_name_));
|
||||||
store.push_back(fmt::arg("volume", vol));
|
store.push_back(fmt::arg("volume", vol));
|
||||||
store.push_back(fmt::arg("icon", getIcon(vol)));
|
store.push_back(fmt::arg("icon", getIcon(vol, getWPIcon())));
|
||||||
store.push_back(fmt::arg("format_source", formatted_source));
|
store.push_back(fmt::arg("format_source", formatted_source));
|
||||||
store.push_back(fmt::arg("source_volume", source_vol));
|
store.push_back(fmt::arg("source_volume", source_vol));
|
||||||
store.push_back(fmt::arg("source_desc", source_name_));
|
store.push_back(fmt::arg("source_desc", source_name_));
|
||||||
|
|||||||
@@ -198,10 +198,18 @@ void AudioBackend::sinkInfoCb(pa_context* /*context*/, const pa_sink_info* i, in
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (const auto mapping = backend->sink_mapping_.find(backend->current_sink_name_);
|
||||||
|
mapping != backend->sink_mapping_.end()) {
|
||||||
|
if (i->name == mapping->second) {
|
||||||
|
backend->current_sink_name_ = i->name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
backend->default_sink_running_ = backend->default_sink_name == i->name &&
|
backend->default_sink_running_ = backend->default_sink_name == i->name &&
|
||||||
(i->state == PA_SINK_RUNNING || i->state == PA_SINK_IDLE);
|
(i->state == PA_SINK_RUNNING || i->state == PA_SINK_IDLE);
|
||||||
|
|
||||||
if (i->name != backend->default_sink_name && !backend->default_sink_running_) {
|
if (i->name != backend->default_sink_name && i->name != backend->current_sink_name_ &&
|
||||||
|
!backend->default_sink_running_) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -493,4 +501,14 @@ void AudioBackend::setIgnoredSinks(const Json::Value& config) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AudioBackend::setSinkMapping(const Json::Value& config) {
|
||||||
|
if (config.isObject()) {
|
||||||
|
for (auto it = config.begin(); it != config.end(); ++it) {
|
||||||
|
if (it.key().isString() && it->isString()) {
|
||||||
|
sink_mapping_.emplace(it.key().asString(), it->asString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace waybar::util
|
} // namespace waybar::util
|
||||||
|
|||||||
Reference in New Issue
Block a user