Merge branch 'master' into openbsd

This commit is contained in:
Alexis Rouillard
2026-07-03 21:30:44 +02:00
committed by GitHub
46 changed files with 738 additions and 317 deletions
+7
View File
@@ -25,6 +25,10 @@ class AModule : public IModule {
bool expandEnabled() const; bool expandEnabled() const;
virtual void suspend() {};
virtual void resume() {};
bool shouldSuspend() const { return disable_on_sleep_; }
protected: protected:
// Don't need to make an object directly // Don't need to make an object directly
// Derived classes are able to use it // Derived classes are able to use it
@@ -48,6 +52,8 @@ class AModule : public IModule {
virtual bool handleMouseLeave(GdkEventCrossing* const& ev); virtual bool handleMouseLeave(GdkEventCrossing* const& ev);
virtual bool handleScroll(GdkEventScroll*); virtual bool handleScroll(GdkEventScroll*);
virtual bool handleRelease(GdkEventButton* const& ev); virtual bool handleRelease(GdkEventButton* const& ev);
bool disable_on_sleep_{false};
GObject* menu_ = nullptr; GObject* menu_ = nullptr;
private: private:
@@ -57,6 +63,7 @@ class AModule : public IModule {
bool hasUserEvents_; bool hasUserEvents_;
gdouble distance_scrolled_y_; gdouble distance_scrolled_y_;
gdouble distance_scrolled_x_; gdouble distance_scrolled_x_;
sigc::connection cursor_timeout_conn_;
std::map<std::string, std::string> eventActionMap_; std::map<std::string, std::string> eventActionMap_;
static const inline std::map<std::pair<uint, GdkEventType>, std::string> eventMap_{ static const inline std::map<std::pair<uint, GdkEventType>, std::string> eventMap_{
{std::make_pair(1, GdkEventType::GDK_BUTTON_PRESS), "on-click"}, {std::make_pair(1, GdkEventType::GDK_BUTTON_PRESS), "on-click"},
+2
View File
@@ -75,6 +75,8 @@ class Bar : public sigc::trackable {
util::KillSignalAction getOnSigusr1Action(); util::KillSignalAction getOnSigusr1Action();
util::KillSignalAction getOnSigusr2Action(); util::KillSignalAction getOnSigusr2Action();
void toggleSuspend(bool suspend);
struct waybar_output* output; struct waybar_output* output;
Json::Value config; Json::Value config;
struct wl_surface* surface; struct wl_surface* surface;
+14
View File
@@ -4,6 +4,7 @@
#include <filesystem> #include <filesystem>
#include <list> #include <list>
#include <mutex> #include <mutex>
#include <optional>
#include <string> #include <string>
#include <thread> #include <thread>
#include <utility> #include <utility>
@@ -35,9 +36,22 @@ class IPC {
Json::Value getSocket1JsonReply(const std::string& rq); Json::Value getSocket1JsonReply(const std::string& rq);
static std::filesystem::path getSocketFolder(const char* instanceSig); static std::filesystem::path getSocketFolder(const char* instanceSig);
/// Dispatch a Hyprland command. Automatically uses the correct protocol
/// (legacy text or Lua-based) depending on the running Hyprland version.
static std::string dispatch(const std::string& dispatcher, const std::string& arg);
/// Build a Lua-format dispatch command string.
static std::string buildLuaDispatch(const std::string& dispatcher, const std::string& arg);
protected: protected:
static std::filesystem::path socketFolder_; static std::filesystem::path socketFolder_;
/// Detect whether the running Hyprland uses the Lua-based IPC protocol.
/// Returns true for Hyprland >= 0.54 (Lua config), false for older versions.
static bool isLuaProtocol();
static std::optional<bool> s_luaProtocolDetected_; // cached detection result
private: private:
void socketListener(); void socketListener();
void parseIPC(const std::string&); void parseIPC(const std::string&);
-2
View File
@@ -19,8 +19,6 @@ class Memory : public ALabel {
private: private:
void parseMeminfo(); void parseMeminfo();
static float calc_divisor(const std::string& divisor);
std::unordered_map<std::string, unsigned long> meminfo_; std::unordered_map<std::string, unsigned long> meminfo_;
util::SleeperThread thread_; util::SleeperThread thread_;
+5 -1
View File
@@ -33,6 +33,7 @@ class MPD : public ALabel {
detail::unique_status status_; detail::unique_status status_;
mpd_state state_; mpd_state state_;
detail::unique_song song_; detail::unique_song song_;
std::string ellipsis_;
public: public:
MPD(const std::string&, const Json::Value&); MPD(const std::string&, const Json::Value&);
@@ -45,6 +46,10 @@ class MPD : public ALabel {
void setLabel(); void setLabel();
std::string getStateIcon() const; std::string getStateIcon() const;
std::string getOptionIcon(const std::string& optionName, bool activated) const; std::string getOptionIcon(const std::string& optionName, bool activated) const;
std::string getArtistStr(bool truncated) const;
std::string getAlbumArtistStr(bool truncated) const;
std::string getAlbumStr(bool truncated) const;
std::string getTitleStr(bool truncated) const;
// GUI-side methods // GUI-side methods
bool handlePlayPause(GdkEventButton* const&); bool handlePlayPause(GdkEventButton* const&);
@@ -54,7 +59,6 @@ class MPD : public ALabel {
void tryConnect(); void tryConnect();
void checkErrors(mpd_connection* conn); void checkErrors(mpd_connection* conn);
void fetchState(); void fetchState();
void queryMPD();
inline bool stopped() const { return connection_ && state_ == MPD_STATE_STOP; } inline bool stopped() const { return connection_ && state_ == MPD_STATE_STOP; }
inline bool playing() const { return connection_ && state_ == MPD_STATE_PLAY; } inline bool playing() const { return connection_ && state_ == MPD_STATE_PLAY; }
-1
View File
@@ -197,7 +197,6 @@ class Context {
void tryConnect() const; void tryConnect() const;
void checkErrors(mpd_connection*) const; void checkErrors(mpd_connection*) const;
void do_update(); void do_update();
void queryMPD() const;
void fetchState() const; void fetchState() const;
constexpr mpd_state state() const; constexpr mpd_state state() const;
void emit() const; void emit() const;
+2 -2
View File
@@ -1,13 +1,14 @@
#pragma once #pragma once
namespace detail { namespace detail {
using namespace std::literals::chrono_literals;
inline bool Context::is_connected() const { return mpd_module_->connection_ != nullptr; } inline bool Context::is_connected() const { return mpd_module_->connection_ != nullptr; }
inline bool Context::is_playing() const { return mpd_module_->playing(); } inline bool Context::is_playing() const { return mpd_module_->playing(); }
inline bool Context::is_paused() const { return mpd_module_->paused(); } inline bool Context::is_paused() const { return mpd_module_->paused(); }
inline bool Context::is_stopped() const { return mpd_module_->stopped(); } inline bool Context::is_stopped() const { return mpd_module_->stopped(); }
constexpr inline std::size_t Context::interval() const { return mpd_module_->interval_.count(); } constexpr inline std::size_t Context::interval() const { return mpd_module_->interval_ / 1s; }
inline void Context::tryConnect() const { mpd_module_->tryConnect(); } inline void Context::tryConnect() const { mpd_module_->tryConnect(); }
inline unique_connection& Context::connection() { return mpd_module_->connection_; } inline unique_connection& Context::connection() { return mpd_module_->connection_; }
constexpr inline mpd_state Context::state() const { return mpd_module_->state_; } constexpr inline mpd_state Context::state() const { return mpd_module_->state_; }
@@ -15,7 +16,6 @@ constexpr inline mpd_state Context::state() const { return mpd_module_->state_;
inline void Context::do_update() { mpd_module_->setLabel(); } inline void Context::do_update() { mpd_module_->setLabel(); }
inline void Context::checkErrors(mpd_connection* conn) const { mpd_module_->checkErrors(conn); } inline void Context::checkErrors(mpd_connection* conn) const { mpd_module_->checkErrors(conn); }
inline void Context::queryMPD() const { mpd_module_->queryMPD(); }
inline void Context::fetchState() const { mpd_module_->fetchState(); } inline void Context::fetchState() const { mpd_module_->fetchState(); }
inline void Context::emit() const { mpd_module_->emit(); } inline void Context::emit() const { mpd_module_->emit(); }
+1
View File
@@ -28,6 +28,7 @@ class Privacy : public AModule {
// Config // Config
Gtk::Box box_; Gtk::Box box_;
std::vector<PrivacyItem*> modules_;
uint iconSpacing = 4; uint iconSpacing = 4;
uint iconSize = 20; uint iconSize = 20;
uint transition_duration = 250; uint transition_duration = 250;
+1
View File
@@ -46,6 +46,7 @@ class Item : public sigc::trackable {
std::string title; std::string title;
std::string icon_name; std::string icon_name;
Glib::RefPtr<Gdk::Pixbuf> icon_pixmap; Glib::RefPtr<Gdk::Pixbuf> icon_pixmap;
bool has_custom_icon_ = false;
Glib::RefPtr<Gtk::IconTheme> icon_theme; Glib::RefPtr<Gtk::IconTheme> icon_theme;
std::string overlay_icon_name; std::string overlay_icon_name;
Glib::RefPtr<Gdk::Pixbuf> overlay_icon_pixmap; Glib::RefPtr<Gdk::Pixbuf> overlay_icon_pixmap;
-1
View File
@@ -22,7 +22,6 @@ class Tray : public AModule {
void queueUpdate(); void queueUpdate();
static inline std::size_t nb_hosts_ = 0; static inline std::size_t nb_hosts_ = 0;
bool show_passive_ = false;
Gtk::Box box_; Gtk::Box box_;
SNI::Watcher::singleton watcher_; SNI::Watcher::singleton watcher_;
SNI::Host host_; SNI::Host host_;
+1
View File
@@ -24,6 +24,7 @@ class Workspaces : public AModule, public sigc::trackable {
private: private:
static constexpr std::string_view workspace_switch_cmd_ = "workspace {} \"{}\""; static constexpr std::string_view workspace_switch_cmd_ = "workspace {} \"{}\"";
static constexpr std::string_view workspace_switch_number_cmd_ = "workspace {} number {}";
static constexpr std::string_view persistent_workspace_switch_cmd_ = static constexpr std::string_view persistent_workspace_switch_cmd_ =
R"(workspace {} "{}"; move workspace to output "{}"; workspace {} "{}")"; R"(workspace {} "{}"; move workspace to output "{}"; workspace {} "{}")";
+2
View File
@@ -14,6 +14,8 @@ class Temperature : public ALabel {
Temperature(const std::string&, const Json::Value&); Temperature(const std::string&, const Json::Value&);
virtual ~Temperature() = default; virtual ~Temperature() = default;
auto update() -> void override; auto update() -> void override;
void suspend() override;
void resume() override;
private: private:
float getTemperature(); float getTemperature();
+9 -3
View File
@@ -7,6 +7,7 @@
#include <codecvt> #include <codecvt>
#include <iostream> #include <iostream>
#include <locale> #include <locale>
#include <memory>
#include <regex> #include <regex>
#if (FMT_VERSION >= 90000) #if (FMT_VERSION >= 90000)
@@ -26,14 +27,19 @@ class JsonParser {
Json::Value root; Json::Value root;
// replace all occurrences of "\x" with "\u00", because JSON doesn't allow "\x" escape sequences // replace all occurrences of "\x" with "\u00", because JSON doesn't allow "\x" escape sequences
std::string modifiedJsonStr = replaceHexadecimalEscape(jsonStr); std::string modifiedJsonStr;
const std::string* json = &jsonStr;
if (jsonStr.find("\\x") != std::string::npos) {
modifiedJsonStr = replaceHexadecimalEscape(jsonStr);
json = &modifiedJsonStr;
}
std::istringstream jsonStream(modifiedJsonStr);
std::string errs; std::string errs;
// Use local CharReaderBuilder for thread safety - the IPC singleton's // Use local CharReaderBuilder for thread safety - the IPC singleton's
// parser can be called concurrently from multiple module threads // parser can be called concurrently from multiple module threads
Json::CharReaderBuilder readerBuilder; Json::CharReaderBuilder readerBuilder;
if (!Json::parseFromStream(readerBuilder, jsonStream, &root, &errs)) { auto reader = std::unique_ptr<Json::CharReader>(readerBuilder.newCharReader());
if (!reader->parse(json->data(), json->data() + json->size(), &root, &errs)) {
throw std::runtime_error("Error parsing JSON: " + errs); throw std::runtime_error("Error parsing JSON: " + errs);
} }
return root; return root;
+24
View File
@@ -79,6 +79,12 @@ class SleeperThread {
auto sleep_for(std::chrono::system_clock::duration dur) { auto sleep_for(std::chrono::system_clock::duration dur) {
std::unique_lock lk(mutex_); std::unique_lock lk(mutex_);
CancellationGuard cancel_lock; CancellationGuard cancel_lock;
condvar_.wait(lk, [this] {
return !is_paused_ || signal_.load(std::memory_order_relaxed) ||
!do_run_.load(std::memory_order_relaxed);
});
constexpr auto max_time_point = std::chrono::steady_clock::time_point::max(); constexpr auto max_time_point = std::chrono::steady_clock::time_point::max();
auto wait_end = max_time_point; auto wait_end = max_time_point;
auto now = std::chrono::steady_clock::now(); auto now = std::chrono::steady_clock::now();
@@ -95,6 +101,12 @@ class SleeperThread {
time_point) { time_point) {
std::unique_lock lk(mutex_); std::unique_lock lk(mutex_);
CancellationGuard cancel_lock; CancellationGuard cancel_lock;
condvar_.wait(lk, [this] {
return !is_paused_ || signal_.load(std::memory_order_relaxed) ||
!do_run_.load(std::memory_order_relaxed);
});
return condvar_.wait_until(lk, time_point, [this] { return condvar_.wait_until(lk, time_point, [this] {
return signal_.load(std::memory_order_relaxed) || !do_run_.load(std::memory_order_relaxed); return signal_.load(std::memory_order_relaxed) || !do_run_.load(std::memory_order_relaxed);
}); });
@@ -122,6 +134,17 @@ class SleeperThread {
} }
} }
void pause() {
std::lock_guard<std::mutex> lock(mutex_);
is_paused_ = true;
}
void resume() {
std::lock_guard<std::mutex> lock(mutex_);
is_paused_ = false;
condvar_.notify_all();
}
~SleeperThread() { ~SleeperThread() {
connection_.disconnect(); connection_.disconnect();
stop(); stop();
@@ -137,6 +160,7 @@ class SleeperThread {
std::atomic<bool> do_run_ = true; std::atomic<bool> do_run_ = true;
std::atomic<bool> signal_ = false; std::atomic<bool> signal_ = false;
sigc::connection connection_; sigc::connection connection_;
bool is_paused_{false};
}; };
} // namespace waybar::util } // namespace waybar::util
+7
View File
@@ -0,0 +1,7 @@
#pragma once
#include <string>
namespace waybar::util {
size_t utf8_width(const std::string& str);
void utf8_truncate(std::string& s, const std::string& ellipsis, size_t max_len);
} // namespace waybar::util
+36
View File
@@ -38,6 +38,20 @@ Addressed by *hyprland/language*
typeof: array ++ typeof: array ++
The actions corresponding to the buttons of the menu. The actions corresponding to the buttons of the menu.
*tooltip*: ++
typeof: boolean ++
default: true ++
Enables or disables the tooltip for the language module. By default, the tooltip is enabled. Set to *false* to disable.
*tooltip-format*: ++
typeof: string ++
default: {long} ++
Specifies the format of the tooltip when it is enabled. It follows the same format replacement rules as the *format* key.
*tooltip-format-<lang>*: ++
typeof: string ++
Allows specifying a different tooltip format for each language. The *<lang>* should be replaced with the language code. This can be used to provide a custom tooltip for each language.
*expand*: ++ *expand*: ++
typeof: bool ++ typeof: bool ++
default: false ++ default: false ++
@@ -66,6 +80,28 @@ Addressed by *hyprland/language*
} }
``` ```
```
"hyprland/language": {
"format": "{}",
"format-en": "US",
"format-es": "ES",
"tooltip": true,
"tooltip-format": "{long}"
}
```
```
"hyprland/language": {
"format": "{}",
"format-en": "US",
"format-es": "ES",
"tooltip": true,
"tooltip-format": "{}",
"tooltip-format-es": "{Español}",
"tooltip-format-en": "{English (american)}"
}
```
# STYLE # STYLE
- *#language* - *#language*
+8
View File
@@ -135,6 +135,14 @@ This setting is ignored if *workspace-taskbar.enable* is set to true.
default: false ++ default: false ++
If set to false, you can't scroll to cycle throughout workspaces from the entire bar. If set to true this behaviour is enabled. If set to false, you can't scroll to cycle throughout workspaces from the entire bar. If set to true this behaviour is enabled.
*on-scroll-up*: ++
typeof: string ++
Command to execute when scrolling up on the module. This replaces the default behaviour of workspace cycling.
*on-scroll-down*: ++
typeof: string ++
Command to execute when scrolling down on the module. This replaces the default behaviour of workspace cycling.
*ignore-workspaces*: ++ *ignore-workspaces*: ++
typeof: array ++ typeof: array ++
default: [] ++ default: [] ++
+2 -1
View File
@@ -186,7 +186,8 @@ src_files = files(
'src/util/icon_loader.cpp', 'src/util/icon_loader.cpp',
'src/util/regex_collection.cpp', 'src/util/regex_collection.cpp',
'src/util/css_reload_helper.cpp', 'src/util/css_reload_helper.cpp',
'src/util/transform_8bit_to_rgba.cpp' 'src/util/transform_8bit_to_rgba.cpp',
'src/util/utf8_string.cpp'
) )
man_files = files( man_files = files(
+13 -13
View File
@@ -33,21 +33,21 @@ std::string toLowerCase(const std::string& input) {
std::optional<std::string> getFileBySuffix(const std::string& dir, const std::string& suffix, std::optional<std::string> getFileBySuffix(const std::string& dir, const std::string& suffix,
bool check_lower_case) { bool check_lower_case) {
if (!std::filesystem::exists(dir)) { try {
return {}; for (const auto& entry : std::filesystem::recursive_directory_iterator(dir)) {
} if (entry.is_regular_file()) {
for (const auto& entry : std::filesystem::recursive_directory_iterator(dir)) { std::string filename = entry.path().filename().string();
if (entry.is_regular_file()) { if (filename.size() < suffix.size()) {
std::string filename = entry.path().filename().string(); continue;
if (filename.size() < suffix.size()) { }
continue; if ((filename.compare(filename.size() - suffix.size(), suffix.size(), suffix) == 0) ||
} (check_lower_case && filename.compare(filename.size() - suffix.size(), suffix.size(),
if ((filename.compare(filename.size() - suffix.size(), suffix.size(), suffix) == 0) || toLowerCase(suffix)) == 0)) {
(check_lower_case && filename.compare(filename.size() - suffix.size(), suffix.size(), return entry.path().string();
toLowerCase(suffix)) == 0)) { }
return entry.path().string();
} }
} }
} catch (const std::filesystem::filesystem_error&) {
} }
return {}; return {};
+15 -4
View File
@@ -17,10 +17,14 @@ AModule::AModule(const Json::Value& config, const std::string& name, const std::
isTooltip{config_["tooltip"].isBool() ? config_["tooltip"].asBool() : true}, isTooltip{config_["tooltip"].isBool() ? config_["tooltip"].asBool() : true},
isExpand{config_["expand"].isBool() ? config_["expand"].asBool() : false}, isExpand{config_["expand"].isBool() ? config_["expand"].asBool() : false},
distance_scrolled_y_(0.0), distance_scrolled_y_(0.0),
distance_scrolled_x_(0.0) { distance_scrolled_x_(0.0),
cursor_timeout_conn_() {
// Configure module action Map // Configure module action Map
const Json::Value actions{config_["actions"]}; const Json::Value actions{config_["actions"]};
disable_on_sleep_ =
config_["disable-on-sleep"].isBool() ? config_["disable-on-sleep"].asBool() : false;
for (Json::Value::const_iterator it = actions.begin(); it != actions.end(); ++it) { for (Json::Value::const_iterator it = actions.begin(); it != actions.end(); ++it) {
if (it.key().isString() && it->isString()) if (it.key().isString() && it->isString())
if (!eventActionMap_.contains(it.key().asString())) { if (!eventActionMap_.contains(it.key().asString())) {
@@ -72,8 +76,12 @@ AModule::AModule(const Json::Value& config, const std::string& name, const std::
// Respect user configuration of cursor // Respect user configuration of cursor
if (config_.isMember("cursor")) { if (config_.isMember("cursor")) {
if (config_["cursor"].isBool() && config_["cursor"].asBool()) { if (config_["cursor"].isBool()) {
setCursor(Gdk::HAND2); if (config_["cursor"].asBool()) {
setCursor(Gdk::HAND2);
} else {
setCursor(Gdk::ARROW);
}
} else if (config_["cursor"].isInt()) { } else if (config_["cursor"].isInt()) {
setCursor(Gdk::CursorType(config_["cursor"].asInt())); setCursor(Gdk::CursorType(config_["cursor"].asInt()));
} else { } else {
@@ -83,6 +91,9 @@ AModule::AModule(const Json::Value& config, const std::string& name, const std::
} }
AModule::~AModule() { AModule::~AModule() {
if (cursor_timeout_conn_.connected()) {
cursor_timeout_conn_.disconnect();
}
for (const auto& pid : pid_children_) { for (const auto& pid : pid_children_) {
if (pid != -1) { if (pid != -1) {
killpg(pid, SIGTERM); killpg(pid, SIGTERM);
@@ -118,7 +129,7 @@ void AModule::setCursor(Gdk::CursorType const& c) {
} else { } else {
// window may not be accessible yet, in this case, // window may not be accessible yet, in this case,
// schedule another call for setting the cursor in 1 sec // schedule another call for setting the cursor in 1 sec
Glib::signal_timeout().connect_seconds( cursor_timeout_conn_ = Glib::signal_timeout().connect_seconds(
[this, c]() { [this, c]() {
setCursor(c); setCursor(c);
return false; return false;
+31 -2
View File
@@ -263,6 +263,16 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config)
window.signal_map_event().connect_notify(sigc::mem_fun(*this, &Bar::onMap)); window.signal_map_event().connect_notify(sigc::mem_fun(*this, &Bar::onMap));
window.signal_unmap().connect([this]() {
spdlog::debug("Output {} unmapped (DPMS off), suspending modules", output->name);
toggleSuspend(true);
});
window.signal_map().connect([this]() {
spdlog::debug("Output {} mapped (DPMS on), resuming modules", output->name);
toggleSuspend(false);
});
#if HAVE_SWAY #if HAVE_SWAY
if (auto ipc = config["ipc"]; ipc.isBool() && ipc.asBool()) { if (auto ipc = config["ipc"]; ipc.isBool() && ipc.asBool()) {
bar_id = Client::inst()->bar_id; bar_id = Client::inst()->bar_id;
@@ -545,8 +555,8 @@ void waybar::Bar::getModules(const Factory& factory, const std::string& pos,
if (group_config["modules"].isNull()) { if (group_config["modules"].isNull()) {
spdlog::warn("Group definition '{}' has not been found, group will be hidden", ref); spdlog::warn("Group definition '{}' has not been found, group will be hidden", ref);
} }
auto group_module = std::make_unique<waybar::Group>( auto group_module =
id_name, class_name, group_config, vertical); std::make_unique<waybar::Group>(id_name, class_name, group_config, vertical);
getModules(factory, ref, group_module.get()); getModules(factory, ref, group_module.get());
module = group_module.release(); module = group_module.release();
@@ -696,3 +706,22 @@ void waybar::Bar::configureGlobalOffset(int width, int height) {
void waybar::Bar::onOutputGeometryChanged() { void waybar::Bar::onOutputGeometryChanged() {
configureGlobalOffset(window.get_width(), window.get_height()); configureGlobalOffset(window.get_width(), window.get_height());
} }
void waybar::Bar::toggleSuspend(bool suspend) {
auto process_modules = [suspend](Gtk::Box& module_box) {
for (auto* widget : module_box.get_children()) {
auto* module = dynamic_cast<waybar::AModule*>(widget);
if (module && module->shouldSuspend()) {
if (suspend) {
module->suspend();
} else {
module->resume();
}
}
}
};
process_modules(left_);
process_modules(center_);
process_modules(right_);
}
+75 -68
View File
@@ -117,24 +117,16 @@ void waybar::modules::Battery::refreshBatteries() {
if (((bat_defined && dir_name == config_["bat"].asString()) || !bat_defined) && if (((bat_defined && dir_name == config_["bat"].asString()) || !bat_defined) &&
(fs::exists(node.path() / "capacity") || fs::exists(node.path() / "charge_now")) && (fs::exists(node.path() / "capacity") || fs::exists(node.path() / "charge_now")) &&
fs::exists(node.path() / "uevent") && fs::exists(node.path() / "uevent") &&
(fs::exists(node.path() / "status") || bat_compatibility) && (fs::exists(node.path() / "status") || bat_compatibility)) {
fs::exists(node.path() / "type")) {
std::string type; std::string type;
std::ifstream(node.path() / "type") >> type; if (std::ifstream{node.path() / "type"} >> type && !type.compare("Battery")) {
if (!type.compare("Battery")) {
// Ignore non-system power supplies unless explicitly requested // Ignore non-system power supplies unless explicitly requested
if (!bat_defined && fs::exists(node.path() / "scope")) { if (!bat_defined) {
std::string scope; std::string scope;
try { // for hotplug-in device, access it is always unstable because you may remove the
// for hotplug-in device, access it is always unstable because you may remove the // device anytime so just allow failure happen and do nothing
// device anytime so just allow failure happen and do nothing if (std::ifstream{node.path() / "scope"} >> scope &&
std::ifstream(node.path() / "scope") >> scope; g_ascii_strcasecmp(scope.data(), "device") == 0) {
} catch (const std::ifstream::failure& e) {
scope.clear();
continue;
}
if (g_ascii_strcasecmp(scope.data(), "device") == 0) {
continue; continue;
} }
} }
@@ -161,7 +153,7 @@ void waybar::modules::Battery::refreshBatteries() {
} }
} }
} catch (fs::filesystem_error& e) { } catch (fs::filesystem_error& e) {
throw std::runtime_error(e.what()); spdlog::warn("Battery directory tracking failed: {}", e.what());
} }
if (warnFirstTime_ && batteries_.empty()) { if (warnFirstTime_ && batteries_.empty()) {
if (config_["bat"].isString()) { if (config_["bat"].isString()) {
@@ -282,11 +274,11 @@ waybar::modules::Battery::getInfos() {
auto bat = item.first; auto bat = item.first;
std::string _status; std::string _status;
/* Check for adapter status if battery is not available */ {
if (!std::ifstream(bat / "status")) { std::ifstream f{bat / "status"};
std::getline(std::ifstream(adapter_ / "status"), _status); if (!std::getline(f, _status)) {
} else { std::getline(std::ifstream(adapter_ / "status"), _status);
std::getline(std::ifstream(bat / "status"), _status); }
} }
// Some battery will report current and charge in μA/μAh. // Some battery will report current and charge in μA/μAh.
@@ -295,64 +287,74 @@ waybar::modules::Battery::getInfos() {
uint32_t current_now = 0; uint32_t current_now = 0;
int32_t _current_now_int = 0; int32_t _current_now_int = 0;
bool current_now_exists = false; bool current_now_exists = false;
if (fs::exists(bat / "current_now")) { if (std::ifstream current_now_f{bat / "current_now"}) {
current_now_exists = true; if (current_now_f >> _current_now_int) {
std::ifstream(bat / "current_now") >> _current_now_int; current_now_exists = true;
} else if (fs::exists(bat / "current_avg")) { }
current_now_exists = true; } else if (std::ifstream current_avg_f{bat / "current_avg"}) {
std::ifstream(bat / "current_avg") >> _current_now_int; if (current_avg_f >> _current_now_int) {
current_now_exists = true;
}
} }
// Documentation ABI allows a negative value when discharging, positive // Documentation ABI allows a negative value when discharging, positive
// value when charging. // value when charging.
current_now = std::abs(_current_now_int); current_now = std::abs(_current_now_int);
if (fs::exists(bat / "time_to_empty_now")) { if (std::ifstream f{bat / "time_to_empty_now"}) {
time_to_empty_now_exists = true; if (f >> time_to_empty_now) {
std::ifstream(bat / "time_to_empty_now") >> time_to_empty_now; time_to_empty_now_exists = true;
}
} }
if (fs::exists(bat / "time_to_full_now")) { if (std::ifstream f{bat / "time_to_full_now"}) {
time_to_full_now_exists = true; if (f >> time_to_full_now) {
std::ifstream(bat / "time_to_full_now") >> time_to_full_now; time_to_full_now_exists = true;
}
} }
uint32_t voltage_now = 0; uint32_t voltage_now = 0;
bool voltage_now_exists = false; bool voltage_now_exists = false;
if (fs::exists(bat / "voltage_now")) { if (std::ifstream voltage_now_f{bat / "voltage_now"}) {
voltage_now_exists = true; if (voltage_now_f >> voltage_now) {
std::ifstream(bat / "voltage_now") >> voltage_now; voltage_now_exists = true;
} else if (fs::exists(bat / "voltage_avg")) { }
voltage_now_exists = true; } else if (std::ifstream voltage_avg_f{bat / "voltage_avg"}) {
std::ifstream(bat / "voltage_avg") >> voltage_now; if (voltage_avg_f >> voltage_now) {
voltage_now_exists = true;
}
} }
uint32_t charge_full = 0; uint32_t charge_full = 0;
bool charge_full_exists = false; bool charge_full_exists = false;
if (fs::exists(bat / "charge_full")) { if (std::ifstream f{bat / "charge_full"}) {
charge_full_exists = true; if (f >> charge_full) {
std::ifstream(bat / "charge_full") >> charge_full; charge_full_exists = true;
}
} }
uint32_t charge_full_design = 0; uint32_t charge_full_design = 0;
bool charge_full_design_exists = false; bool charge_full_design_exists = false;
if (fs::exists(bat / "charge_full_design")) { if (std::ifstream f{bat / "charge_full_design"}) {
charge_full_design_exists = true; if (f >> charge_full_design) {
std::ifstream(bat / "charge_full_design") >> charge_full_design; charge_full_design_exists = true;
}
} }
uint32_t charge_now = 0; uint32_t charge_now = 0;
bool charge_now_exists = false; bool charge_now_exists = false;
if (fs::exists(bat / "charge_now")) { if (std::ifstream f{bat / "charge_now"}) {
charge_now_exists = true; if (f >> charge_now) {
std::ifstream(bat / "charge_now") >> charge_now; charge_now_exists = true;
}
} }
uint32_t power_now = 0; uint32_t power_now = 0;
int32_t _power_now_int = 0; int32_t _power_now_int = 0;
bool power_now_exists = false; bool power_now_exists = false;
if (fs::exists(bat / "power_now")) { if (std::ifstream f{bat / "power_now"}) {
power_now_exists = true; if (f >> _power_now_int) {
std::ifstream(bat / "power_now") >> _power_now_int; power_now_exists = true;
}
} }
// Some drivers (example: Qualcomm) exposes use a negative value when // Some drivers (example: Qualcomm) exposes use a negative value when
// discharging, positive value when charging. // discharging, positive value when charging.
@@ -360,28 +362,31 @@ waybar::modules::Battery::getInfos() {
uint32_t energy_now = 0; uint32_t energy_now = 0;
bool energy_now_exists = false; bool energy_now_exists = false;
if (fs::exists(bat / "energy_now")) { if (std::ifstream f{bat / "energy_now"}) {
energy_now_exists = true; if (f >> energy_now) {
std::ifstream(bat / "energy_now") >> energy_now; energy_now_exists = true;
}
} }
uint32_t energy_full = 0; uint32_t energy_full = 0;
bool energy_full_exists = false; bool energy_full_exists = false;
if (fs::exists(bat / "energy_full")) { if (std::ifstream f{bat / "energy_full"}) {
energy_full_exists = true; if (f >> energy_full) {
std::ifstream(bat / "energy_full") >> energy_full; energy_full_exists = true;
}
} }
uint32_t energy_full_design = 0; uint32_t energy_full_design = 0;
bool energy_full_design_exists = false; bool energy_full_design_exists = false;
if (fs::exists(bat / "energy_full_design")) { if (std::ifstream f{bat / "energy_full_design"}) {
energy_full_design_exists = true; if (f >> energy_full_design) {
std::ifstream(bat / "energy_full_design") >> energy_full_design; energy_full_design_exists = true;
}
} }
uint16_t cycleCount = 0; uint16_t cycleCount = 0;
if (fs::exists(bat / "cycle_count")) { if (std::ifstream f{bat / "cycle_count"}) {
std::ifstream(bat / "cycle_count") >> cycleCount; f >> cycleCount;
} }
if (charge_full_design >= largestDesignCapacity) { if (charge_full_design >= largestDesignCapacity) {
largestDesignCapacity = charge_full_design; largestDesignCapacity = charge_full_design;
@@ -411,9 +416,10 @@ waybar::modules::Battery::getInfos() {
} else if (energy_now_exists && energy_full_exists && energy_full != 0) { } else if (energy_now_exists && energy_full_exists && energy_full != 0) {
capacity_exists = true; capacity_exists = true;
capacity = 100 * (uint64_t)energy_now / (uint64_t)energy_full; capacity = 100 * (uint64_t)energy_now / (uint64_t)energy_full;
} else if (fs::exists(bat / "capacity")) { } else if (std::ifstream f{bat / "capacity"}) {
capacity_exists = true; if (f >> capacity) {
std::ifstream(bat / "capacity") >> capacity; capacity_exists = true;
}
} }
if (!voltage_now_exists) { if (!voltage_now_exists) {
@@ -593,11 +599,12 @@ waybar::modules::Battery::getInfos() {
float calculated_capacity{0.0f}; float calculated_capacity{0.0f};
if (total_capacity_exists) { if (total_capacity_exists) {
if (total_capacity > 0.0f) if (total_capacity > 0.0f) {
calculated_capacity = (float)total_capacity / batteries_.size(); calculated_capacity = (float)total_capacity / batteries_.size();
else if (total_energy_full_exists && total_energy_exists) { } else if (total_energy_full_exists && total_energy_exists) {
if (total_energy_full > 0.0f) if (total_energy_full > 0.0f) {
calculated_capacity = ((float)total_energy * 100.0f / (float)total_energy_full); calculated_capacity = ((float)total_energy * 100.0f / (float)total_energy_full);
}
} }
} }
+8 -10
View File
@@ -23,23 +23,21 @@ std::vector<float> waybar::modules::CpuFrequency::parseCpuFrequencies() {
if (frequencies.size() <= 0) { if (frequencies.size() <= 0) {
std::string cpufreq_dir = "/sys/devices/system/cpu/cpufreq"; std::string cpufreq_dir = "/sys/devices/system/cpu/cpufreq";
if (std::filesystem::exists(cpufreq_dir)) { try {
std::vector<std::string> frequency_files = {"/cpuinfo_min_freq", "/cpuinfo_max_freq"}; std::vector<std::string> frequency_files = {"/cpuinfo_min_freq", "/cpuinfo_max_freq"};
for (auto& p : std::filesystem::directory_iterator(cpufreq_dir)) { for (auto& p : std::filesystem::directory_iterator(cpufreq_dir)) {
for (const auto& freq_file : frequency_files) { for (const auto& freq_file : frequency_files) {
std::string freq_file_path = p.path().string() + freq_file; std::string freq_file_path = p.path().string() + freq_file;
if (std::filesystem::exists(freq_file_path)) { std::string freq_value;
std::string freq_value; std::ifstream freq(freq_file_path);
std::ifstream freq(freq_file_path); if (freq.is_open()) {
if (freq.is_open()) { getline(freq, freq_value);
getline(freq, freq_value); float frequency = std::strtol(freq_value.c_str(), nullptr, 10);
float frequency = std::strtol(freq_value.c_str(), nullptr, 10); frequencies.push_back(frequency / 1000);
frequencies.push_back(frequency / 1000);
freq.close();
}
} }
} }
} }
} catch (const std::filesystem::filesystem_error&) {
} }
} }
+5
View File
@@ -39,6 +39,11 @@ waybar::modules::Custom::~Custom() {
} }
void waybar::modules::Custom::delayWorker() { void waybar::modules::Custom::delayWorker() {
if (!config_["exec"].isString() && !config_["exec-if"].isString()) {
dp.emit();
return;
}
thread_ = [this] { thread_ = [this] {
for (int i : this->pid_children_) { for (int i : this->pid_children_) {
int status; int status;
+67
View File
@@ -13,6 +13,7 @@
#include <cerrno> #include <cerrno>
#include <cstring> #include <cstring>
#include <filesystem> #include <filesystem>
#include <optional>
#include <string> #include <string>
#include "util/scoped_fd.hpp" #include "util/scoped_fd.hpp"
@@ -20,6 +21,7 @@
namespace waybar::modules::hyprland { namespace waybar::modules::hyprland {
std::filesystem::path IPC::socketFolder_; std::filesystem::path IPC::socketFolder_;
std::optional<bool> IPC::s_luaProtocolDetected_;
std::filesystem::path IPC::getSocketFolder(const char* instanceSig) { std::filesystem::path IPC::getSocketFolder(const char* instanceSig) {
static std::mutex folderMutex; static std::mutex folderMutex;
@@ -290,4 +292,69 @@ Json::Value IPC::getSocket1JsonReply(const std::string& rq) {
return parser_.parse(reply); return parser_.parse(reply);
} }
bool IPC::isLuaProtocol() {
if (s_luaProtocolDetected_.has_value()) {
return *s_luaProtocolDetected_;
}
// Probe: send a harmless old-style dispatch and check the error.
// In Lua-based Hyprland (>= 0.54) the error contains "hl.dispatch".
// In older versions it returns "ok" or a different error.
auto reply = getSocket1Reply("dispatch workspace __waybar_probe__");
bool luaProto = reply.find("hl.dispatch") != std::string::npos;
if (luaProto) {
spdlog::info("Hyprland IPC: detected Lua-based dispatch protocol (Hyprland >= 0.54)");
} else {
spdlog::info("Hyprland IPC: detected legacy dispatch protocol");
}
s_luaProtocolDetected_ = luaProto;
return luaProto;
}
std::string IPC::buildLuaDispatch(const std::string& dispatcher, const std::string& arg) {
// Map old-style dispatchers to the new Lua hl.dsp API.
//
// Old format: dispatch workspace 1
// New format: /dispatch hl.dsp.focus({ workspace = "1" })
//
// Old format: dispatch focusworkspaceoncurrentmonitor 2
// New format: /dispatch hl.dsp.focus({ workspace = "2", on_current_monitor = true })
//
// Old format: dispatch togglespecialworkspace name
// New format: /dispatch hl.dsp.workspace.toggle_special("name")
if (dispatcher == "workspace") {
return "/dispatch hl.dsp.focus({ workspace = \"" + arg + "\" })";
}
if (dispatcher == "focusworkspaceoncurrentmonitor") {
return "/dispatch hl.dsp.focus({ workspace = \"" + arg + "\", on_current_monitor = true })";
}
if (dispatcher == "togglespecialworkspace") {
if (arg.empty()) {
return "/dispatch hl.dsp.workspace.toggle_special()";
}
return "/dispatch hl.dsp.workspace.toggle_special(\"" + arg + "\")";
}
// Fallback for any other dispatcher: try the old format wrapped in dispatch().
// This may not work for all dispatchers, but it's a reasonable default.
spdlog::warn("Hyprland IPC: unknown dispatcher '{}' in Lua mode, attempting generic format",
dispatcher);
return "/dispatch hl.dsp." + dispatcher + "(\"" + arg + "\")";
}
std::string IPC::dispatch(const std::string& dispatcher, const std::string& arg) {
if (isLuaProtocol()) {
return getSocket1Reply(buildLuaDispatch(dispatcher, arg));
}
// Legacy format: "dispatch <dispatcher> <arg>"
std::string cmd = "dispatch " + dispatcher;
if (!arg.empty()) {
cmd += " " + arg;
}
return getSocket1Reply(cmd);
}
} // namespace waybar::modules::hyprland } // namespace waybar::modules::hyprland
+37 -5
View File
@@ -48,12 +48,39 @@ auto Language::update() -> void {
fmt::arg("shortDescription", layout_.short_description), fmt::arg("shortDescription", layout_.short_description),
fmt::arg("variant", layout_.variant))); fmt::arg("variant", layout_.variant)));
} }
spdlog::debug("hyprland language formatted layout name {}", layoutName); spdlog::debug("hyprland language formatted layout name {}", layoutName);
std::string tooltipContent = std::string{};
bool tooltip_enabled = tooltipEnabled();
if (tooltip_enabled) {
if (config_.isMember("tooltip-format")) {
auto tooltip_format = config_["tooltip-format"].asString();
if (config_.isMember("tooltip-format-" + layout_.short_description + "-" + layout_.variant)) {
const auto propName = "tooltip-format-" + layout_.short_description + "-" + layout_.variant;
tooltipContent = fmt::format(fmt::runtime(tooltip_format), config_[propName].asString());
} else if (config_.isMember("tooltip-format-" + layout_.short_description)) {
const auto propName = "tooltip-format-" + layout_.short_description;
tooltipContent = fmt::format(fmt::runtime(tooltip_format), config_[propName].asString());
} else {
tooltipContent =
trim(fmt::format(fmt::runtime(tooltip_format), fmt::arg("long", layout_.full_name),
fmt::arg("short", layout_.short_name),
fmt::arg("shortDescription", layout_.short_description),
fmt::arg("variant", layout_.variant)));
}
} else {
// if no tooltip format is provided, use the same text as the module
tooltipContent = layoutName;
}
spdlog::debug("hyprland language formatted tooltip content {}", tooltipContent);
}
if (!format_.empty()) { if (!format_.empty()) {
label_.show(); label_.show();
label_.set_markup(layoutName); label_.set_markup(layoutName);
if (tooltip_enabled) {
label_.set_tooltip_markup(tooltipContent);
}
} else { } else {
label_.hide(); label_.hide();
} }
@@ -74,8 +101,6 @@ void Language::onEvent(const std::string& ev) {
spdlog::warn("hyprland language received malformed event payload: {}", ev); spdlog::warn("hyprland language received malformed event payload: {}", ev);
return; return;
} }
std::string kbName = payload.substr(0, kbSeparator);
// Last comma before variants parenthesis, eg: // Last comma before variants parenthesis, eg:
// activelayout>>micro-star-int'l-co.,-ltd.-msi-gk50-elite-gaming-keyboard,English (US, intl., // activelayout>>micro-star-int'l-co.,-ltd.-msi-gk50-elite-gaming-keyboard,English (US, intl.,
// with dead keys) // with dead keys)
@@ -93,8 +118,15 @@ void Language::onEvent(const std::string& ev) {
} }
auto layoutName = payload.substr(layoutSeparator + 1); auto layoutName = payload.substr(layoutSeparator + 1);
if (config_.isMember("keyboard-name") && kbName != config_["keyboard-name"].asString()) if (config_.isMember("keyboard-name")) {
return; // ignore const auto keyboardName = config_["keyboard-name"].asString();
// The keyboard name itself can contain commas, so match it as a full prefix
// (followed by the ',' separator) rather than comparing against the substring
// before the first comma, which would truncate such names and drop the event.
if (payload.size() <= keyboardName.size() || payload[keyboardName.size()] != ',' ||
payload.compare(0, keyboardName.size(), keyboardName) != 0)
return; // ignore
}
layoutName = waybar::util::sanitize_string(layoutName); layoutName = waybar::util::sanitize_string(layoutName);
+6 -12
View File
@@ -108,21 +108,14 @@ auto Window::update() -> void {
AAppIconLabel::update(); AAppIconLabel::update();
} }
auto Window::getActiveWorkspace() -> Workspace { auto Window::getActiveWorkspace() -> Workspace { return getActiveWorkspace(""); }
const auto workspace = IPC::inst().getSocket1JsonReply("activeworkspace");
if (workspace.isObject()) {
return Workspace::parse(workspace);
}
return {};
}
auto Window::getActiveWorkspace(const std::string& monitorName) -> Workspace { auto Window::getActiveWorkspace(const std::string& monitorName) -> Workspace {
const auto monitors = IPC::inst().getSocket1JsonReply("monitors"); const auto monitors = IPC::inst().getSocket1JsonReply("monitors");
if (monitors.isArray()) { if (monitors.isArray()) {
auto monitor = std::ranges::find_if( auto monitor = std::ranges::find_if(monitors, [&](const Json::Value& monitor) {
monitors, [&](const Json::Value& monitor) { return monitor["name"] == monitorName; }); return monitorName.empty() ? monitor["focused"].asBool() : monitor["name"] == monitorName;
});
if (monitor == std::end(monitors)) { if (monitor == std::end(monitors)) {
spdlog::warn("Monitor not found: {}", monitorName); spdlog::warn("Monitor not found: {}", monitorName);
return Workspace{ return Workspace{
@@ -132,7 +125,8 @@ auto Window::getActiveWorkspace(const std::string& monitorName) -> Workspace {
.last_window_title = "", .last_window_title = "",
}; };
} }
const int id = (*monitor)["activeWorkspace"]["id"].asInt(); const int special_id = (*monitor)["specialWorkspace"]["id"].asInt();
const int id = special_id != 0 ? special_id : (*monitor)["activeWorkspace"]["id"].asInt();
const auto workspaces = IPC::inst().getSocket1JsonReply("workspaces"); const auto workspaces = IPC::inst().getSocket1JsonReply("workspaces");
if (workspaces.isArray()) { if (workspaces.isArray()) {
+17 -14
View File
@@ -71,20 +71,20 @@ bool Workspace::handleClicked(GdkEventButton* bt) const {
try { try {
if (id() > 0) { // normal if (id() > 0) { // normal
if (m_workspaceManager.moveToMonitor()) { if (m_workspaceManager.moveToMonitor()) {
m_ipc.getSocket1Reply("dispatch focusworkspaceoncurrentmonitor " + std::to_string(id())); IPC::dispatch("focusworkspaceoncurrentmonitor", std::to_string(id()));
} else { } else {
m_ipc.getSocket1Reply("dispatch workspace " + std::to_string(id())); IPC::dispatch("workspace", std::to_string(id()));
} }
} else if (!isSpecial()) { // named (this includes persistent) } else if (!isSpecial()) { // named (this includes persistent)
if (m_workspaceManager.moveToMonitor()) { if (m_workspaceManager.moveToMonitor()) {
m_ipc.getSocket1Reply("dispatch focusworkspaceoncurrentmonitor name:" + name()); IPC::dispatch("focusworkspaceoncurrentmonitor", "name:" + name());
} else { } else {
m_ipc.getSocket1Reply("dispatch workspace name:" + name()); IPC::dispatch("workspace", "name:" + name());
} }
} else if (id() != -99) { // named special } else if (id() != -99) { // named special
m_ipc.getSocket1Reply("dispatch togglespecialworkspace " + name()); IPC::dispatch("togglespecialworkspace", name());
} else { // special } else { // special
m_ipc.getSocket1Reply("dispatch togglespecialworkspace"); IPC::dispatch("togglespecialworkspace", "");
} }
return true; return true;
} catch (const std::exception& e) { } catch (const std::exception& e) {
@@ -301,15 +301,18 @@ void Workspace::updateTaskbar(const std::string& workspace_icon) {
auto window_box = Gtk::make_managed<Gtk::Box>(Gtk::ORIENTATION_HORIZONTAL); auto window_box = Gtk::make_managed<Gtk::Box>(Gtk::ORIENTATION_HORIZONTAL);
window_box->set_tooltip_markup(window_repr.window_title); window_box->set_tooltip_markup(window_repr.window_title);
window_box->get_style_context()->add_class("taskbar-window");
auto button = Gtk::manage(new Gtk::Button());
button->set_relief(Gtk::RELIEF_NONE);
button->add(*window_box);
button->get_style_context()->add_class("taskbar-window");
if (window_repr.isActive) { if (window_repr.isActive) {
window_box->get_style_context()->add_class("active"); button->get_style_context()->add_class("active");
} }
auto event_box = Gtk::manage(new Gtk::EventBox());
event_box->add(*window_box);
if (m_workspaceManager.onClickWindow() != "") { if (m_workspaceManager.onClickWindow() != "") {
event_box->signal_button_press_event().connect( button->signal_button_press_event().connect(
sigc::bind(sigc::mem_fun(*this, &Workspace::handleClick), window_repr.address)); sigc::bind(sigc::mem_fun(*this, &Workspace::handleClick), window_repr.address),
false);
} }
auto text_before = fmt::format(fmt::runtime(m_workspaceManager.taskbarFormatBefore()), auto text_before = fmt::format(fmt::runtime(m_workspaceManager.taskbarFormatBefore()),
@@ -334,8 +337,8 @@ void Workspace::updateTaskbar(const std::string& workspace_icon) {
window_box->pack_start(*window_label_after, true, true); window_box->pack_start(*window_label_after, true, true);
} }
m_content.pack_start(*event_box, true, false); m_content.pack_start(*button, true, false);
event_box->show_all(); button->show_all();
}; };
if (m_workspaceManager.taskbarReverseDirection()) { if (m_workspaceManager.taskbarReverseDirection()) {
+16 -5
View File
@@ -50,7 +50,8 @@ void Workspaces::init() {
if (m_scrollEventConnection_.connected()) { if (m_scrollEventConnection_.connected()) {
m_scrollEventConnection_.disconnect(); m_scrollEventConnection_.disconnect();
} }
if (barScroll()) { bool hasScrollConfig = config_["on-scroll-up"].isString() || config_["on-scroll-down"].isString();
if (barScroll() || hasScrollConfig) {
auto& window = const_cast<Bar&>(m_bar).window; auto& window = const_cast<Bar&>(m_bar).window;
window.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK); window.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);
m_scrollEventConnection_ = m_scrollEventConnection_ =
@@ -326,6 +327,10 @@ void Workspaces::loadPersistentWorkspacesFromWorkspaceRules(const Json::Value& c
// 2. the rule's monitor is the current monitor // 2. the rule's monitor is the current monitor
// 3. no monitor is specified in the rule => assume it needs to be persistent on every monitor // 3. no monitor is specified in the rule => assume it needs to be persistent on every monitor
if (allOutputs() || m_bar.output->name == monitor || monitor.empty()) { if (allOutputs() || m_bar.output->name == monitor || monitor.empty()) {
// => skip ignore-workspaces even if its a persistent
if(isWorkspaceIgnored(workspace)) {
continue;
}
// => persistent workspace should be shown on this monitor // => persistent workspace should be shown on this monitor
auto workspaceData = createMonitorWorkspaceData(workspace, m_bar.output->name); auto workspaceData = createMonitorWorkspaceData(workspace, m_bar.output->name);
workspaceData["persistent-rule"] = true; workspaceData["persistent-rule"] = true;
@@ -1188,6 +1193,12 @@ bool Workspaces::handleScroll(GdkEventScroll* e) {
if (gdk_event_get_pointer_emulated((GdkEvent*)e)) { if (gdk_event_get_pointer_emulated((GdkEvent*)e)) {
return false; return false;
} }
// Check for custom scroll commands first; delegate to base class
if (config_["on-scroll-up"].isString() || config_["on-scroll-down"].isString()) {
return AModule::handleScroll(e);
}
auto dir = AModule::getScrollDir(e); auto dir = AModule::getScrollDir(e);
if (dir == SCROLL_DIR::NONE) { if (dir == SCROLL_DIR::NONE) {
return true; return true;
@@ -1195,15 +1206,15 @@ bool Workspaces::handleScroll(GdkEventScroll* e) {
if (dir == SCROLL_DIR::DOWN || dir == SCROLL_DIR::RIGHT) { if (dir == SCROLL_DIR::DOWN || dir == SCROLL_DIR::RIGHT) {
if (allOutputs()) { if (allOutputs()) {
m_ipc.getSocket1Reply("dispatch workspace e+1"); IPC::dispatch("workspace", "e+1");
} else { } else {
m_ipc.getSocket1Reply("dispatch workspace m+1"); IPC::dispatch("workspace", "m+1");
} }
} else if (dir == SCROLL_DIR::UP || dir == SCROLL_DIR::LEFT) { } else if (dir == SCROLL_DIR::UP || dir == SCROLL_DIR::LEFT) {
if (allOutputs()) { if (allOutputs()) {
m_ipc.getSocket1Reply("dispatch workspace e-1"); IPC::dispatch("workspace", "e-1");
} else { } else {
m_ipc.getSocket1Reply("dispatch workspace m-1"); IPC::dispatch("workspace", "m-1");
} }
} }
+20 -24
View File
@@ -1,5 +1,18 @@
#include "modules/memory.hpp" #include "modules/memory.hpp"
namespace {
const std::unordered_map<std::string, float> kUnits = {
{"kB", 1.000},
{"kiB", 1.024},
{"MB", 1.000 * 1000.0},
{"MiB", 1.024 * 1024.0},
{"GB", 1.000 * 1000.0 * 1000.0},
{"GiB", 1.024 * 1024.0 * 1024.0},
{"TB", 1.000 * 1000.0 * 1000.0 * 1000.0},
{"TiB", 1.024 * 1024.0 * 1024.0 * 1024.0}
};
}
waybar::modules::Memory::Memory(const std::string& id, const Json::Value& config) waybar::modules::Memory::Memory(const std::string& id, const Json::Value& config)
: ALabel(config, "memory", id, "{}%", 30) { : ALabel(config, "memory", id, "{}%", 30) {
thread_ = [this] { thread_ = [this] {
@@ -8,6 +21,11 @@ waybar::modules::Memory::Memory(const std::string& id, const Json::Value& config
}; };
if (config["unit"].isString()) { if (config["unit"].isString()) {
unit_ = config["unit"].asString(); unit_ = config["unit"].asString();
if (!kUnits.contains(unit_)) {
unit_ = "GiB";
}
} else {
unit_ = "GiB";
} }
} }
@@ -40,7 +58,7 @@ auto waybar::modules::Memory::update() -> void {
used_swap_percentage = 100 * (swaptotal - swapfree) / swaptotal; used_swap_percentage = 100 * (swaptotal - swapfree) / swaptotal;
} }
float divisor = calc_divisor(unit_); float divisor = kUnits.at(unit_);
float total_ram = memtotal / divisor; float total_ram = memtotal / divisor;
float total_swap = swaptotal / divisor; float total_swap = swaptotal / divisor;
float used_ram = (memtotal - memfree) / divisor; float used_ram = (memtotal - memfree) / divisor;
@@ -82,7 +100,7 @@ auto waybar::modules::Memory::update() -> void {
fmt::arg("swapUsed", used_swap), fmt::arg("avail", available_ram), fmt::arg("swapUsed", used_swap), fmt::arg("avail", available_ram),
fmt::arg("swapAvail", available_swap))); fmt::arg("swapAvail", available_swap)));
} else { } else {
label_.set_tooltip_markup(fmt::format("{:.{}f}GiB used", used_ram, 1)); label_.set_tooltip_markup(fmt::format("{:.{}f}{} used", used_ram, 1, unit_));
} }
} }
} else { } else {
@@ -91,25 +109,3 @@ auto waybar::modules::Memory::update() -> void {
// Call parent update // Call parent update
ALabel::update(); ALabel::update();
} }
float waybar::modules::Memory::calc_divisor(const std::string& divisor) {
if (divisor == "kB") {
return 1.0;
} else if (divisor == "kiB") {
return 1.024;
} else if (divisor == "MB") {
return 1.000 * 1000.0;
} else if (divisor == "MiB") {
return 1.024 * 1024.0;
} else if (divisor == "GB") {
return 1.000 * 1000.0 * 1000.0;
} else if (divisor == "GiB") {
return 1.024 * 1024.0 * 1024.0;
} else if (divisor == "TB") {
return 1.000 * 1000.0 * 1000.0 * 1000.0;
} else if (divisor == "TiB") {
return 1.024 * 1024.0 * 1024.0 * 1024.0;
} else { // default to GiB if it is anything that we don't recongnise
return 1.024 * 1024.0 * 1024.0;
}
}
+44 -34
View File
@@ -6,6 +6,7 @@
#include <system_error> #include <system_error>
#include <util/sanitize_str.hpp> #include <util/sanitize_str.hpp>
#include <util/utf8_string.hpp>
using namespace waybar::util; using namespace waybar::util;
#include "modules/mpd/state.hpp" #include "modules/mpd/state.hpp"
@@ -24,7 +25,8 @@ waybar::modules::MPD::MPD(const std::string& id, const Json::Value& config)
timeout_(config_["timeout"].isUInt() ? config_["timeout"].asUInt() * 1'000 : 30'000), timeout_(config_["timeout"].isUInt() ? config_["timeout"].asUInt() * 1'000 : 30'000),
connection_(nullptr, &mpd_connection_free), connection_(nullptr, &mpd_connection_free),
status_(nullptr, &mpd_status_free), status_(nullptr, &mpd_status_free),
song_(nullptr, &mpd_song_free) { song_(nullptr, &mpd_song_free),
ellipsis_(config_["ellipsis"].isString() ? config_["ellipsis"].asString() : "\u2026") {
if (!config_["port"].isNull() && !config_["port"].isUInt()) { if (!config_["port"].isNull() && !config_["port"].isUInt()) {
spdlog::warn("{}: `port` configuration should be an unsigned int", module_name_); spdlog::warn("{}: `port` configuration should be an unsigned int", module_name_);
} }
@@ -51,21 +53,6 @@ auto waybar::modules::MPD::update() -> void {
ALabel::update(); ALabel::update();
} }
void waybar::modules::MPD::queryMPD() {
if (connection_ != nullptr) {
spdlog::trace("{}: fetching state information", module_name_);
try {
fetchState();
spdlog::trace("{}: fetch complete", module_name_);
} catch (std::exception const& e) {
spdlog::error("{}: {}", module_name_, e.what());
state_ = MPD_STATE_UNKNOWN;
}
dp.emit();
}
}
std::string waybar::modules::MPD::getTag(mpd_tag_type type, unsigned idx) const { std::string waybar::modules::MPD::getTag(mpd_tag_type type, unsigned idx) const {
std::string result = std::string result =
config_["unknown-tag"].isString() ? config_["unknown-tag"].asString() : "N/A"; config_["unknown-tag"].isString() ? config_["unknown-tag"].asString() : "N/A";
@@ -117,15 +104,15 @@ void waybar::modules::MPD::setLabel() {
label_.get_style_context()->remove_class("disconnected"); label_.get_style_context()->remove_class("disconnected");
auto format = format_; auto format = format_;
Glib::ustring artist, album_artist, album, title;
std::string date, filename, uri; std::string date, filename, uri;
int song_pos = 0, queue_length = 0, volume = 0; int song_pos = 0, queue_length = 0, volume = 0;
std::chrono::seconds elapsedTime, totalTime; std::chrono::seconds elapsedTime, totalTime;
std::string stateIcon = ""; std::string stateIcon = "";
bool no_song = song_.get() == nullptr; bool no_song = song_.get() == nullptr;
if (stopped() || no_song) { bool is_stopped = stopped();
if (no_song) spdlog::warn("Bug in mpd: no current song but state is not stopped."); if (is_stopped || no_song) {
if (no_song && !is_stopped) spdlog::warn("mpd: no current song while state is not stopped");
format = format =
config_["format-stopped"].isString() ? config_["format-stopped"].asString() : "stopped"; config_["format-stopped"].isString() ? config_["format-stopped"].asString() : "stopped";
label_.get_style_context()->add_class("stopped"); label_.get_style_context()->add_class("stopped");
@@ -145,10 +132,6 @@ void waybar::modules::MPD::setLabel() {
stateIcon = getStateIcon(); stateIcon = getStateIcon();
artist = sanitize_string(getTag(MPD_TAG_ARTIST));
album_artist = sanitize_string(getTag(MPD_TAG_ALBUM_ARTIST));
album = sanitize_string(getTag(MPD_TAG_ALBUM));
title = sanitize_string(getTag(MPD_TAG_TITLE));
date = sanitize_string(getTag(MPD_TAG_DATE)); date = sanitize_string(getTag(MPD_TAG_DATE));
filename = sanitize_string(getFilename()); filename = sanitize_string(getFilename());
uri = mpd_song_get_uri(song_.get()); uri = mpd_song_get_uri(song_.get());
@@ -170,17 +153,12 @@ void waybar::modules::MPD::setLabel() {
std::string repeatIcon = getOptionIcon("repeat", repeatActivated); std::string repeatIcon = getOptionIcon("repeat", repeatActivated);
bool singleActivated = mpd_status_get_single(status_.get()); bool singleActivated = mpd_status_get_single(status_.get());
std::string singleIcon = getOptionIcon("single", singleActivated); std::string singleIcon = getOptionIcon("single", singleActivated);
if (config_["artist-len"].isInt()) artist = artist.substr(0, config_["artist-len"].asInt());
if (config_["album-artist-len"].isInt())
album_artist = album_artist.substr(0, config_["album-artist-len"].asInt());
if (config_["album-len"].isInt()) album = album.substr(0, config_["album-len"].asInt());
if (config_["title-len"].isInt()) title = title.substr(0, config_["title-len"].asInt());
try { try {
auto text = fmt::format( auto text = fmt::format(
fmt::runtime(format), fmt::arg("artist", artist.raw()), fmt::runtime(format), fmt::arg("artist", getArtistStr(true)),
fmt::arg("albumArtist", album_artist.raw()), fmt::arg("album", album.raw()), fmt::arg("albumArtist", getAlbumArtistStr(true)), fmt::arg("album", getAlbumStr(true)),
fmt::arg("title", title.raw()), fmt::arg("date", date), fmt::arg("volume", volume), fmt::arg("title", getTitleStr(true)), fmt::arg("date", date), fmt::arg("volume", volume),
fmt::arg("elapsedTime", elapsedTime), fmt::arg("totalTime", totalTime), fmt::arg("elapsedTime", elapsedTime), fmt::arg("totalTime", totalTime),
fmt::arg("songPosition", song_pos), fmt::arg("queueLength", queue_length), fmt::arg("songPosition", song_pos), fmt::arg("queueLength", queue_length),
fmt::arg("stateIcon", stateIcon), fmt::arg("consumeIcon", consumeIcon), fmt::arg("stateIcon", stateIcon), fmt::arg("consumeIcon", consumeIcon),
@@ -202,9 +180,9 @@ void waybar::modules::MPD::setLabel() {
: "MPD (connected)"; : "MPD (connected)";
try { try {
auto tooltip_text = fmt::format( auto tooltip_text = fmt::format(
fmt::runtime(tooltip_format), fmt::arg("artist", artist.raw()), fmt::runtime(tooltip_format), fmt::arg("artist", getArtistStr(false)),
fmt::arg("albumArtist", album_artist.raw()), fmt::arg("album", album.raw()), fmt::arg("albumArtist", getAlbumArtistStr(false)), fmt::arg("album", getAlbumStr(false)),
fmt::arg("title", title.raw()), fmt::arg("date", date), fmt::arg("volume", volume), fmt::arg("title", getTitleStr(false)), fmt::arg("date", date), fmt::arg("volume", volume),
fmt::arg("elapsedTime", elapsedTime), fmt::arg("totalTime", totalTime), fmt::arg("elapsedTime", elapsedTime), fmt::arg("totalTime", totalTime),
fmt::arg("songPosition", song_pos), fmt::arg("queueLength", queue_length), fmt::arg("songPosition", song_pos), fmt::arg("queueLength", queue_length),
fmt::arg("stateIcon", stateIcon), fmt::arg("consumeIcon", consumeIcon), fmt::arg("stateIcon", stateIcon), fmt::arg("consumeIcon", consumeIcon),
@@ -257,6 +235,38 @@ std::string waybar::modules::MPD::getOptionIcon(const std::string& optionName,
} }
} }
std::string waybar::modules::MPD::getArtistStr(bool truncated) const {
std::string artist = getTag(MPD_TAG_ARTIST);
if (truncated && config_["artist-len"].isInt()) {
waybar::util::utf8_truncate(artist, ellipsis_, config_["artist-len"].asInt());
}
return sanitize_string(artist);
}
std::string waybar::modules::MPD::getAlbumArtistStr(bool truncated) const {
std::string album_artist = getTag(MPD_TAG_ALBUM_ARTIST);
if (truncated && config_["album-artist-len"].isInt()) {
waybar::util::utf8_truncate(album_artist, ellipsis_, config_["album-artist-len"].asInt());
}
return sanitize_string(album_artist);
}
std::string waybar::modules::MPD::getAlbumStr(bool truncated) const {
std::string album = getTag(MPD_TAG_ALBUM);
if (truncated && config_["album-len"].isInt()) {
waybar::util::utf8_truncate(album, ellipsis_, config_["album-len"].asInt());
}
return sanitize_string(album);
}
std::string waybar::modules::MPD::getTitleStr(bool truncated) const {
std::string title = getTag(MPD_TAG_TITLE);
if (truncated && config_["title-len"].isInt()) {
waybar::util::utf8_truncate(title, ellipsis_, config_["title-len"].asInt());
}
return sanitize_string(title);
}
static bool isServerUnavailable(const std::error_code& ec) { static bool isServerUnavailable(const std::error_code& ec) {
if (ec.category() == std::system_category()) { if (ec.category() == std::system_category()) {
switch (ec.value()) { switch (ec.value()) {
-1
View File
@@ -152,7 +152,6 @@ bool Playing::on_timer() {
return false; return false;
} }
ctx_->queryMPD();
ctx_->emit(); ctx_->emit();
} catch (std::exception const& e) { } catch (std::exception const& e) {
spdlog::warn("mpd: Playing: error: {}", e.what()); spdlog::warn("mpd: Playing: error: {}", e.what());
+14 -57
View File
@@ -7,6 +7,7 @@
#include <string> #include <string>
#include "util/scope_guard.hpp" #include "util/scope_guard.hpp"
#include "util/utf8_string.hpp"
extern "C" { extern "C" {
#include <playerctl/playerctl.h> #include <playerctl/playerctl.h>
@@ -202,69 +203,22 @@ auto Mpris::getIconFromJson(const Json::Value& icons, const std::string& key) ->
// Wide characters count as two, zero-width characters count as zero // Wide characters count as two, zero-width characters count as zero
// Modifies str in-place (unless width = std::string::npos) // Modifies str in-place (unless width = std::string::npos)
// Returns the total width of the string pre-truncating // Returns the total width of the string pre-truncating
size_t utf8_truncate(std::string& str, size_t width = std::string::npos) {
if (str.length() == 0) return 0;
const gchar* trunc_end = nullptr;
size_t total_width = 0;
for (gchar *data = str.data(), *end = data + str.size(); data != nullptr;) {
gunichar c = g_utf8_get_char_validated(data, end - data);
if (c == -1U || c == -2U) {
// invalid unicode, treat string as ascii
if (width != std::string::npos && str.length() > width) str.resize(width);
return str.length();
} else if (g_unichar_iswide(c)) {
total_width += 2;
} else if (!g_unichar_iszerowidth(c) && c != 0xAD) { // neither zero-width nor soft hyphen
total_width += 1;
}
data = g_utf8_find_next_char(data, end);
if (width != std::string::npos && total_width <= width && !g_unichar_isspace(c))
trunc_end = data;
}
if (trunc_end) str.resize(trunc_end - str.data());
return total_width;
}
size_t utf8_width(const std::string& str) { return utf8_truncate(const_cast<std::string&>(str)); }
void truncate(std::string& s, const std::string& ellipsis, size_t max_len) {
if (max_len == 0) {
s.resize(0);
return;
}
size_t len = utf8_truncate(s, max_len);
if (len > max_len) {
size_t ellipsis_len = utf8_width(ellipsis);
if (max_len >= ellipsis_len) {
if (ellipsis_len) utf8_truncate(s, max_len - ellipsis_len);
s += ellipsis;
} else {
s.resize(0);
}
}
}
auto Mpris::getArtistStr(const PlayerInfo& info, bool truncated) -> std::string { auto Mpris::getArtistStr(const PlayerInfo& info, bool truncated) -> std::string {
auto artist = info.artist.value_or(std::string()); auto artist = info.artist.value_or(std::string());
if (truncated && artist_len_ >= 0) truncate(artist, ellipsis_, artist_len_); if (truncated && artist_len_ >= 0) waybar::util::utf8_truncate(artist, ellipsis_, artist_len_);
return artist; return artist;
} }
auto Mpris::getAlbumStr(const PlayerInfo& info, bool truncated) -> std::string { auto Mpris::getAlbumStr(const PlayerInfo& info, bool truncated) -> std::string {
auto album = info.album.value_or(std::string()); auto album = info.album.value_or(std::string());
if (truncated && album_len_ >= 0) truncate(album, ellipsis_, album_len_); if (truncated && album_len_ >= 0) waybar::util::utf8_truncate(album, ellipsis_, album_len_);
return album; return album;
} }
auto Mpris::getTitleStr(const PlayerInfo& info, bool truncated) -> std::string { auto Mpris::getTitleStr(const PlayerInfo& info, bool truncated) -> std::string {
auto title = info.title.value_or(std::string()); auto title = info.title.value_or(std::string());
if (truncated && title_len_ >= 0) truncate(title, ellipsis_, title_len_); if (truncated && title_len_ >= 0) waybar::util::utf8_truncate(title, ellipsis_, title_len_);
return title; return title;
} }
@@ -292,9 +246,9 @@ auto Mpris::getDynamicStr(const PlayerInfo& info, bool truncated, bool html) ->
// keep position format same as length format // keep position format same as length format
auto position = getPositionStr(info, truncated && truncate_hours_ && length.length() < 6); auto position = getPositionStr(info, truncated && truncate_hours_ && length.length() < 6);
size_t artistLen = utf8_width(artist); size_t artistLen = waybar::util::utf8_width(artist);
size_t albumLen = utf8_width(album); size_t albumLen = waybar::util::utf8_width(album);
size_t titleLen = utf8_width(title); size_t titleLen = waybar::util::utf8_width(title);
size_t lengthLen = length.length(); size_t lengthLen = length.length();
size_t posLen = position.length(); size_t posLen = position.length();
@@ -313,7 +267,7 @@ auto Mpris::getDynamicStr(const PlayerInfo& info, bool truncated, bool html) ->
// Since the first element doesn't present a separator and we don't know a priori which one // Since the first element doesn't present a separator and we don't know a priori which one
// it will be, we add a "virtual separatorLen" to the dynamicLen, since we are adding the // it will be, we add a "virtual separatorLen" to the dynamicLen, since we are adding the
// separatorLen to all the other lengths. // separatorLen to all the other lengths.
size_t separatorLen = utf8_width(dynamic_separator_); size_t separatorLen = waybar::util::utf8_width(dynamic_separator_);
size_t dynamicLen = dynamic_len_ + separatorLen; size_t dynamicLen = dynamic_len_ + separatorLen;
if (showArtist) artistLen += separatorLen; if (showArtist) artistLen += separatorLen;
if (showAlbum) albumLen += separatorLen; if (showAlbum) albumLen += separatorLen;
@@ -775,9 +729,12 @@ auto Mpris::update() -> void {
auto tooltip_text = fmt::format( auto tooltip_text = fmt::format(
fmt::runtime(tooltipstr), fmt::arg("player", info.name), fmt::runtime(tooltipstr), fmt::arg("player", info.name),
fmt::arg("status", info.status_string), fmt::arg("status", info.status_string),
fmt::arg("artist", getArtistStr(info, tooltip_len_limits_)), fmt::arg("artist",
fmt::arg("title", getTitleStr(info, tooltip_len_limits_)), std::string(Glib::Markup::escape_text(getArtistStr(info, tooltip_len_limits_)))),
fmt::arg("album", getAlbumStr(info, tooltip_len_limits_)), fmt::arg("title",
std::string(Glib::Markup::escape_text(getTitleStr(info, tooltip_len_limits_)))),
fmt::arg("album",
std::string(Glib::Markup::escape_text(getAlbumStr(info, tooltip_len_limits_)))),
fmt::arg("length", tooltipLength), fmt::arg("position", tooltipPosition), fmt::arg("length", tooltipLength), fmt::arg("position", tooltipPosition),
fmt::arg("dynamic", getDynamicStr(info, tooltip_len_limits_, false)), fmt::arg("dynamic", getDynamicStr(info, tooltip_len_limits_, false)),
fmt::arg("player_icon", getIconFromJson(config_["player-icons"], info.name)), fmt::arg("player_icon", getIconFromJson(config_["player-icons"], info.name)),
+3 -1
View File
@@ -103,7 +103,9 @@ void Workspaces::doUpdate() {
fmt::arg("output", ws["output"].asString())); fmt::arg("output", ws["output"].asString()));
} }
if (!config_["disable-markup"].asBool()) { if (!config_["disable-markup"].asBool()) {
static_cast<Gtk::Label*>(button.get_children()[0])->set_markup(name); auto* child = gtk_bin_get_child(GTK_BIN(button.gobj()));
if (child != nullptr && GTK_IS_LABEL(child))
gtk_label_set_markup(GTK_LABEL(child), name.c_str());
} else { } else {
button.set_label(name); button.set_label(name);
} }
+2 -3
View File
@@ -71,6 +71,7 @@ Privacy::Privacy(const std::string& id, const Json::Value& config, Gtk::Orientat
auto* item = Gtk::make_managed<PrivacyItem>(module, nodeType, nodePtr, orientation, pos, auto* item = Gtk::make_managed<PrivacyItem>(module, nodeType, nodePtr, orientation, pos,
iconSize, transition_duration); iconSize, transition_duration);
box_.add(*item); box_.add(*item);
modules_.push_back(item);
} }
} }
@@ -148,9 +149,7 @@ auto Privacy::update() -> void {
bool useAudioOut = false; bool useAudioOut = false;
mutex_.lock(); mutex_.lock();
for (Gtk::Widget* widget : box_.get_children()) { for (PrivacyItem* module : modules_) {
auto* module = dynamic_cast<PrivacyItem*>(widget);
if (module == nullptr) continue;
switch (module->privacy_type) { switch (module->privacy_type) {
case util::PipewireBackend::PRIVACY_NODE_TYPE_VIDEO_INPUT: case util::PipewireBackend::PRIVACY_NODE_TYPE_VIDEO_INPUT:
setScreenshare = true; setScreenshare = true;
+5 -3
View File
@@ -178,9 +178,11 @@ void Host::addRegisteredItem(const std::string& service) {
return bus_name == item->bus_name && object_path == item->object_path; return bus_name == item->bus_name && object_path == item->object_path;
}); });
if (it == items_.end()) { if (it == items_.end()) {
items_.emplace_back(new Item( items_.emplace_back(std::make_unique<Item>(
bus_name, object_path, config_, bar_, [this](Item& item) { itemReady(item); }, bus_name, object_path, config_, bar_,
[this](Item& item) { itemInvalidated(item); }, on_update_)); [this](Item& item) { itemReady(item); },
[this](Item& item) { itemInvalidated(item); },
on_update_));
} }
} }
+12 -2
View File
@@ -190,9 +190,17 @@ void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) {
} else if (name == "Status") { } else if (name == "Status") {
setStatus(get_variant<Glib::ustring>(value)); setStatus(get_variant<Glib::ustring>(value));
} else if (name == "IconName") { } else if (name == "IconName") {
icon_name = get_variant<std::string>(value); if (has_custom_icon_) {
spdlog::trace("Item '{}': ignoring IconName update, custom icon is set", id);
} else {
icon_name = get_variant<std::string>(value);
}
} else if (name == "IconPixmap") { } else if (name == "IconPixmap") {
icon_pixmap = this->extractPixBuf(value.gobj()); if (has_custom_icon_) {
spdlog::trace("Item '{}': ignoring IconPixmap update, custom icon is set", id);
} else {
icon_pixmap = this->extractPixBuf(value.gobj());
}
} else if (name == "OverlayIconName") { } else if (name == "OverlayIconName") {
overlay_icon_name = get_variant<std::string>(value); overlay_icon_name = get_variant<std::string>(value);
} else if (name == "OverlayIconPixmap") { } else if (name == "OverlayIconPixmap") {
@@ -270,11 +278,13 @@ void Item::setCustomIcon(const std::string& id) {
Glib::RefPtr<Gdk::Pixbuf> custom_pixbuf = Gdk::Pixbuf::create_from_file(custom_icon); Glib::RefPtr<Gdk::Pixbuf> custom_pixbuf = Gdk::Pixbuf::create_from_file(custom_icon);
icon_name = ""; // icon_name has priority over pixmap icon_name = ""; // icon_name has priority over pixmap
icon_pixmap = custom_pixbuf; icon_pixmap = custom_pixbuf;
has_custom_icon_ = true;
} catch (const Glib::Error& e) { } catch (const Glib::Error& e) {
spdlog::error("Failed to load custom icon {}: {}", custom_icon, e.what()); spdlog::error("Failed to load custom icon {}: {}", custom_icon, e.what());
} }
} else { // if file doesn't exist it's most likely an icon_name } else { // if file doesn't exist it's most likely an icon_name
icon_name = custom_icon; icon_name = custom_icon;
has_custom_icon_ = true;
} }
} }
} }
+4 -13
View File
@@ -24,9 +24,6 @@ Tray::Tray(const std::string& id, const Bar& bar, const Json::Value& config)
if (config_["spacing"].isUInt()) { if (config_["spacing"].isUInt()) {
box_.set_spacing(config_["spacing"].asUInt()); box_.set_spacing(config_["spacing"].asUInt());
} }
if (config["show-passive-items"].isBool()) {
show_passive_ = config["show-passive-items"].asBool();
}
nb_hosts_ += 1; nb_hosts_ += 1;
if (config_["icons"].isObject()) { if (config_["icons"].isObject()) {
IconManager::instance().setIconsConfig(config_["icons"]); IconManager::instance().setIconsConfig(config_["icons"]);
@@ -42,6 +39,8 @@ void Tray::onAdd(std::unique_ptr<Item>& item) {
} else { } else {
box_.pack_start(item->event_box); box_.pack_start(item->event_box);
} }
item->event_box.signal_show().connect([this] { dp.emit(); });
item->event_box.signal_hide().connect([this] { dp.emit(); });
dp.emit(); dp.emit();
} }
@@ -51,17 +50,9 @@ void Tray::onRemove(std::unique_ptr<Item>& item) {
} }
auto Tray::update() -> void { auto Tray::update() -> void {
// Show tray only when items are available
std::vector<Gtk::Widget*> children = box_.get_children(); std::vector<Gtk::Widget*> children = box_.get_children();
if (show_passive_) { event_box_.set_visible(std::any_of(children.begin(), children.end(),
event_box_.set_visible(!children.empty()); [](Gtk::Widget* child) { return child->get_visible(); }));
} else {
event_box_.set_visible(!std::all_of(children.begin(), children.end(), [](Gtk::Widget* child) {
return child->get_style_context()->has_class("passive");
}));
}
// Call parent update
AModule::update(); AModule::update();
} }
+42 -15
View File
@@ -54,21 +54,28 @@ void Language::onCmd(const struct Ipc::ipc_response& res) {
std::lock_guard<std::mutex> lock(mutex_); std::lock_guard<std::mutex> lock(mutex_);
auto payload = parser_.parse(res.payload); auto payload = parser_.parse(res.payload);
std::vector<std::string> used_layouts; std::vector<std::string> used_layouts;
// Display current layout of a device with a maximum count of layouts, expecting that all will // Union layout names across every keyboard input so hot-plugged devices contribute their
// be OK // layouts to the map. Track the device with the most layouts to seed the initially displayed
// layout, matching the previous behaviour at startup.
Json::ArrayIndex max_id = 0, max = 0; Json::ArrayIndex max_id = 0, max = 0;
for (Json::ArrayIndex i = 0; i < payload.size(); i++) { for (Json::ArrayIndex i = 0; i < payload.size(); i++) {
auto size = payload[i][XKB_LAYOUT_NAMES_KEY].size(); if (payload[i]["type"].asString() != "keyboard") continue;
if (size > max) { const auto& names = payload[i][XKB_LAYOUT_NAMES_KEY];
max = size; if (names.size() > max) {
max = names.size();
max_id = i; max_id = i;
} }
for (const auto& layout : names) {
const auto name = layout.asString();
if (std::find(used_layouts.begin(), used_layouts.end(), name) == used_layouts.end()) {
used_layouts.push_back(name);
}
}
} }
for (const auto& layout : payload[max_id][XKB_LAYOUT_NAMES_KEY]) { // Rebuild from scratch so init_layouts_map's duplicate-suffix pass doesn't compound across
used_layouts.push_back(layout.asString()); // refreshes (e.g. "us" -> "us1" -> "us11").
} layouts_map_.clear();
init_layouts_map(used_layouts); init_layouts_map(used_layouts);
set_current_layout(payload[max_id][XKB_ACTIVE_LAYOUT_NAME_KEY].asString()); set_current_layout(payload[max_id][XKB_ACTIVE_LAYOUT_NAME_KEY].asString());
dp.emit(); dp.emit();
@@ -82,13 +89,26 @@ void Language::onEvent(const struct Ipc::ipc_response& res) {
return; return;
} }
bool refresh_inputs = false;
try { try {
std::lock_guard<std::mutex> lock(mutex_); {
auto payload = parser_.parse(res.payload)["input"]; std::lock_guard<std::mutex> lock(mutex_);
if (payload["type"].asString() == "keyboard") { auto root = parser_.parse(res.payload);
set_current_layout(payload[XKB_ACTIVE_LAYOUT_NAME_KEY].asString()); auto change = root["change"].asString();
auto payload = root["input"];
if (payload["type"].asString() == "keyboard") {
// A device was added or its keymap changed - the layout set may have grown, so refresh
// layouts_map_ via IPC_GET_INPUTS once we've released mutex_.
refresh_inputs = (change == "added" || change == "xkb_keymap");
set_current_layout(payload[XKB_ACTIVE_LAYOUT_NAME_KEY].asString());
}
dp.emit();
}
// sendCmd is synchronous: it blocks on the IPC reply and then emits signal_cmd on this same
// thread, which lands in onCmd and re-locks mutex_. Must call it with mutex_ released.
if (refresh_inputs) {
ipc_.sendCmd(IPC_GET_INPUTS);
} }
dp.emit();
} catch (const std::exception& e) { } catch (const std::exception& e) {
spdlog::error("Language: {}", e.what()); spdlog::error("Language: {}", e.what());
} }
@@ -125,8 +145,15 @@ auto Language::update() -> void {
} }
auto Language::set_current_layout(const std::string& current_layout) -> void { auto Language::set_current_layout(const std::string& current_layout) -> void {
// Guard against unknown / empty layout names: transient virtual keyboards (e.g. wtype) and
// hot-plugged devices whose layouts haven't made it into the map yet would otherwise blank out
// layout_ via map::operator[]'s default-construct-on-miss.
auto it = layouts_map_.find(current_layout);
if (it == layouts_map_.end()) {
return;
}
label_.get_style_context()->remove_class(layout_.short_name); label_.get_style_context()->remove_class(layout_.short_name);
layout_ = layouts_map_[current_layout]; layout_ = it->second;
label_.get_style_context()->add_class(layout_.short_name); label_.get_style_context()->add_class(layout_.short_name);
} }
+12 -6
View File
@@ -229,7 +229,10 @@ bool Workspaces::filterButtons() {
auto ws = std::find_if(workspaces_.begin(), workspaces_.end(), auto ws = std::find_if(workspaces_.begin(), workspaces_.end(),
[it](const auto& node) { return node["name"].asString() == it->first; }); [it](const auto& node) { return node["name"].asString() == it->first; });
if (ws == workspaces_.end() || if (ws == workspaces_.end() ||
(!config_["all-outputs"].asBool() && (*ws)["output"].asString() != bar_.output->name)) { ((*ws).isMember("target_output") ? (*ws)["target_output"].asString() != bar_.output->name &&
(*ws)["target_output"].asString() != ""
: !config_["all-outputs"].asBool() &&
(*ws)["output"].asString() != bar_.output->name)) {
it = buttons_.erase(it); it = buttons_.erase(it);
needReorder = true; needReorder = true;
} else { } else {
@@ -369,11 +372,14 @@ Gtk::Button& Workspaces::addButton(const Json::Value& node) {
node["name"].asString(), node["target_output"].asString(), node["name"].asString(), node["target_output"].asString(),
"--no-auto-back-and-forth", node["name"].asString())); "--no-auto-back-and-forth", node["name"].asString()));
} else { } else {
ipc_.sendCmd(IPC_COMMAND, fmt::format("workspace {} \"{}\"", std::string flag = config_["disable-auto-back-and-forth"].asBool()
config_["disable-auto-back-and-forth"].asBool() ? "--no-auto-back-and-forth"
? "--no-auto-back-and-forth" : "";
: "", if (node["num"].asInt() >= 0) {
node["name"].asString())); ipc_.sendCmd(IPC_COMMAND, fmt::format(workspace_switch_number_cmd_, flag, node["num"].asInt()));
} else {
ipc_.sendCmd(IPC_COMMAND, fmt::format(workspace_switch_cmd_, flag, node["name"].asString()));
}
} }
} catch (const std::exception& e) { } catch (const std::exception& e) {
spdlog::error("Workspaces: {}", e.what()); spdlog::error("Workspaces: {}", e.what());
+7 -2
View File
@@ -84,6 +84,9 @@ SystemdFailedUnits::SystemdFailedUnits(const std::string& id, const Json::Value&
} }
} }
if (!user_props_proxy_ && !system_props_proxy_)
throw std::runtime_error("Neither system nor user status is requested.");
updateData(); updateData();
/* Always update for the first time. */ /* Always update for the first time. */
dp.emit(); dp.emit();
@@ -103,7 +106,7 @@ auto SystemdFailedUnits::notify_cb(const Glib::ustring& sender_name,
void SystemdFailedUnits::RequestSystemState() { void SystemdFailedUnits::RequestSystemState() {
auto load = [](const char* kind, Glib::RefPtr<Gio::DBus::Proxy>& proxy) -> std::string { auto load = [](const char* kind, Glib::RefPtr<Gio::DBus::Proxy>& proxy) -> std::string {
try { try {
if (!proxy) return "unknown"; if (!proxy) return "ignored";
auto parameters = Glib::VariantContainerBase( auto parameters = Glib::VariantContainerBase(
g_variant_new("(ss)", "org.freedesktop.systemd1.Manager", "SystemState")); g_variant_new("(ss)", "org.freedesktop.systemd1.Manager", "SystemState"));
Glib::VariantContainerBase data = proxy->call_sync("Get", parameters); Glib::VariantContainerBase data = proxy->call_sync("Get", parameters);
@@ -122,7 +125,9 @@ void SystemdFailedUnits::RequestSystemState() {
system_state_ = load("systemwide", system_props_proxy_); system_state_ = load("systemwide", system_props_proxy_);
user_state_ = load("user", user_props_proxy_); user_state_ = load("user", user_props_proxy_);
if (system_state_ == "running" && user_state_ == "running")
if ((system_state_ == "running" || system_state_ == "ignored") &&
(user_state_ == "running" || user_state_ == "ignored"))
overall_state_ = "ok"; overall_state_ = "ok";
else else
overall_state_ = "degraded"; overall_state_ = "degraded";
+4
View File
@@ -155,3 +155,7 @@ bool waybar::modules::Temperature::isCritical(uint16_t temperature_c) {
return config_["critical-threshold"].isInt() && return config_["critical-threshold"].isInt() &&
temperature_c >= config_["critical-threshold"].asInt(); temperature_c >= config_["critical-threshold"].asInt();
} }
void waybar::modules::Temperature::suspend() { thread_.pause(); }
void waybar::modules::Temperature::resume() { thread_.resume(); }
+7 -4
View File
@@ -347,10 +347,13 @@ auto IPC::update_state_handler(const std::string& event, const Json::Value& data
if (event == "output-wset-changed") { if (event == "output-wset-changed") {
// data: { event, new-wset: wset.name, output: id, new-wset-data: wset, output-data: output } // data: { event, new-wset: wset.name, output: id, new-wset-data: wset, output-data: output }
auto& output = state.outputs.at(data["output-data"]["name"].asString()); try {
auto wset_idx = data["new-wset-data"]["index"].asUInt(); auto& output = state.outputs.at(data["output-data"]["name"].asString());
state.wsets.at(wset_idx).output = output; auto wset_idx = data["new-wset-data"]["index"].asUInt();
output.wset_idx = wset_idx; state.wsets.at(wset_idx).output = output;
output.wset_idx = wset_idx;
} catch (const std::exception&) {
}
return; return;
} }
+10 -8
View File
@@ -99,15 +99,17 @@ void AudioBackend::contextStateCb(pa_context* c, void* data) {
nullptr, nullptr); nullptr, nullptr);
break; break;
case PA_CONTEXT_FAILED: case PA_CONTEXT_FAILED:
// When pulseaudio server restarts, the connection is "failed". Try to reconnect. if (pa_context_errno(c) != PA_ERR_CONNECTIONREFUSED) {
// pa_threaded_mainloop_lock is already acquired in callback threads. // When pulseaudio server restarts, the connection is "failed". Try to reconnect.
// So there is no need to lock it again. // pa_threaded_mainloop_lock is already acquired in callback threads.
if (backend->context_ != nullptr) { // So there is no need to lock it again.
pa_context_disconnect(backend->context_); if (backend->context_ != nullptr) {
pa_context_unref(backend->context_); pa_context_disconnect(backend->context_);
backend->context_ = nullptr; pa_context_unref(backend->context_);
backend->context_ = nullptr;
}
backend->connectContext();
} }
backend->connectContext();
break; break;
case PA_CONTEXT_CONNECTING: case PA_CONTEXT_CONNECTING:
case PA_CONTEXT_AUTHORIZING: case PA_CONTEXT_AUTHORIZING:
+62
View File
@@ -0,0 +1,62 @@
#include <glib.h>
#include <string>
#include <util/utf8_string.hpp>
namespace waybar::util {
namespace {
// Wide characters count as two, zero-width characters count as zero
// Modifies str in-place (unless width = std::string::npos)
// Returns the total width of the string pre-truncating
size_t measure_and_truncate(std::string& str, size_t width = std::string::npos) {
if (str.length() == 0) return 0;
const gchar* trunc_end = nullptr;
size_t total_width = 0;
for (gchar *data = str.data(), *end = data + str.size(); data != nullptr;) {
gunichar c = g_utf8_get_char_validated(data, end - data);
if (c == -1U || c == -2U) {
// invalid unicode, treat string as ascii
if (width != std::string::npos && str.length() > width) str.resize(width);
return str.length();
} else if (g_unichar_iswide(c)) {
total_width += 2;
} else if (!g_unichar_iszerowidth(c) && c != 0xAD) { // neither zero-width nor soft hyphen
total_width += 1;
}
data = g_utf8_find_next_char(data, end);
if (width != std::string::npos && total_width <= width && !g_unichar_isspace(c))
trunc_end = data;
}
if (trunc_end) str.resize(trunc_end - str.data());
return total_width;
}
} // namespace
size_t utf8_width(const std::string& str) {
return measure_and_truncate(const_cast<std::string&>(str));
}
void utf8_truncate(std::string& s, const std::string& ellipsis, size_t max_len) {
if (max_len == 0) {
s.resize(0);
return;
}
size_t len = measure_and_truncate(s, max_len);
if (len > max_len) {
size_t ellipsis_len = utf8_width(ellipsis);
if (max_len >= ellipsis_len) {
if (ellipsis_len) measure_and_truncate(s, max_len - ellipsis_len);
s += ellipsis;
} else {
s.resize(0);
}
}
}
} // namespace waybar::util
+79
View File
@@ -15,6 +15,10 @@ namespace {
class IPCTestHelper : public hyprland::IPC { class IPCTestHelper : public hyprland::IPC {
public: public:
static void resetSocketFolder() { socketFolder_.clear(); } static void resetSocketFolder() { socketFolder_.clear(); }
static void resetLuaProtocolDetection() { s_luaProtocolDetected_.reset(); }
static void setLuaProtocolDetected(bool value) { s_luaProtocolDetected_ = value; }
using hyprland::IPC::buildLuaDispatch;
using hyprland::IPC::isLuaProtocol;
}; };
std::size_t countOpenFds() { std::size_t countOpenFds() {
@@ -133,3 +137,78 @@ TEST_CASE("getSocket1Reply failure paths do not leak fds", "[getSocket1Reply][fd
REQUIRE(after_connect_failures == baseline); REQUIRE(after_connect_failures == baseline);
} }
#endif #endif
// --- Tests for new Lua IPC dispatch functions ---
TEST_CASE("buildLuaDispatch workspace", "[buildLuaDispatch]") {
SECTION("numeric workspace") {
auto result = IPCTestHelper::buildLuaDispatch("workspace", "1");
REQUIRE(result == "/dispatch hl.dsp.focus({ workspace = \"1\" })");
}
SECTION("named workspace") {
auto result = IPCTestHelper::buildLuaDispatch("workspace", "name:term");
REQUIRE(result == "/dispatch hl.dsp.focus({ workspace = \"name:term\" })");
}
SECTION("relative workspace") {
auto result = IPCTestHelper::buildLuaDispatch("workspace", "e+1");
REQUIRE(result == "/dispatch hl.dsp.focus({ workspace = \"e+1\" })");
}
}
TEST_CASE("buildLuaDispatch focusworkspaceoncurrentmonitor", "[buildLuaDispatch]") {
auto result =
IPCTestHelper::buildLuaDispatch("focusworkspaceoncurrentmonitor", "3");
REQUIRE(
result ==
"/dispatch hl.dsp.focus({ workspace = \"3\", on_current_monitor = true })");
}
TEST_CASE("buildLuaDispatch togglespecialworkspace", "[buildLuaDispatch]") {
SECTION("with name") {
auto result =
IPCTestHelper::buildLuaDispatch("togglespecialworkspace", "scratchpad");
REQUIRE(result ==
"/dispatch hl.dsp.workspace.toggle_special(\"scratchpad\")");
}
SECTION("empty arg") {
auto result =
IPCTestHelper::buildLuaDispatch("togglespecialworkspace", "");
REQUIRE(result == "/dispatch hl.dsp.workspace.toggle_special()");
}
}
TEST_CASE("buildLuaDispatch unknown dispatcher fallback", "[buildLuaDispatch]") {
auto result =
IPCTestHelper::buildLuaDispatch("unknown_dispatcher", "some_arg");
REQUIRE(result ==
"/dispatch hl.dsp.unknown_dispatcher(\"some_arg\")");
}
TEST_CASE("dispatch throws when Hyprland is not running", "[dispatch]") {
unsetenv("HYPRLAND_INSTANCE_SIGNATURE");
IPCTestHelper::resetSocketFolder();
IPCTestHelper::resetLuaProtocolDetection();
CHECK_THROWS(hyprland::IPC::dispatch("workspace", "1"));
}
TEST_CASE("isLuaProtocol uses cached value and avoids socket call",
"[isLuaProtocol]") {
unsetenv("HYPRLAND_INSTANCE_SIGNATURE");
IPCTestHelper::resetSocketFolder();
SECTION("cached false") {
IPCTestHelper::setLuaProtocolDetected(false);
// Should return false without throwing (no socket call needed)
REQUIRE(IPCTestHelper::isLuaProtocol() == false);
}
SECTION("cached true") {
IPCTestHelper::setLuaProtocolDetected(true);
// Should return true without throwing (no socket call needed)
REQUIRE(IPCTestHelper::isLuaProtocol() == true);
}
// Cleanup: reset detection so other tests aren't affected
IPCTestHelper::resetLuaProtocolDetection();
}