Merge remote-tracking branch 'origin/master' into refactor/generic-tooltip

# Conflicts:
#	src/modules/wireplumber.cpp
This commit is contained in:
Alex
2026-07-04 00:29:26 +02:00
51 changed files with 1903 additions and 84 deletions
+57
View File
@@ -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
+2
View File
@@ -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;
+7
View File
@@ -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;
+4
View File
@@ -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
+32
View File
@@ -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 -2
View File
@@ -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_;
+49
View File
@@ -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
+4
View File
@@ -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;
+10
View File
@@ -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
+3
View File
@@ -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)
+1
View File
@@ -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();
+1
View File
@@ -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_; }
+1
View File
@@ -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_;
+3 -3
View File
@@ -20,8 +20,8 @@ class Tags : public waybar::AModule {
void handle_focused_tags(uint32_t tags); void handle_focused_tags(uint32_t tags);
void handle_view_tags(struct wl_array* tags); void handle_view_tags(struct wl_array* tags);
void handle_urgent_tags(uint32_t tags); void handle_urgent_tags(uint32_t tags);
void handle_focused_output(struct wl_output *output); void handle_focused_output(struct wl_output* output);
void handle_unfocused_output(struct wl_output *output); void handle_unfocused_output(struct wl_output* output);
void handle_show(); void handle_show();
void handle_primary_clicked(uint32_t tag); void handle_primary_clicked(uint32_t tag);
@@ -33,7 +33,7 @@ class Tags : public waybar::AModule {
private: private:
const waybar::Bar& bar_; const waybar::Bar& bar_;
struct wl_output *output_; // stores the output this module belongs to struct wl_output* output_; // stores the output this module belongs to
Gtk::Box box_; Gtk::Box box_;
std::vector<Gtk::Button> buttons_; std::vector<Gtk::Button> buttons_;
struct zriver_output_status_v1* output_status_; struct zriver_output_status_v1* output_status_;
+2
View File
@@ -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
+2
View File
@@ -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_; }
+14
View File
@@ -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*
+2
View File
@@ -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
+80
View File
@@ -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*
+189
View File
@@ -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*
+13
View File
@@ -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.
+42 -2
View File
@@ -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*
+5
View File
@@ -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 ++
+7
View File
@@ -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.
+4
View File
@@ -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.
+1
View File
@@ -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.
+5
View File
@@ -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.
``` ```
+4
View File
@@ -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',
+131
View File
@@ -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>
+1
View File
@@ -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
View File
@@ -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
+8
View File
@@ -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
View File
@@ -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]);
} }
+14 -6
View File
@@ -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");
@@ -110,11 +115,14 @@ bool Group::handleMouseEnter(GdkEventCrossing* const& e) {
if (reveal_delay > 0) { if (reveal_delay > 0) {
if (reveal_timeout_.connected()) { if (reveal_timeout_.connected()) {
reveal_timeout_.disconnect(); reveal_timeout_.disconnect();
} }
reveal_timeout_ = Glib::signal_timeout().connect( reveal_timeout_ = Glib::signal_timeout().connect(
[this]() { show_group(); return false; }, [this]() {
reveal_delay); show_group();
return false;
},
reveal_delay);
} else { } else {
show_group(); show_group();
} }
+151 -3
View File
@@ -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;
} }
+7
View File
@@ -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>
+47
View File
@@ -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
View File
@@ -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 {
+281
View File
@@ -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;
}
}
+62 -23
View File
@@ -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();
@@ -337,7 +334,7 @@ std::string& Workspace::selectIcon(std::map<std::string, std::string>& icons_map
if (specialNamedIconIt != icons_map.end()) { if (specialNamedIconIt != icons_map.end()) {
return specialNamedIconIt->second; return specialNamedIconIt->second;
} }
auto specialIconIt = icons_map.find("special"); auto specialIconIt = icons_map.find("special");
if (specialIconIt != icons_map.end()) { if (specialIconIt != icons_map.end()) {
return specialIconIt->second; return specialIconIt->second;
@@ -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,16 +432,56 @@ 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();
for (auto it = m_windowMap.begin(); it != end_it; ++it) { if (groupThreshold > 0) {
if (isNotFirst) { // Build ordered counts of each unique icon (including singular ones when threshold set to 1)
windows.append(windowSeparator); std::vector<std::pair<std::string, int>> iconCounts;
for (auto it = m_windowMap.begin(); it != end_it; ++it) {
const auto& window_repr = *it;
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;
windows.append(it->repr_rewrite);
} }
isNotFirst = true;
windows.append(it->repr_rewrite);
} }
} }
@@ -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);
+10
View File
@@ -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);
+148 -15
View File
@@ -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,21 +108,34 @@ 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;
timeout_ = Glib::signal_timeout().connect_seconds( // If wait-for-activity is enabled, set up idle notification
[]() { if (wait_for_activity_) {
/* intentionally not tied to a module instance lifetime spdlog::debug("idle_inhibitor: wait-for-activity enabled, timeout: {} ms", idle_timeout_ms_);
* as the output with `this` can be disconnected // Tear down any existing notification first to ensure fresh setup
*/ teardownIdleNotification();
spdlog::info("deactivating idle_inhibitor by timeout"); setupIdleNotification();
status = false; } else {
for (auto const& module : waybar::modules::IdleInhibitor::modules) { // Original behavior: simple timeout
module->update(); timeout_ = Glib::signal_timeout().connect_seconds(
} []() {
/* disconnect */ /* intentionally not tied to a module instance lifetime
return false; * as the output with `this` can be disconnected
}, */
timeoutSecs); spdlog::info("deactivating idle_inhibitor by timeout");
status = false;
for (auto const& module : waybar::modules::IdleInhibitor::modules) {
module->update();
}
/* disconnect */
return false;
},
timeoutSecs);
}
} else {
// When deactivated, tear down idle notification
teardownIdleNotification();
} }
} }
@@ -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;
}
}
+6
View File
@@ -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_);
+3 -3
View File
@@ -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 {
+9 -3
View File
@@ -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) {
+49 -4
View File
@@ -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();
+1
View File
@@ -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;
+1
View File
@@ -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();
+5 -4
View File
@@ -35,12 +35,12 @@ static const zriver_output_status_v1_listener output_status_listener_impl{
static void listen_focused_output(void* data, struct zriver_seat_status_v1* zriver_seat_status_v1, static void listen_focused_output(void* data, struct zriver_seat_status_v1* zriver_seat_status_v1,
struct wl_output* output) { struct wl_output* output) {
static_cast<Tags *>(data)->handle_focused_output(output); static_cast<Tags*>(data)->handle_focused_output(output);
} }
static void listen_unfocused_output(void* data, struct zriver_seat_status_v1* zriver_seat_status_v1, static void listen_unfocused_output(void* data, struct zriver_seat_status_v1* zriver_seat_status_v1,
struct wl_output* output) { struct wl_output* output) {
static_cast<Tags *>(data)->handle_unfocused_output(output); static_cast<Tags*>(data)->handle_unfocused_output(output);
} }
static void listen_focused_view(void* data, struct zriver_seat_status_v1* zriver_seat_status_v1, static void listen_focused_view(void* data, struct zriver_seat_status_v1* zriver_seat_status_v1,
@@ -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();
} }
@@ -306,7 +307,7 @@ void Tags::handle_urgent_tags(uint32_t tags) {
} }
} }
void Tags::handle_focused_output(struct wl_output *output) { void Tags::handle_focused_output(struct wl_output* output) {
if (output_ == output) { if (output_ == output) {
for (size_t i = 0; i < buttons_.size(); ++i) { for (size_t i = 0; i < buttons_.size(); ++i) {
buttons_[i].get_style_context()->add_class("output"); buttons_[i].get_style_context()->add_class("output");
@@ -314,7 +315,7 @@ void Tags::handle_focused_output(struct wl_output *output) {
} }
} }
void Tags::handle_unfocused_output(struct wl_output *output) { void Tags::handle_unfocused_output(struct wl_output* output) {
if (output_ == output) { if (output_ == output) {
for (size_t i = 0; i < buttons_.size(); ++i) { for (size_t i = 0; i < buttons_.size(); ++i) {
buttons_[i].get_style_context()->remove_class("output"); buttons_[i].get_style_context()->remove_class("output");
+67 -10
View File
@@ -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_));
+19 -1
View File
@@ -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