Merge branch 'master' into patch-1
This commit is contained in:
@@ -52,3 +52,4 @@ result-*
|
||||
|
||||
.ccls-cache
|
||||
_codeql_detected_source_root
|
||||
heaptrack*
|
||||
|
||||
Binary file not shown.
@@ -25,6 +25,10 @@ class AModule : public IModule {
|
||||
|
||||
bool expandEnabled() const;
|
||||
|
||||
virtual void suspend() {};
|
||||
virtual void resume() {};
|
||||
bool shouldSuspend() const { return disable_on_sleep_; }
|
||||
|
||||
protected:
|
||||
// Don't need to make an object directly
|
||||
// Derived classes are able to use it
|
||||
@@ -48,6 +52,8 @@ class AModule : public IModule {
|
||||
virtual bool handleMouseLeave(GdkEventCrossing* const& ev);
|
||||
virtual bool handleScroll(GdkEventScroll*);
|
||||
virtual bool handleRelease(GdkEventButton* const& ev);
|
||||
|
||||
bool disable_on_sleep_{false};
|
||||
GObject* menu_ = nullptr;
|
||||
|
||||
private:
|
||||
@@ -57,6 +63,7 @@ class AModule : public IModule {
|
||||
bool hasUserEvents_;
|
||||
gdouble distance_scrolled_y_;
|
||||
gdouble distance_scrolled_x_;
|
||||
sigc::connection cursor_timeout_conn_;
|
||||
std::map<std::string, std::string> eventActionMap_;
|
||||
static const inline std::map<std::pair<uint, GdkEventType>, std::string> eventMap_{
|
||||
{std::make_pair(1, GdkEventType::GDK_BUTTON_PRESS), "on-click"},
|
||||
|
||||
@@ -75,6 +75,8 @@ class Bar : public sigc::trackable {
|
||||
util::KillSignalAction getOnSigusr1Action();
|
||||
util::KillSignalAction getOnSigusr2Action();
|
||||
|
||||
void toggleSuspend(bool suspend);
|
||||
|
||||
struct waybar_output* output;
|
||||
Json::Value config;
|
||||
struct wl_surface* surface;
|
||||
|
||||
@@ -44,6 +44,7 @@ class Client {
|
||||
const char* interface, uint32_t version);
|
||||
static void handleGlobalRemove(void* data, struct wl_registry* registry, uint32_t name);
|
||||
static void handleOutputDone(void*, struct zxdg_output_v1*);
|
||||
void createBarsBatch();
|
||||
static void handleOutputName(void*, struct zxdg_output_v1*, const char*);
|
||||
static void handleOutputDescription(void*, struct zxdg_output_v1*, const char*);
|
||||
void handleMonitorAdded(Glib::RefPtr<Gdk::Monitor> monitor);
|
||||
@@ -58,6 +59,8 @@ class Client {
|
||||
std::string m_cssFile;
|
||||
sigc::connection monitor_added_connection_;
|
||||
sigc::connection monitor_removed_connection_;
|
||||
std::list<waybar_output*> pending_outputs_;
|
||||
bool bars_scheduled_ = false;
|
||||
};
|
||||
|
||||
} // namespace waybar
|
||||
|
||||
@@ -26,7 +26,7 @@ class Gamemode : public AModule {
|
||||
const std::string DEFAULT_FORMAT = "{glyph}";
|
||||
const std::string DEFAULT_FORMAT_ALT = "{glyph} {count}";
|
||||
const std::string DEFAULT_TOOLTIP_FORMAT = "Games running: {count}";
|
||||
const std::string DEFAULT_GLYPH = "";
|
||||
const std::string DEFAULT_GLYPH = "";
|
||||
|
||||
void appear(const Glib::RefPtr<Gio::DBus::Connection>& connection, const Glib::ustring& name,
|
||||
const Glib::ustring& name_owner);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <filesystem>
|
||||
#include <list>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
@@ -35,9 +36,22 @@ class IPC {
|
||||
Json::Value getSocket1JsonReply(const std::string& rq);
|
||||
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:
|
||||
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:
|
||||
void socketListener();
|
||||
void parseIPC(const std::string&);
|
||||
|
||||
@@ -30,6 +30,7 @@ class Workspace {
|
||||
public:
|
||||
explicit Workspace(const Json::Value& workspace_data, Workspaces& workspace_manager,
|
||||
const Json::Value& clients_data = Json::Value::nullRef);
|
||||
~Workspace();
|
||||
std::string& selectIcon(std::map<std::string, std::string>& icons_map);
|
||||
Gtk::Button& button() { return m_button; };
|
||||
|
||||
@@ -45,6 +46,15 @@ class Workspace {
|
||||
bool isUrgent() const { return m_isUrgent; };
|
||||
|
||||
bool handleClicked(GdkEventButton* bt) const;
|
||||
|
||||
bool handleEnter(GdkEventCrossing* event);
|
||||
bool handleLeave(GdkEventCrossing* event);
|
||||
|
||||
void startHoverCheck();
|
||||
void stopHoverCheck();
|
||||
bool syncHoverClass();
|
||||
bool pointerInsideButton();
|
||||
|
||||
void setActive(bool value = true) { m_isActive = value; };
|
||||
void setPersistentRule(bool value = true) { m_isPersistentRule = value; };
|
||||
void setPersistentConfig(bool value = true) { m_isPersistentConfig = value; };
|
||||
@@ -80,6 +90,8 @@ class Workspace {
|
||||
bool m_isUrgent = false;
|
||||
bool m_isVisible = false;
|
||||
|
||||
sigc::connection m_hoverCheckConnection;
|
||||
|
||||
std::vector<WindowRepr> m_windowMap;
|
||||
|
||||
Gtk::Button m_button;
|
||||
|
||||
@@ -22,6 +22,8 @@ class Memory : public ALabel {
|
||||
std::unordered_map<std::string, unsigned long> meminfo_;
|
||||
|
||||
util::SleeperThread thread_;
|
||||
|
||||
std::string unit_;
|
||||
};
|
||||
|
||||
} // namespace waybar::modules
|
||||
|
||||
@@ -33,6 +33,7 @@ class MPD : public ALabel {
|
||||
detail::unique_status status_;
|
||||
mpd_state state_;
|
||||
detail::unique_song song_;
|
||||
std::string ellipsis_;
|
||||
|
||||
public:
|
||||
MPD(const std::string&, const Json::Value&);
|
||||
@@ -45,6 +46,10 @@ class MPD : public ALabel {
|
||||
void setLabel();
|
||||
std::string getStateIcon() 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
|
||||
bool handlePlayPause(GdkEventButton* const&);
|
||||
@@ -54,7 +59,6 @@ class MPD : public ALabel {
|
||||
void tryConnect();
|
||||
void checkErrors(mpd_connection* conn);
|
||||
void fetchState();
|
||||
void queryMPD();
|
||||
|
||||
inline bool stopped() const { return connection_ && state_ == MPD_STATE_STOP; }
|
||||
inline bool playing() const { return connection_ && state_ == MPD_STATE_PLAY; }
|
||||
|
||||
@@ -197,7 +197,6 @@ class Context {
|
||||
void tryConnect() const;
|
||||
void checkErrors(mpd_connection*) const;
|
||||
void do_update();
|
||||
void queryMPD() const;
|
||||
void fetchState() const;
|
||||
constexpr mpd_state state() const;
|
||||
void emit() const;
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
namespace detail {
|
||||
using namespace std::literals::chrono_literals;
|
||||
|
||||
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_paused() const { return mpd_module_->paused(); }
|
||||
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 unique_connection& Context::connection() { return mpd_module_->connection_; }
|
||||
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::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::emit() const { mpd_module_->emit(); }
|
||||
|
||||
|
||||
@@ -70,6 +70,9 @@ class Network : public ALabel {
|
||||
|
||||
unsigned long long bandwidth_down_total_{0};
|
||||
unsigned long long bandwidth_up_total_{0};
|
||||
unsigned long long bandwidth_down_prev_{0};
|
||||
unsigned long long bandwidth_up_prev_{0};
|
||||
std::chrono::steady_clock::time_point bandwidth_last_sample_time_;
|
||||
|
||||
std::string state_;
|
||||
std::string essid_;
|
||||
|
||||
@@ -28,6 +28,7 @@ class Privacy : public AModule {
|
||||
|
||||
// Config
|
||||
Gtk::Box box_;
|
||||
std::vector<PrivacyItem*> modules_;
|
||||
uint iconSpacing = 4;
|
||||
uint iconSize = 20;
|
||||
uint transition_duration = 250;
|
||||
|
||||
@@ -43,6 +43,8 @@ class Host {
|
||||
std::size_t watcher_id_;
|
||||
GCancellable* cancellable_ = nullptr;
|
||||
SnWatcher* watcher_ = nullptr;
|
||||
sigc::connection retry_connection_;
|
||||
unsigned retry_count_ = 0;
|
||||
const Json::Value& config_;
|
||||
const Bar& bar_;
|
||||
const std::function<void(std::unique_ptr<Item>&)> on_add_;
|
||||
|
||||
@@ -46,6 +46,7 @@ class Item : public sigc::trackable {
|
||||
std::string title;
|
||||
std::string icon_name;
|
||||
Glib::RefPtr<Gdk::Pixbuf> icon_pixmap;
|
||||
bool has_custom_icon_ = false;
|
||||
Glib::RefPtr<Gtk::IconTheme> icon_theme;
|
||||
std::string overlay_icon_name;
|
||||
Glib::RefPtr<Gdk::Pixbuf> overlay_icon_pixmap;
|
||||
|
||||
@@ -22,7 +22,6 @@ class Tray : public AModule {
|
||||
void queueUpdate();
|
||||
|
||||
static inline std::size_t nb_hosts_ = 0;
|
||||
bool show_passive_ = false;
|
||||
Gtk::Box box_;
|
||||
SNI::Watcher::singleton watcher_;
|
||||
SNI::Host host_;
|
||||
|
||||
@@ -24,6 +24,7 @@ class Workspaces : public AModule, public sigc::trackable {
|
||||
|
||||
private:
|
||||
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_ =
|
||||
R"(workspace {} "{}"; move workspace to output "{}"; workspace {} "{}")";
|
||||
|
||||
|
||||
@@ -14,6 +14,8 @@ class Temperature : public ALabel {
|
||||
Temperature(const std::string&, const Json::Value&);
|
||||
virtual ~Temperature() = default;
|
||||
auto update() -> void override;
|
||||
void suspend() override;
|
||||
void resume() override;
|
||||
|
||||
private:
|
||||
float getTemperature();
|
||||
|
||||
@@ -12,11 +12,6 @@
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#ifdef __OpenBSD__
|
||||
#define SIGRTMIN SIGUSR1 - 1
|
||||
#define SIGRTMAX SIGUSR1 + 1
|
||||
#endif
|
||||
|
||||
namespace waybar {
|
||||
|
||||
/**
|
||||
|
||||
@@ -14,12 +14,14 @@ struct pollfd;
|
||||
namespace waybar {
|
||||
class CssReloadHelper {
|
||||
public:
|
||||
CssReloadHelper(std::string cssFile, std::function<void()> callback);
|
||||
CssReloadHelper(std::string cssFile, std::function<void(const std::string&)> callback);
|
||||
|
||||
virtual ~CssReloadHelper() = default;
|
||||
|
||||
virtual void monitorChanges();
|
||||
|
||||
virtual void changeCssFile(const std::string& newCssFile);
|
||||
|
||||
protected:
|
||||
std::vector<std::string> parseImports(const std::string& cssFile);
|
||||
|
||||
@@ -42,7 +44,7 @@ class CssReloadHelper {
|
||||
private:
|
||||
std::string m_cssFile;
|
||||
|
||||
std::function<void()> m_callback;
|
||||
std::function<void(const std::string&)> m_callback;
|
||||
|
||||
std::vector<std::tuple<Glib::RefPtr<Gio::FileMonitor>>> m_fileMonitors;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
/**
|
||||
* Result of transforming 8-bit hex codes to rgba().
|
||||
*/
|
||||
struct TransformResult {
|
||||
std::string css;
|
||||
bool was_transformed;
|
||||
};
|
||||
|
||||
/**
|
||||
* Reads a CSS file, searches for 8-bit hex codes (#RRGGBBAA),
|
||||
* and transforms them into GTK-compatible rgba() syntax.
|
||||
*/
|
||||
TransformResult transform_8bit_to_hex(const std::string& file_path);
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <codecvt>
|
||||
#include <iostream>
|
||||
#include <locale>
|
||||
#include <memory>
|
||||
#include <regex>
|
||||
|
||||
#if (FMT_VERSION >= 90000)
|
||||
@@ -26,14 +27,19 @@ class JsonParser {
|
||||
Json::Value root;
|
||||
|
||||
// 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;
|
||||
// Use local CharReaderBuilder for thread safety - the IPC singleton's
|
||||
// parser can be called concurrently from multiple module threads
|
||||
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);
|
||||
}
|
||||
return root;
|
||||
|
||||
@@ -79,6 +79,12 @@ class SleeperThread {
|
||||
auto sleep_for(std::chrono::system_clock::duration dur) {
|
||||
std::unique_lock lk(mutex_);
|
||||
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();
|
||||
auto wait_end = max_time_point;
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
@@ -95,6 +101,12 @@ class SleeperThread {
|
||||
time_point) {
|
||||
std::unique_lock lk(mutex_);
|
||||
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 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() {
|
||||
connection_.disconnect();
|
||||
stop();
|
||||
@@ -137,6 +160,7 @@ class SleeperThread {
|
||||
std::atomic<bool> do_run_ = true;
|
||||
std::atomic<bool> signal_ = false;
|
||||
sigc::connection connection_;
|
||||
bool is_paused_{false};
|
||||
};
|
||||
|
||||
} // namespace waybar::util
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
|
||||
namespace waybar::util {
|
||||
size_t utf8_width(const std::string& str);
|
||||
void utf8_truncate(std::string& s, const std::string& ellipsis, size_t max_len);
|
||||
} // namespace waybar::util
|
||||
@@ -27,6 +27,11 @@ The *battery* module displays the current capacity and state (eg. charging) of y
|
||||
default: false ++
|
||||
Option to use the battery design capacity instead of its current maximal capacity.
|
||||
|
||||
*full-at-plugged*: ++
|
||||
typeof: bool ++
|
||||
default: false ++
|
||||
When enabled, a battery that is *Full* while the adapter is online is reported with the *Plugged* status instead of *Full* (so you can style/format it separately). Disabled by default to preserve the existing *Full* behaviour.
|
||||
|
||||
*interval*: ++
|
||||
typeof: integer ++
|
||||
default: 60 ++
|
||||
|
||||
@@ -204,7 +204,7 @@ View all valid format options in *strftime(3)* or have a look https://en.cpprefe
|
||||
```
|
||||
"clock": {
|
||||
"format": "{:%H:%M} ",
|
||||
"format-alt": "{:%A, %B %d, %Y (%R)} ",
|
||||
"format-alt": "{:%A, %B %d, %Y (%R)} ",
|
||||
"tooltip-format": "<tt><small>{calendar}</small></tt>",
|
||||
"calendar": {
|
||||
"mode" : "year",
|
||||
@@ -299,7 +299,7 @@ Example of working config
|
||||
```
|
||||
"clock": {
|
||||
"format": "{:%H:%M} ",
|
||||
"format-alt": "{:%A, %B %d, %Y (%R)} ",
|
||||
"format-alt": "{:%A, %B %d, %Y (%R)} ",
|
||||
"tooltip-format": "\n<span size='9pt' font='WenQuanYi Zen Hei Mono'>{calendar}</span>",
|
||||
"calendar": {
|
||||
"mode" : "year",
|
||||
|
||||
@@ -43,7 +43,7 @@ Feral Gamemode optimizations.
|
||||
|
||||
*glyph*: ++
|
||||
typeof: string ++
|
||||
default: ++
|
||||
default: ++
|
||||
The string icon to display. Only visible if *use-icon* is set to false.
|
||||
|
||||
*icon-name*: ++
|
||||
@@ -82,7 +82,7 @@ Feral Gamemode optimizations.
|
||||
"gamemode": {
|
||||
"format": "{glyph}",
|
||||
"format-alt": "{glyph} {count}",
|
||||
"glyph": "",
|
||||
"glyph": "",
|
||||
"hide-not-running": true,
|
||||
"use-icon": true,
|
||||
"icon-name": "input-gaming-symbolic",
|
||||
|
||||
@@ -38,6 +38,20 @@ Addressed by *hyprland/language*
|
||||
typeof: array ++
|
||||
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*: ++
|
||||
typeof: bool ++
|
||||
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
|
||||
|
||||
- *#language*
|
||||
|
||||
@@ -135,6 +135,14 @@ This setting is ignored if *workspace-taskbar.enable* is set to true.
|
||||
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.
|
||||
|
||||
*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*: ++
|
||||
typeof: array ++
|
||||
default: [] ++
|
||||
|
||||
+12
-6
@@ -102,23 +102,29 @@ Addressed by *memory*
|
||||
default: false ++
|
||||
Enables this module to consume all left over space dynamically.
|
||||
|
||||
*unit*: ++
|
||||
typeof: string ++
|
||||
default: GiB ++
|
||||
Used to specify unit for total, swapTotal, used, swapUsed, avail, swapAvail,
|
||||
and swapState. Accepts B, kB, kiB, MB, MiB, GB, GiB, TB, and TiB.
|
||||
|
||||
# FORMAT REPLACEMENTS
|
||||
|
||||
*{percentage}*: Percentage of memory in use.
|
||||
|
||||
*{swapPercentage}*: Percentage of swap in use.
|
||||
|
||||
*{total}*: Amount of total memory available in GiB.
|
||||
*{total}*: Amount of total memory available. Defaults to GiB.
|
||||
|
||||
*{swapTotal}*: Amount of total swap available in GiB.
|
||||
*{swapTotal}*: Amount of total swap available. Defaults to GiB.
|
||||
|
||||
*{used}*: Amount of used memory in GiB.
|
||||
*{used}*: Amount of used memory. Defaults to GiB.
|
||||
|
||||
*{swapUsed}*: Amount of used swap in GiB.
|
||||
*{swapUsed}*: Amount of used swap. Defaults to GiB.
|
||||
|
||||
*{avail}*: Amount of available memory in GiB.
|
||||
*{avail}*: Amount of available memory. Defaults to GiB.
|
||||
|
||||
*{swapAvail}*: Amount of available swap in GiB.
|
||||
*{swapAvail}*: Amount of available swap. Defaults to GiB.
|
||||
|
||||
*{swapState}*: Signals if swap is activated or not
|
||||
|
||||
|
||||
@@ -178,8 +178,8 @@ to be selected when the corresponding audio device is muted. This applies to *de
|
||||
"alsa_output.pci-0000_00_1f.3.analog-stereo": "",
|
||||
"alsa_output.pci-0000_00_1f.3.analog-stereo-muted": "",
|
||||
"headphone": "",
|
||||
"hands-free": "",
|
||||
"headset": "",
|
||||
"hands-free": "",
|
||||
"headset": "",
|
||||
"phone": "",
|
||||
"phone-muted": "",
|
||||
"portable": "",
|
||||
|
||||
@@ -87,7 +87,7 @@ Addressed by *river/mode*
|
||||
|
||||
```
|
||||
"river/mode": {
|
||||
"format": " {}"
|
||||
"format": " {}"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@ Addressed by *sway/mode*
|
||||
|
||||
```
|
||||
"sway/mode": {
|
||||
"format": " {}",
|
||||
"format": " {}",
|
||||
"max-length": 50
|
||||
}
|
||||
```
|
||||
|
||||
@@ -128,7 +128,7 @@ The *wireplumber* module displays the current volume reported by WirePlumber.
|
||||
```
|
||||
"wireplumber#sink": {
|
||||
"format": "{volume}% {icon}",
|
||||
"format-muted": "",
|
||||
"format-muted": "",
|
||||
"format-icons": ["", "", ""],
|
||||
"on-click": "helvum",
|
||||
"on-click-right": "wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle",
|
||||
|
||||
@@ -364,6 +364,11 @@ A group may hide all but one element, showing them only on mouse hover. In order
|
||||
default: false ++
|
||||
Whether left click should reveal the content rather than mouse over. Note that grouped modules may still process their own on-click events.
|
||||
|
||||
*start-expanded*: ++
|
||||
typeof: bool ++
|
||||
default: false ++
|
||||
Defines whether the drawer should initialize in an expanded state.
|
||||
|
||||
*transition-left-to-right*: ++
|
||||
typeof: bool ++
|
||||
default: true ++
|
||||
|
||||
+3
-1
@@ -185,7 +185,9 @@ src_files = files(
|
||||
'src/util/gtk_icon.cpp',
|
||||
'src/util/icon_loader.cpp',
|
||||
'src/util/regex_collection.cpp',
|
||||
'src/util/css_reload_helper.cpp'
|
||||
'src/util/css_reload_helper.cpp',
|
||||
'src/util/transform_8bit_to_rgba.cpp',
|
||||
'src/util/utf8_string.cpp'
|
||||
)
|
||||
|
||||
man_files = files(
|
||||
|
||||
+3
-3
@@ -6,13 +6,13 @@
|
||||
}:
|
||||
let
|
||||
libcava = rec {
|
||||
version = "0.10.7-beta";
|
||||
version = "0.10.7";
|
||||
src = pkgs.fetchFromGitHub {
|
||||
owner = "LukashonakV";
|
||||
repo = "cava";
|
||||
# NOTE: Needs to match the cava.wrap
|
||||
tag = "v${version}";
|
||||
hash = "sha256-IX1B375gTwVDRjpRfwKGuzTAZOV2pgDWzUd4bW2cTDU=";
|
||||
tag = "${version}";
|
||||
hash = "sha256-zkyj1vBzHtoypX4Bxdh1Vmwh967DKKxN751v79hzmgQ=";
|
||||
};
|
||||
};
|
||||
in
|
||||
|
||||
@@ -128,7 +128,7 @@
|
||||
"critical-threshold": 80,
|
||||
// "format-critical": "{temperatureC}°C {icon}",
|
||||
"format": "{temperatureC}°C {icon}",
|
||||
"format-icons": ["", "", ""]
|
||||
"format-icons": ["", "", ""]
|
||||
},
|
||||
"backlight": {
|
||||
// "device": "acpi_video1",
|
||||
@@ -143,7 +143,7 @@
|
||||
},
|
||||
"format": "{capacity}% {icon}",
|
||||
"format-full": "{capacity}% {icon}",
|
||||
"format-charging": "{capacity}% ",
|
||||
"format-charging": "{capacity}% ",
|
||||
"format-plugged": "{capacity}% ",
|
||||
"format-alt": "{time} {icon}",
|
||||
// "format-good": "", // An empty format will hide the module
|
||||
@@ -167,9 +167,9 @@
|
||||
"network": {
|
||||
// "interface": "wlp2*", // (Optional) To force the use of this interface
|
||||
"format-wifi": "{essid} ({signalStrength}%) ",
|
||||
"format-ethernet": "{ipaddr}/{cidr} ",
|
||||
"tooltip-format": "{ifname} via {gwaddr} ",
|
||||
"format-linked": "{ifname} (No IP) ",
|
||||
"format-ethernet": "{ipaddr}/{cidr} ",
|
||||
"tooltip-format": "{ifname} via {gwaddr} ",
|
||||
"format-linked": "{ifname} (No IP) ",
|
||||
"format-disconnected": "Disconnected ⚠",
|
||||
"format-alt": "{ifname}: {ipaddr}/{cidr}"
|
||||
},
|
||||
@@ -177,14 +177,14 @@
|
||||
// "scroll-step": 1, // %, can be a float
|
||||
"format": "{volume}% {icon} {format_source}",
|
||||
"format-bluetooth": "{volume}% {icon} {format_source}",
|
||||
"format-bluetooth-muted": " {icon} {format_source}",
|
||||
"format-muted": " {format_source}",
|
||||
"format-bluetooth-muted": " {icon} {format_source}",
|
||||
"format-muted": " {format_source}",
|
||||
"format-source": "{volume}% ",
|
||||
"format-source-muted": "",
|
||||
"format-icons": {
|
||||
"headphone": "",
|
||||
"hands-free": "",
|
||||
"headset": "",
|
||||
"hands-free": "",
|
||||
"headset": "",
|
||||
"phone": "",
|
||||
"portable": "",
|
||||
"car": "",
|
||||
|
||||
@@ -112,17 +112,19 @@ class PlayerManager:
|
||||
logger.debug(f"Metadata changed for player {player.props.player_name}")
|
||||
player_name = player.props.player_name
|
||||
artist = player.get_artist()
|
||||
artist = artist.replace("&", "&")
|
||||
artist = artist and artist.replace("&", "&")
|
||||
title = player.get_title()
|
||||
title = title.replace("&", "&")
|
||||
title = title and title.replace("&", "&")
|
||||
|
||||
track_info = ""
|
||||
if player_name == "spotify" and "mpris:trackid" in metadata.keys() and ":ad:" in player.props.metadata["mpris:trackid"]:
|
||||
track_info = "Advertisement"
|
||||
elif artist is not None and title is not None:
|
||||
track_info = f"{artist} - {title}"
|
||||
else:
|
||||
elif title is not None:
|
||||
track_info = title
|
||||
elif artist is not None:
|
||||
track_info = artist
|
||||
|
||||
if track_info:
|
||||
if player.props.status == "Playing":
|
||||
|
||||
@@ -33,9 +33,7 @@ std::string toLowerCase(const std::string& input) {
|
||||
|
||||
std::optional<std::string> getFileBySuffix(const std::string& dir, const std::string& suffix,
|
||||
bool check_lower_case) {
|
||||
if (!std::filesystem::exists(dir)) {
|
||||
return {};
|
||||
}
|
||||
try {
|
||||
for (const auto& entry : std::filesystem::recursive_directory_iterator(dir)) {
|
||||
if (entry.is_regular_file()) {
|
||||
std::string filename = entry.path().filename().string();
|
||||
@@ -49,6 +47,8 @@ std::optional<std::string> getFileBySuffix(const std::string& dir, const std::st
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (const std::filesystem::filesystem_error&) {
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
+14
-3
@@ -17,10 +17,14 @@ AModule::AModule(const Json::Value& config, const std::string& name, const std::
|
||||
isTooltip{config_["tooltip"].isBool() ? config_["tooltip"].asBool() : true},
|
||||
isExpand{config_["expand"].isBool() ? config_["expand"].asBool() : false},
|
||||
distance_scrolled_y_(0.0),
|
||||
distance_scrolled_x_(0.0) {
|
||||
distance_scrolled_x_(0.0),
|
||||
cursor_timeout_conn_() {
|
||||
// Configure module action Map
|
||||
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) {
|
||||
if (it.key().isString() && it->isString())
|
||||
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
|
||||
if (config_.isMember("cursor")) {
|
||||
if (config_["cursor"].isBool() && config_["cursor"].asBool()) {
|
||||
if (config_["cursor"].isBool()) {
|
||||
if (config_["cursor"].asBool()) {
|
||||
setCursor(Gdk::HAND2);
|
||||
} else {
|
||||
setCursor(Gdk::ARROW);
|
||||
}
|
||||
} else if (config_["cursor"].isInt()) {
|
||||
setCursor(Gdk::CursorType(config_["cursor"].asInt()));
|
||||
} else {
|
||||
@@ -83,6 +91,9 @@ AModule::AModule(const Json::Value& config, const std::string& name, const std::
|
||||
}
|
||||
|
||||
AModule::~AModule() {
|
||||
if (cursor_timeout_conn_.connected()) {
|
||||
cursor_timeout_conn_.disconnect();
|
||||
}
|
||||
for (const auto& pid : pid_children_) {
|
||||
if (pid != -1) {
|
||||
killpg(pid, SIGTERM);
|
||||
@@ -118,7 +129,7 @@ void AModule::setCursor(Gdk::CursorType const& c) {
|
||||
} else {
|
||||
// window may not be accessible yet, in this case,
|
||||
// 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]() {
|
||||
setCursor(c);
|
||||
return false;
|
||||
|
||||
+35
-4
@@ -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_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 (auto ipc = config["ipc"]; ipc.isBool() && ipc.asBool()) {
|
||||
bar_id = Client::inst()->bar_id;
|
||||
@@ -541,13 +551,15 @@ void waybar::Bar::getModules(const Factory& factory, const std::string& pos,
|
||||
auto vertical = (group != nullptr ? group->getBox().get_orientation()
|
||||
: box_.get_orientation()) == Gtk::ORIENTATION_VERTICAL;
|
||||
|
||||
auto group_config = config[ref];
|
||||
const Json::Value& group_config = config[ref];
|
||||
if (group_config["modules"].isNull()) {
|
||||
spdlog::warn("Group definition '{}' has not been found, group will be hidden", ref);
|
||||
}
|
||||
auto* group_module = new waybar::Group(id_name, class_name, group_config, vertical);
|
||||
getModules(factory, ref, group_module);
|
||||
module = group_module;
|
||||
auto group_module =
|
||||
std::make_unique<waybar::Group>(id_name, class_name, group_config, vertical);
|
||||
|
||||
getModules(factory, ref, group_module.get());
|
||||
module = group_module.release();
|
||||
} else {
|
||||
module = factory.makeModule(ref, pos);
|
||||
}
|
||||
@@ -694,3 +706,22 @@ void waybar::Bar::configureGlobalOffset(int width, int height) {
|
||||
void waybar::Bar::onOutputGeometryChanged() {
|
||||
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_);
|
||||
}
|
||||
|
||||
+47
-7
@@ -10,6 +10,7 @@
|
||||
#include "idle-inhibit-unstable-v1-client-protocol.h"
|
||||
#include "util/clara.hpp"
|
||||
#include "util/format.hpp"
|
||||
#include "util/hex_checker.hpp"
|
||||
|
||||
waybar::Client* waybar::Client::inst() {
|
||||
static auto* c = new Client();
|
||||
@@ -19,11 +20,23 @@ waybar::Client* waybar::Client::inst() {
|
||||
void waybar::Client::handleGlobal(void* data, struct wl_registry* registry, uint32_t name,
|
||||
const char* interface, uint32_t version) {
|
||||
auto* client = static_cast<Client*>(data);
|
||||
|
||||
if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0 &&
|
||||
version >= ZXDG_OUTPUT_V1_NAME_SINCE_VERSION) {
|
||||
if (client->xdg_output_manager != nullptr) {
|
||||
zxdg_output_manager_v1_destroy(client->xdg_output_manager);
|
||||
client->xdg_output_manager = nullptr;
|
||||
}
|
||||
|
||||
client->xdg_output_manager = static_cast<struct zxdg_output_manager_v1*>(wl_registry_bind(
|
||||
registry, name, &zxdg_output_manager_v1_interface, ZXDG_OUTPUT_V1_NAME_SINCE_VERSION));
|
||||
|
||||
} else if (strcmp(interface, zwp_idle_inhibit_manager_v1_interface.name) == 0) {
|
||||
if (client->idle_inhibit_manager != nullptr) {
|
||||
zwp_idle_inhibit_manager_v1_destroy(client->idle_inhibit_manager);
|
||||
client->idle_inhibit_manager = nullptr;
|
||||
}
|
||||
|
||||
client->idle_inhibit_manager = static_cast<struct zwp_idle_inhibit_manager_v1*>(
|
||||
wl_registry_bind(registry, name, &zwp_idle_inhibit_manager_v1_interface, 1));
|
||||
}
|
||||
@@ -78,11 +91,14 @@ void waybar::Client::handleOutputDone(void* data, struct zxdg_output_v1* /*xdg_o
|
||||
output.xdg_output.reset();
|
||||
spdlog::debug("Output detection done: {} ({})", output.name, output.identifier);
|
||||
|
||||
auto configs = client->getOutputConfigs(output);
|
||||
if (!configs.empty()) {
|
||||
for (const auto& config : configs) {
|
||||
client->bars.emplace_back(std::make_unique<Bar>(&output, config));
|
||||
}
|
||||
client->pending_outputs_.push_back(&output);
|
||||
|
||||
if (!client->bars_scheduled_) {
|
||||
client->bars_scheduled_ = true;
|
||||
|
||||
Glib::signal_idle().connect_once([client]() {
|
||||
client->createBarsBatch();
|
||||
}, Glib::PRIORITY_HIGH_IDLE);
|
||||
}
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
@@ -90,6 +106,25 @@ void waybar::Client::handleOutputDone(void* data, struct zxdg_output_v1* /*xdg_o
|
||||
}
|
||||
}
|
||||
|
||||
void waybar::Client::createBarsBatch() {
|
||||
pending_outputs_.remove_if([this](auto* output) { return std::none_of(outputs_.begin(), outputs_.end(), [&output](const auto& o) { return &o == output; }); });
|
||||
for (auto* output : pending_outputs_) {
|
||||
try {
|
||||
auto configs = getOutputConfigs(*output);
|
||||
if (!configs.empty()) {
|
||||
for (const auto& config : configs) {
|
||||
bars.emplace_back(std::make_unique<Bar>(output, config));
|
||||
}
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
spdlog::warn("Error creating bar: {}", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
pending_outputs_.clear();
|
||||
bars_scheduled_ = false;
|
||||
}
|
||||
|
||||
void waybar::Client::handleOutputName(void* data, struct zxdg_output_v1* /*xdg_output*/,
|
||||
const char* name) {
|
||||
auto* client = waybar::Client::inst();
|
||||
@@ -194,11 +229,15 @@ auto waybar::Client::setupCss(const std::string& css_file) -> void {
|
||||
}
|
||||
|
||||
css_provider_ = Gtk::CssProvider::create();
|
||||
auto [modified_css, was_transformed] = transform_8bit_to_hex(css_file);
|
||||
if (was_transformed) {
|
||||
css_provider_->load_from_data(modified_css);
|
||||
} else {
|
||||
if (!css_provider_->load_from_path(css_file)) {
|
||||
css_provider_.reset();
|
||||
throw std::runtime_error("Can't open style file");
|
||||
}
|
||||
|
||||
}
|
||||
Gtk::StyleContext::add_provider_for_screen(screen, css_provider_,
|
||||
GTK_STYLE_PROVIDER_PRIORITY_USER);
|
||||
}
|
||||
@@ -289,9 +328,10 @@ int waybar::Client::main(int argc, char* argv[]) {
|
||||
}
|
||||
m_cssFile = getStyle(style_opt);
|
||||
setupCss(m_cssFile);
|
||||
m_cssReloadHelper = std::make_unique<CssReloadHelper>(m_cssFile, [&]() { setupCss(m_cssFile); });
|
||||
m_cssReloadHelper = std::make_unique<CssReloadHelper>(m_cssFile, [&](const std::string& css_file) { setupCss(css_file); });
|
||||
portal->signal_appearance_changed().connect([&](waybar::Appearance appearance) {
|
||||
auto css_file = getStyle(style_opt, appearance);
|
||||
m_cssReloadHelper->changeCssFile(css_file);
|
||||
setupCss(css_file);
|
||||
});
|
||||
|
||||
|
||||
+9
-1
@@ -64,11 +64,19 @@ Group::Group(const std::string& name, const std::string& id, const Json::Value&
|
||||
: true);
|
||||
click_to_reveal = drawer_config["click-to-reveal"].asBool();
|
||||
|
||||
const bool start_expanded =
|
||||
(drawer_config["start-expanded"].isBool() ? drawer_config["start-expanded"].asBool()
|
||||
: false);
|
||||
|
||||
auto transition_type = getPreferredTransitionType(vertical);
|
||||
|
||||
revealer.set_transition_type(transition_type);
|
||||
revealer.set_transition_duration(transition_duration);
|
||||
revealer.set_reveal_child(false);
|
||||
revealer.set_reveal_child(start_expanded);
|
||||
|
||||
if (start_expanded) {
|
||||
box.set_state_flags(Gtk::StateFlags::STATE_FLAG_PRELIGHT);
|
||||
}
|
||||
|
||||
revealer.get_style_context()->add_class("drawer");
|
||||
|
||||
|
||||
@@ -55,9 +55,11 @@ static void catchSignals(waybar::SafeSignal<int>& signal_handler) {
|
||||
std::signal(SIGINT, writeSignalToPipe);
|
||||
std::signal(SIGCHLD, writeSignalToPipe);
|
||||
|
||||
#ifdef SIGRTMIN
|
||||
for (int sig = SIGRTMIN + 1; sig <= SIGRTMAX; ++sig) {
|
||||
std::signal(sig, writeSignalToPipe);
|
||||
}
|
||||
#endif
|
||||
|
||||
while (true) {
|
||||
int signum;
|
||||
@@ -119,12 +121,14 @@ void handleUserSignal(int signal, bool& reload) {
|
||||
// If this signal should restart or close the bar, this function will write
|
||||
// `true` or `false`, respectively, into `reload`.
|
||||
static void handleSignalMainThread(int signum, bool& reload) {
|
||||
#ifdef SIGRTMIN
|
||||
if (signum >= SIGRTMIN + 1 && signum <= SIGRTMAX) {
|
||||
for (auto& bar : waybar::Client::inst()->bars) {
|
||||
bar->handleSignal(signum);
|
||||
}
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
switch (signum) {
|
||||
case SIGUSR1:
|
||||
|
||||
+68
-47
@@ -117,17 +117,16 @@ void waybar::modules::Battery::refreshBatteries() {
|
||||
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() / "uevent") &&
|
||||
(fs::exists(node.path() / "status") || bat_compatibility) &&
|
||||
fs::exists(node.path() / "type")) {
|
||||
(fs::exists(node.path() / "status") || bat_compatibility)) {
|
||||
std::string type;
|
||||
std::ifstream(node.path() / "type") >> type;
|
||||
|
||||
if (!type.compare("Battery")) {
|
||||
if (std::ifstream{node.path() / "type"} >> type && !type.compare("Battery")) {
|
||||
// Ignore non-system power supplies unless explicitly requested
|
||||
if (!bat_defined && fs::exists(node.path() / "scope")) {
|
||||
if (!bat_defined) {
|
||||
std::string scope;
|
||||
std::ifstream(node.path() / "scope") >> scope;
|
||||
if (g_ascii_strcasecmp(scope.data(), "device") == 0) {
|
||||
// for hotplug-in device, access it is always unstable because you may remove the
|
||||
// device anytime so just allow failure happen and do nothing
|
||||
if (std::ifstream{node.path() / "scope"} >> scope &&
|
||||
g_ascii_strcasecmp(scope.data(), "device") == 0) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -154,7 +153,7 @@ void waybar::modules::Battery::refreshBatteries() {
|
||||
}
|
||||
}
|
||||
} catch (fs::filesystem_error& e) {
|
||||
throw std::runtime_error(e.what());
|
||||
spdlog::warn("Battery directory tracking failed: {}", e.what());
|
||||
}
|
||||
if (warnFirstTime_ && batteries_.empty()) {
|
||||
if (config_["bat"].isString()) {
|
||||
@@ -275,11 +274,11 @@ waybar::modules::Battery::getInfos() {
|
||||
auto bat = item.first;
|
||||
std::string _status;
|
||||
|
||||
/* Check for adapter status if battery is not available */
|
||||
if (!std::ifstream(bat / "status")) {
|
||||
{
|
||||
std::ifstream f{bat / "status"};
|
||||
if (!std::getline(f, _status)) {
|
||||
std::getline(std::ifstream(adapter_ / "status"), _status);
|
||||
} else {
|
||||
std::getline(std::ifstream(bat / "status"), _status);
|
||||
}
|
||||
}
|
||||
|
||||
// Some battery will report current and charge in μA/μAh.
|
||||
@@ -288,64 +287,74 @@ waybar::modules::Battery::getInfos() {
|
||||
uint32_t current_now = 0;
|
||||
int32_t _current_now_int = 0;
|
||||
bool current_now_exists = false;
|
||||
if (fs::exists(bat / "current_now")) {
|
||||
if (std::ifstream current_now_f{bat / "current_now"}) {
|
||||
if (current_now_f >> _current_now_int) {
|
||||
current_now_exists = true;
|
||||
std::ifstream(bat / "current_now") >> _current_now_int;
|
||||
} else if (fs::exists(bat / "current_avg")) {
|
||||
}
|
||||
} else if (std::ifstream current_avg_f{bat / "current_avg"}) {
|
||||
if (current_avg_f >> _current_now_int) {
|
||||
current_now_exists = true;
|
||||
std::ifstream(bat / "current_avg") >> _current_now_int;
|
||||
}
|
||||
}
|
||||
// Documentation ABI allows a negative value when discharging, positive
|
||||
// value when charging.
|
||||
current_now = std::abs(_current_now_int);
|
||||
|
||||
if (fs::exists(bat / "time_to_empty_now")) {
|
||||
if (std::ifstream f{bat / "time_to_empty_now"}) {
|
||||
if (f >> time_to_empty_now) {
|
||||
time_to_empty_now_exists = true;
|
||||
std::ifstream(bat / "time_to_empty_now") >> time_to_empty_now;
|
||||
}
|
||||
}
|
||||
|
||||
if (fs::exists(bat / "time_to_full_now")) {
|
||||
if (std::ifstream f{bat / "time_to_full_now"}) {
|
||||
if (f >> time_to_full_now) {
|
||||
time_to_full_now_exists = true;
|
||||
std::ifstream(bat / "time_to_full_now") >> time_to_full_now;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t voltage_now = 0;
|
||||
bool voltage_now_exists = false;
|
||||
if (fs::exists(bat / "voltage_now")) {
|
||||
if (std::ifstream voltage_now_f{bat / "voltage_now"}) {
|
||||
if (voltage_now_f >> voltage_now) {
|
||||
voltage_now_exists = true;
|
||||
std::ifstream(bat / "voltage_now") >> voltage_now;
|
||||
} else if (fs::exists(bat / "voltage_avg")) {
|
||||
}
|
||||
} else if (std::ifstream voltage_avg_f{bat / "voltage_avg"}) {
|
||||
if (voltage_avg_f >> voltage_now) {
|
||||
voltage_now_exists = true;
|
||||
std::ifstream(bat / "voltage_avg") >> voltage_now;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t charge_full = 0;
|
||||
bool charge_full_exists = false;
|
||||
if (fs::exists(bat / "charge_full")) {
|
||||
if (std::ifstream f{bat / "charge_full"}) {
|
||||
if (f >> charge_full) {
|
||||
charge_full_exists = true;
|
||||
std::ifstream(bat / "charge_full") >> charge_full;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t charge_full_design = 0;
|
||||
bool charge_full_design_exists = false;
|
||||
if (fs::exists(bat / "charge_full_design")) {
|
||||
if (std::ifstream f{bat / "charge_full_design"}) {
|
||||
if (f >> charge_full_design) {
|
||||
charge_full_design_exists = true;
|
||||
std::ifstream(bat / "charge_full_design") >> charge_full_design;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t charge_now = 0;
|
||||
bool charge_now_exists = false;
|
||||
if (fs::exists(bat / "charge_now")) {
|
||||
if (std::ifstream f{bat / "charge_now"}) {
|
||||
if (f >> charge_now) {
|
||||
charge_now_exists = true;
|
||||
std::ifstream(bat / "charge_now") >> charge_now;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t power_now = 0;
|
||||
int32_t _power_now_int = 0;
|
||||
bool power_now_exists = false;
|
||||
if (fs::exists(bat / "power_now")) {
|
||||
if (std::ifstream f{bat / "power_now"}) {
|
||||
if (f >> _power_now_int) {
|
||||
power_now_exists = true;
|
||||
std::ifstream(bat / "power_now") >> _power_now_int;
|
||||
}
|
||||
}
|
||||
// Some drivers (example: Qualcomm) exposes use a negative value when
|
||||
// discharging, positive value when charging.
|
||||
@@ -353,28 +362,31 @@ waybar::modules::Battery::getInfos() {
|
||||
|
||||
uint32_t energy_now = 0;
|
||||
bool energy_now_exists = false;
|
||||
if (fs::exists(bat / "energy_now")) {
|
||||
if (std::ifstream f{bat / "energy_now"}) {
|
||||
if (f >> energy_now) {
|
||||
energy_now_exists = true;
|
||||
std::ifstream(bat / "energy_now") >> energy_now;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t energy_full = 0;
|
||||
bool energy_full_exists = false;
|
||||
if (fs::exists(bat / "energy_full")) {
|
||||
if (std::ifstream f{bat / "energy_full"}) {
|
||||
if (f >> energy_full) {
|
||||
energy_full_exists = true;
|
||||
std::ifstream(bat / "energy_full") >> energy_full;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t energy_full_design = 0;
|
||||
bool energy_full_design_exists = false;
|
||||
if (fs::exists(bat / "energy_full_design")) {
|
||||
if (std::ifstream f{bat / "energy_full_design"}) {
|
||||
if (f >> energy_full_design) {
|
||||
energy_full_design_exists = true;
|
||||
std::ifstream(bat / "energy_full_design") >> energy_full_design;
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t cycleCount = 0;
|
||||
if (fs::exists(bat / "cycle_count")) {
|
||||
std::ifstream(bat / "cycle_count") >> cycleCount;
|
||||
if (std::ifstream f{bat / "cycle_count"}) {
|
||||
f >> cycleCount;
|
||||
}
|
||||
if (charge_full_design >= largestDesignCapacity) {
|
||||
largestDesignCapacity = charge_full_design;
|
||||
@@ -404,9 +416,10 @@ waybar::modules::Battery::getInfos() {
|
||||
} else if (energy_now_exists && energy_full_exists && energy_full != 0) {
|
||||
capacity_exists = true;
|
||||
capacity = 100 * (uint64_t)energy_now / (uint64_t)energy_full;
|
||||
} else if (fs::exists(bat / "capacity")) {
|
||||
} else if (std::ifstream f{bat / "capacity"}) {
|
||||
if (f >> capacity) {
|
||||
capacity_exists = true;
|
||||
std::ifstream(bat / "capacity") >> capacity;
|
||||
}
|
||||
}
|
||||
|
||||
if (!voltage_now_exists) {
|
||||
@@ -586,13 +599,14 @@ waybar::modules::Battery::getInfos() {
|
||||
|
||||
float calculated_capacity{0.0f};
|
||||
if (total_capacity_exists) {
|
||||
if (total_capacity > 0.0f)
|
||||
if (total_capacity > 0.0f) {
|
||||
calculated_capacity = (float)total_capacity / batteries_.size();
|
||||
else if (total_energy_full_exists && total_energy_exists) {
|
||||
if (total_energy_full > 0.0f)
|
||||
} else if (total_energy_full_exists && total_energy_exists) {
|
||||
if (total_energy_full > 0.0f) {
|
||||
calculated_capacity = ((float)total_energy * 100.0f / (float)total_energy_full);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle weighted-average
|
||||
if (weightedAverage_ && total_energy_exists && total_energy_full_exists) {
|
||||
@@ -687,13 +701,20 @@ auto waybar::modules::Battery::update() -> void {
|
||||
if (status == "Unknown") {
|
||||
status = getAdapterStatus(capacity);
|
||||
}
|
||||
auto state = getState(capacity, true);
|
||||
bool adapter_online = false;
|
||||
if (!adapter_.empty()) {
|
||||
std::ifstream(adapter_ / "online") >> adapter_online;
|
||||
}
|
||||
if (config_["full-at-plugged"].asBool() && status == "Full" && adapter_online) {
|
||||
status = "Plugged";
|
||||
}
|
||||
auto status_pretty = status;
|
||||
|
||||
// Transform to lowercase and replace space with dash
|
||||
std::ranges::transform(status.begin(), status.end(), status.begin(),
|
||||
[](char ch) { return ch == ' ' ? '-' : std::tolower(ch); });
|
||||
auto format = format_;
|
||||
auto state = getState(capacity, true);
|
||||
processEvents(state, status, capacity);
|
||||
setBarClass(state);
|
||||
auto time_remaining_formatted = formatTimeRemaining(time_remaining);
|
||||
|
||||
@@ -23,23 +23,21 @@ std::vector<float> waybar::modules::CpuFrequency::parseCpuFrequencies() {
|
||||
|
||||
if (frequencies.size() <= 0) {
|
||||
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"};
|
||||
for (auto& p : std::filesystem::directory_iterator(cpufreq_dir)) {
|
||||
for (const auto& freq_file : frequency_files) {
|
||||
std::string freq_file_path = p.path().string() + freq_file;
|
||||
if (std::filesystem::exists(freq_file_path)) {
|
||||
std::string freq_value;
|
||||
std::ifstream freq(freq_file_path);
|
||||
if (freq.is_open()) {
|
||||
getline(freq, freq_value);
|
||||
float frequency = std::strtol(freq_value.c_str(), nullptr, 10);
|
||||
frequencies.push_back(frequency / 1000);
|
||||
freq.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (const std::filesystem::filesystem_error&) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,11 @@ waybar::modules::Custom::~Custom() {
|
||||
}
|
||||
|
||||
void waybar::modules::Custom::delayWorker() {
|
||||
if (!config_["exec"].isString() && !config_["exec-if"].isString()) {
|
||||
dp.emit();
|
||||
return;
|
||||
}
|
||||
|
||||
thread_ = [this] {
|
||||
for (int i : this->pid_children_) {
|
||||
int status;
|
||||
@@ -138,9 +143,11 @@ void waybar::modules::Custom::waitingWorker() {
|
||||
}
|
||||
|
||||
void waybar::modules::Custom::refresh(int sig) {
|
||||
#ifdef SIGRTMIN
|
||||
if (config_["signal"].isInt() && sig == SIGRTMIN + config_["signal"].asInt()) {
|
||||
thread_.wake_up();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void waybar::modules::Custom::handleEvent() {
|
||||
|
||||
@@ -70,17 +70,33 @@ static const zdwl_ipc_output_v2_listener output_status_listener_impl{
|
||||
|
||||
static void handle_global(void* data, struct wl_registry* registry, uint32_t name,
|
||||
const char* interface, uint32_t version) {
|
||||
if (std::strcmp(interface, zdwl_ipc_manager_v2_interface.name) == 0) {
|
||||
static_cast<Tags*>(data)->status_manager_ = static_cast<struct zdwl_ipc_manager_v2*>(
|
||||
(zdwl_ipc_manager_v2*)wl_registry_bind(registry, name, &zdwl_ipc_manager_v2_interface, 1));
|
||||
}
|
||||
if (std::strcmp(interface, wl_seat_interface.name) == 0) {
|
||||
version = std::min<uint32_t>(version, 1);
|
||||
static_cast<Tags*>(data)->seat_ =
|
||||
static_cast<struct wl_seat*>(wl_registry_bind(registry, name, &wl_seat_interface, version));
|
||||
}
|
||||
|
||||
if (std::strcmp(interface, zdwl_ipc_manager_v2_interface.name) == 0) {
|
||||
auto* self = static_cast<Tags*>(data);
|
||||
|
||||
if (self->status_manager_) {
|
||||
zdwl_ipc_manager_v2_destroy(self->status_manager_);
|
||||
self->status_manager_ = nullptr;
|
||||
}
|
||||
|
||||
self->status_manager_ = static_cast<struct zdwl_ipc_manager_v2*>(
|
||||
wl_registry_bind(registry, name, &zdwl_ipc_manager_v2_interface, 1));
|
||||
}
|
||||
|
||||
if (std::strcmp(interface, wl_seat_interface.name) == 0) {
|
||||
auto* self = static_cast<Tags*>(data);
|
||||
|
||||
if (self->seat_) {
|
||||
wl_seat_destroy(self->seat_);
|
||||
self->seat_ = nullptr;
|
||||
}
|
||||
|
||||
version = std::min<uint32_t>(version, 1);
|
||||
|
||||
self->seat_ = static_cast<struct wl_seat*>(
|
||||
wl_registry_bind(registry, name, &wl_seat_interface, version));
|
||||
}
|
||||
}
|
||||
static void handle_global_remove(void* data, struct wl_registry* registry, uint32_t name) {
|
||||
/* Ignore event */
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include "util/scoped_fd.hpp"
|
||||
@@ -20,6 +21,7 @@
|
||||
namespace waybar::modules::hyprland {
|
||||
|
||||
std::filesystem::path IPC::socketFolder_;
|
||||
std::optional<bool> IPC::s_luaProtocolDetected_;
|
||||
|
||||
std::filesystem::path IPC::getSocketFolder(const char* instanceSig) {
|
||||
static std::mutex folderMutex;
|
||||
@@ -290,4 +292,69 @@ Json::Value IPC::getSocket1JsonReply(const std::string& rq) {
|
||||
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
|
||||
|
||||
@@ -48,12 +48,39 @@ auto Language::update() -> void {
|
||||
fmt::arg("shortDescription", layout_.short_description),
|
||||
fmt::arg("variant", layout_.variant)));
|
||||
}
|
||||
|
||||
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()) {
|
||||
label_.show();
|
||||
label_.set_markup(layoutName);
|
||||
if (tooltip_enabled) {
|
||||
label_.set_tooltip_markup(tooltipContent);
|
||||
}
|
||||
} else {
|
||||
label_.hide();
|
||||
}
|
||||
@@ -74,8 +101,6 @@ void Language::onEvent(const std::string& ev) {
|
||||
spdlog::warn("hyprland language received malformed event payload: {}", ev);
|
||||
return;
|
||||
}
|
||||
std::string kbName = payload.substr(0, kbSeparator);
|
||||
|
||||
// Last comma before variants parenthesis, eg:
|
||||
// activelayout>>micro-star-int'l-co.,-ltd.-msi-gk50-elite-gaming-keyboard,English (US, intl.,
|
||||
// with dead keys)
|
||||
@@ -93,8 +118,15 @@ void Language::onEvent(const std::string& ev) {
|
||||
}
|
||||
auto layoutName = payload.substr(layoutSeparator + 1);
|
||||
|
||||
if (config_.isMember("keyboard-name") && kbName != config_["keyboard-name"].asString())
|
||||
if (config_.isMember("keyboard-name")) {
|
||||
const auto keyboardName = config_["keyboard-name"].asString();
|
||||
// The keyboard name itself can contain commas, so match it as a full prefix
|
||||
// (followed by the ',' separator) rather than comparing against the substring
|
||||
// before the first comma, which would truncate such names and drop the event.
|
||||
if (payload.size() <= keyboardName.size() || payload[keyboardName.size()] != ',' ||
|
||||
payload.compare(0, keyboardName.size(), keyboardName) != 0)
|
||||
return; // ignore
|
||||
}
|
||||
|
||||
layoutName = waybar::util::sanitize_string(layoutName);
|
||||
|
||||
|
||||
@@ -19,20 +19,19 @@ std::shared_mutex windowIpcSmtx;
|
||||
|
||||
Window::Window(const std::string& id, const Bar& bar, const Json::Value& config)
|
||||
: AAppIconLabel(config, "window", id, "{title}", 0, true), bar_(bar), m_ipc(IPC::inst()) {
|
||||
std::unique_lock<std::shared_mutex> windowIpcUniqueLock(windowIpcSmtx);
|
||||
|
||||
separateOutputs_ = config["separate-outputs"].asBool();
|
||||
|
||||
update();
|
||||
|
||||
// register for hyprland ipc
|
||||
std::unique_lock<std::shared_mutex> windowIpcUniqueLock(windowIpcSmtx);
|
||||
m_ipc.registerForIPC("activewindow", this);
|
||||
m_ipc.registerForIPC("closewindow", this);
|
||||
m_ipc.registerForIPC("movewindow", this);
|
||||
m_ipc.registerForIPC("changefloatingmode", this);
|
||||
m_ipc.registerForIPC("fullscreen", this);
|
||||
|
||||
windowIpcUniqueLock.unlock();
|
||||
|
||||
update();
|
||||
dp.emit();
|
||||
}
|
||||
|
||||
@@ -109,21 +108,14 @@ auto Window::update() -> void {
|
||||
AAppIconLabel::update();
|
||||
}
|
||||
|
||||
auto Window::getActiveWorkspace() -> Workspace {
|
||||
const auto workspace = IPC::inst().getSocket1JsonReply("activeworkspace");
|
||||
|
||||
if (workspace.isObject()) {
|
||||
return Workspace::parse(workspace);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
auto Window::getActiveWorkspace() -> Workspace { return getActiveWorkspace(""); }
|
||||
|
||||
auto Window::getActiveWorkspace(const std::string& monitorName) -> Workspace {
|
||||
const auto monitors = IPC::inst().getSocket1JsonReply("monitors");
|
||||
if (monitors.isArray()) {
|
||||
auto monitor = std::ranges::find_if(
|
||||
monitors, [&](const Json::Value& monitor) { return monitor["name"] == monitorName; });
|
||||
auto monitor = std::ranges::find_if(monitors, [&](const Json::Value& monitor) {
|
||||
return monitorName.empty() ? monitor["focused"].asBool() : monitor["name"] == monitorName;
|
||||
});
|
||||
if (monitor == std::end(monitors)) {
|
||||
spdlog::warn("Monitor not found: {}", monitorName);
|
||||
return Workspace{
|
||||
@@ -133,7 +125,8 @@ auto Window::getActiveWorkspace(const std::string& monitorName) -> Workspace {
|
||||
.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");
|
||||
if (workspaces.isArray()) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <json/value.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <glibmm/main.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
@@ -30,6 +31,11 @@ Workspace::Workspace(const Json::Value& workspace_data, Workspaces& workspace_ma
|
||||
}
|
||||
|
||||
m_button.add_events(Gdk::BUTTON_PRESS_MASK);
|
||||
m_button.add_events(Gdk::ENTER_NOTIFY_MASK | Gdk::LEAVE_NOTIFY_MASK);
|
||||
|
||||
m_button.signal_enter_notify_event().connect(sigc::mem_fun(*this, &Workspace::handleEnter));
|
||||
m_button.signal_leave_notify_event().connect(sigc::mem_fun(*this, &Workspace::handleLeave));
|
||||
|
||||
m_button.signal_button_press_event().connect(sigc::mem_fun(*this, &Workspace::handleClicked),
|
||||
false);
|
||||
|
||||
@@ -45,6 +51,12 @@ Workspace::Workspace(const Json::Value& workspace_data, Workspaces& workspace_ma
|
||||
initializeWindowMap(clients_data);
|
||||
}
|
||||
|
||||
Workspace::~Workspace() {
|
||||
// Disconnect the hover-check timeout so it can't fire on this destroyed
|
||||
// instance (Workspaces are removed at runtime while a check may be armed).
|
||||
stopHoverCheck();
|
||||
}
|
||||
|
||||
void addOrRemoveClass(const Glib::RefPtr<Gtk::StyleContext>& context, bool condition,
|
||||
const std::string& class_name) {
|
||||
if (condition) {
|
||||
@@ -66,25 +78,121 @@ std::optional<WindowRepr> Workspace::closeWindow(WindowAddress const& addr) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool Workspace::pointerInsideButton() {
|
||||
auto display = Gdk::Display::get_default();
|
||||
if (!display) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto seat = display->get_default_seat();
|
||||
if (!seat) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto pointer = seat->get_pointer();
|
||||
if (!pointer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Glib::RefPtr<Gdk::Screen> screen;
|
||||
int pointerRootX = 0;
|
||||
int pointerRootY = 0;
|
||||
|
||||
pointer->get_position(screen, pointerRootX, pointerRootY);
|
||||
|
||||
Gtk::Widget* toplevel = m_button.get_toplevel();
|
||||
if (toplevel == nullptr || !toplevel->get_window()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int buttonX = 0;
|
||||
int buttonY = 0;
|
||||
|
||||
if (!m_button.translate_coordinates(*toplevel, 0, 0, buttonX, buttonY)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int windowRootX = 0;
|
||||
int windowRootY = 0;
|
||||
toplevel->get_window()->get_root_origin(windowRootX, windowRootY);
|
||||
|
||||
const auto allocation = m_button.get_allocation();
|
||||
|
||||
const int buttonRootX = windowRootX + buttonX;
|
||||
const int buttonRootY = windowRootY + buttonY;
|
||||
const int buttonWidth = allocation.get_width();
|
||||
const int buttonHeight = allocation.get_height();
|
||||
|
||||
return pointerRootX >= buttonRootX && pointerRootY >= buttonRootY &&
|
||||
pointerRootX < buttonRootX + buttonWidth &&
|
||||
pointerRootY < buttonRootY + buttonHeight;
|
||||
}
|
||||
|
||||
bool Workspace::syncHoverClass() {
|
||||
auto styleContext = m_button.get_style_context();
|
||||
|
||||
if (pointerInsideButton()) {
|
||||
styleContext->add_class("workspace-hover");
|
||||
return true;
|
||||
}
|
||||
|
||||
styleContext->remove_class("workspace-hover");
|
||||
stopHoverCheck();
|
||||
return false;
|
||||
}
|
||||
|
||||
void Workspace::startHoverCheck() {
|
||||
if (m_hoverCheckConnection.connected()) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_hoverCheckConnection = Glib::signal_timeout().connect(
|
||||
sigc::mem_fun(*this, &Workspace::syncHoverClass),
|
||||
50);
|
||||
}
|
||||
|
||||
void Workspace::stopHoverCheck() {
|
||||
if (m_hoverCheckConnection.connected()) {
|
||||
m_hoverCheckConnection.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
bool Workspace::handleEnter(GdkEventCrossing* /*event*/) {
|
||||
m_button.get_style_context()->add_class("workspace-hover");
|
||||
startHoverCheck();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Workspace::handleLeave(GdkEventCrossing* /*event*/) {
|
||||
/*
|
||||
* Do not remove immediately.
|
||||
* Workspace taskbar children can fire misleading leave events while the
|
||||
* pointer is still visually inside the workspace button.
|
||||
*
|
||||
* The polling check will remove the class once the pointer really leaves.
|
||||
*/
|
||||
startHoverCheck();
|
||||
return false;
|
||||
}
|
||||
bool Workspace::handleClicked(GdkEventButton* bt) const {
|
||||
if (bt->type == GDK_BUTTON_PRESS) {
|
||||
try {
|
||||
if (id() > 0) { // normal
|
||||
if (m_workspaceManager.moveToMonitor()) {
|
||||
m_ipc.getSocket1Reply("dispatch focusworkspaceoncurrentmonitor " + std::to_string(id()));
|
||||
IPC::dispatch("focusworkspaceoncurrentmonitor", std::to_string(id()));
|
||||
} else {
|
||||
m_ipc.getSocket1Reply("dispatch workspace " + std::to_string(id()));
|
||||
IPC::dispatch("workspace", std::to_string(id()));
|
||||
}
|
||||
} else if (!isSpecial()) { // named (this includes persistent)
|
||||
if (m_workspaceManager.moveToMonitor()) {
|
||||
m_ipc.getSocket1Reply("dispatch focusworkspaceoncurrentmonitor name:" + name());
|
||||
IPC::dispatch("focusworkspaceoncurrentmonitor", "name:" + name());
|
||||
} else {
|
||||
m_ipc.getSocket1Reply("dispatch workspace name:" + name());
|
||||
IPC::dispatch("workspace", "name:" + name());
|
||||
}
|
||||
} else if (id() != -99) { // named special
|
||||
m_ipc.getSocket1Reply("dispatch togglespecialworkspace " + name());
|
||||
IPC::dispatch("togglespecialworkspace", name());
|
||||
} else { // special
|
||||
m_ipc.getSocket1Reply("dispatch togglespecialworkspace");
|
||||
IPC::dispatch("togglespecialworkspace", "");
|
||||
}
|
||||
return true;
|
||||
} catch (const std::exception& e) {
|
||||
@@ -161,6 +269,17 @@ std::string& Workspace::selectIcon(std::map<std::string, std::string>& icons_map
|
||||
}
|
||||
}
|
||||
|
||||
if (isActive() && isSpecial()) {
|
||||
auto activeIconIt = icons_map.find("active:" + name());
|
||||
if (activeIconIt != icons_map.end()) {
|
||||
return activeIconIt->second;
|
||||
}
|
||||
auto namedIconIt = icons_map.find(name());
|
||||
if (namedIconIt != icons_map.end()) {
|
||||
return namedIconIt->second;
|
||||
}
|
||||
}
|
||||
|
||||
if (isActive()) {
|
||||
auto activeIconIt = icons_map.find("active");
|
||||
if (activeIconIt != icons_map.end()) {
|
||||
@@ -234,6 +353,7 @@ void Workspace::update(const std::string& workspace_icon) {
|
||||
auto styleContext = m_button.get_style_context();
|
||||
addOrRemoveClass(styleContext, isActive(), "active");
|
||||
addOrRemoveClass(styleContext, isSpecial(), "special");
|
||||
addOrRemoveClass(styleContext, isSpecial(), name());
|
||||
addOrRemoveClass(styleContext, isEmpty(), "empty");
|
||||
addOrRemoveClass(styleContext, isPersistent(), "persistent");
|
||||
addOrRemoveClass(styleContext, isUrgent(), "urgent");
|
||||
@@ -301,15 +421,18 @@ void Workspace::updateTaskbar(const std::string& workspace_icon) {
|
||||
|
||||
auto window_box = Gtk::make_managed<Gtk::Box>(Gtk::ORIENTATION_HORIZONTAL);
|
||||
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) {
|
||||
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() != "") {
|
||||
event_box->signal_button_press_event().connect(
|
||||
sigc::bind(sigc::mem_fun(*this, &Workspace::handleClick), window_repr.address));
|
||||
button->signal_button_press_event().connect(
|
||||
sigc::bind(sigc::mem_fun(*this, &Workspace::handleClick), window_repr.address),
|
||||
false);
|
||||
}
|
||||
|
||||
auto text_before = fmt::format(fmt::runtime(m_workspaceManager.taskbarFormatBefore()),
|
||||
@@ -334,8 +457,8 @@ void Workspace::updateTaskbar(const std::string& workspace_icon) {
|
||||
window_box->pack_start(*window_label_after, true, true);
|
||||
}
|
||||
|
||||
m_content.pack_start(*event_box, true, false);
|
||||
event_box->show_all();
|
||||
m_content.pack_start(*button, true, false);
|
||||
button->show_all();
|
||||
};
|
||||
|
||||
if (m_workspaceManager.taskbarReverseDirection()) {
|
||||
|
||||
@@ -50,7 +50,8 @@ void Workspaces::init() {
|
||||
if (m_scrollEventConnection_.connected()) {
|
||||
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;
|
||||
window.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);
|
||||
m_scrollEventConnection_ =
|
||||
@@ -326,6 +327,10 @@ void Workspaces::loadPersistentWorkspacesFromWorkspaceRules(const Json::Value& c
|
||||
// 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
|
||||
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
|
||||
auto workspaceData = createMonitorWorkspaceData(workspace, m_bar.output->name);
|
||||
workspaceData["persistent-rule"] = true;
|
||||
@@ -1188,6 +1193,12 @@ bool Workspaces::handleScroll(GdkEventScroll* e) {
|
||||
if (gdk_event_get_pointer_emulated((GdkEvent*)e)) {
|
||||
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);
|
||||
if (dir == SCROLL_DIR::NONE) {
|
||||
return true;
|
||||
@@ -1195,15 +1206,15 @@ bool Workspaces::handleScroll(GdkEventScroll* e) {
|
||||
|
||||
if (dir == SCROLL_DIR::DOWN || dir == SCROLL_DIR::RIGHT) {
|
||||
if (allOutputs()) {
|
||||
m_ipc.getSocket1Reply("dispatch workspace e+1");
|
||||
IPC::dispatch("workspace", "e+1");
|
||||
} else {
|
||||
m_ipc.getSocket1Reply("dispatch workspace m+1");
|
||||
IPC::dispatch("workspace", "m+1");
|
||||
}
|
||||
} else if (dir == SCROLL_DIR::UP || dir == SCROLL_DIR::LEFT) {
|
||||
if (allOutputs()) {
|
||||
m_ipc.getSocket1Reply("dispatch workspace e-1");
|
||||
IPC::dispatch("workspace", "e-1");
|
||||
} else {
|
||||
m_ipc.getSocket1Reply("dispatch workspace m-1");
|
||||
IPC::dispatch("workspace", "m-1");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -46,9 +46,11 @@ void waybar::modules::Image::delayWorker() {
|
||||
}
|
||||
|
||||
void waybar::modules::Image::refresh(int sig) {
|
||||
#ifdef SIGRTMIN
|
||||
if (config_["signal"].isInt() && sig == SIGRTMIN + config_["signal"].asInt()) {
|
||||
thread_.wake_up();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
auto waybar::modules::Image::update() -> void {
|
||||
|
||||
@@ -1,11 +1,32 @@
|
||||
#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)
|
||||
: ALabel(config, "memory", id, "{}%", 30) {
|
||||
thread_ = [this] {
|
||||
dp.emit();
|
||||
thread_.sleep_for(interval_);
|
||||
};
|
||||
if (config["unit"].isString()) {
|
||||
unit_ = config["unit"].asString();
|
||||
if (!kUnits.contains(unit_)) {
|
||||
unit_ = "GiB";
|
||||
}
|
||||
} else {
|
||||
unit_ = "GiB";
|
||||
}
|
||||
}
|
||||
|
||||
auto waybar::modules::Memory::update() -> void {
|
||||
@@ -13,15 +34,15 @@ auto waybar::modules::Memory::update() -> void {
|
||||
|
||||
unsigned long memtotal = meminfo_["MemTotal"];
|
||||
unsigned long swaptotal = 0;
|
||||
if (meminfo_.count("SwapTotal")) {
|
||||
if (meminfo_.contains("SwapTotal")) {
|
||||
swaptotal = meminfo_["SwapTotal"];
|
||||
}
|
||||
unsigned long memfree;
|
||||
unsigned long swapfree = 0;
|
||||
if (meminfo_.count("SwapFree")) {
|
||||
if (meminfo_.contains("SwapFree")) {
|
||||
swapfree = meminfo_["SwapFree"];
|
||||
}
|
||||
if (meminfo_.count("MemAvailable")) {
|
||||
if (meminfo_.contains("MemAvailable")) {
|
||||
// New kernels (3.4+) have an accurate available memory field.
|
||||
memfree = meminfo_["MemAvailable"] + meminfo_["zfs_size"];
|
||||
} else {
|
||||
@@ -31,18 +52,19 @@ auto waybar::modules::Memory::update() -> void {
|
||||
}
|
||||
|
||||
if (memtotal > 0 && memfree >= 0) {
|
||||
float total_ram_gigabytes =
|
||||
0.01 * round(memtotal / 10485.76); // 100*10485.76 = 2^20 = 1024^2 = GiB/KiB
|
||||
float total_swap_gigabytes = 0.01 * round(swaptotal / 10485.76);
|
||||
int used_ram_percentage = 100 * (memtotal - memfree) / memtotal;
|
||||
int used_swap_percentage = 0;
|
||||
if (swaptotal) {
|
||||
if ((bool) swaptotal) {
|
||||
used_swap_percentage = 100 * (swaptotal - swapfree) / swaptotal;
|
||||
}
|
||||
float used_ram_gigabytes = 0.01 * round((memtotal - memfree) / 10485.76);
|
||||
float used_swap_gigabytes = 0.01 * round((swaptotal - swapfree) / 10485.76);
|
||||
float available_ram_gigabytes = 0.01 * round(memfree / 10485.76);
|
||||
float available_swap_gigabytes = 0.01 * round(swapfree / 10485.76);
|
||||
|
||||
float divisor = kUnits.at(unit_);
|
||||
float total_ram = memtotal / divisor;
|
||||
float total_swap = swaptotal / divisor;
|
||||
float used_ram = (memtotal - memfree) / divisor;
|
||||
float used_swap = (swaptotal - swapfree) / divisor;
|
||||
float available_ram = memfree / divisor;
|
||||
float available_swap = swapfree / divisor;
|
||||
|
||||
auto format = format_;
|
||||
auto state = getState(used_ram_percentage);
|
||||
@@ -58,12 +80,12 @@ auto waybar::modules::Memory::update() -> void {
|
||||
label_.set_markup(fmt::format(
|
||||
fmt::runtime(format), used_ram_percentage,
|
||||
fmt::arg("icon", getIcon(used_ram_percentage, icons)),
|
||||
fmt::arg("total", total_ram_gigabytes), fmt::arg("swapTotal", total_swap_gigabytes),
|
||||
fmt::arg("total", total_ram), fmt::arg("swapTotal", total_swap),
|
||||
fmt::arg("percentage", used_ram_percentage),
|
||||
fmt::arg("swapState", swaptotal == 0 ? "Off" : "On"),
|
||||
fmt::arg("swapPercentage", used_swap_percentage), fmt::arg("used", used_ram_gigabytes),
|
||||
fmt::arg("swapUsed", used_swap_gigabytes), fmt::arg("avail", available_ram_gigabytes),
|
||||
fmt::arg("swapAvail", available_swap_gigabytes)));
|
||||
fmt::arg("swapPercentage", used_swap_percentage), fmt::arg("used", used_ram),
|
||||
fmt::arg("swapUsed", used_swap), fmt::arg("avail", available_ram),
|
||||
fmt::arg("swapAvail", available_swap)));
|
||||
}
|
||||
|
||||
if (tooltipEnabled()) {
|
||||
@@ -71,14 +93,14 @@ auto waybar::modules::Memory::update() -> void {
|
||||
auto tooltip_format = config_["tooltip-format"].asString();
|
||||
label_.set_tooltip_markup(fmt::format(
|
||||
fmt::runtime(tooltip_format), used_ram_percentage,
|
||||
fmt::arg("total", total_ram_gigabytes), fmt::arg("swapTotal", total_swap_gigabytes),
|
||||
fmt::arg("total", total_ram), fmt::arg("swapTotal", total_swap),
|
||||
fmt::arg("percentage", used_ram_percentage),
|
||||
fmt::arg("swapState", swaptotal == 0 ? "Off" : "On"),
|
||||
fmt::arg("swapPercentage", used_swap_percentage), fmt::arg("used", used_ram_gigabytes),
|
||||
fmt::arg("swapUsed", used_swap_gigabytes), fmt::arg("avail", available_ram_gigabytes),
|
||||
fmt::arg("swapAvail", available_swap_gigabytes)));
|
||||
fmt::arg("swapPercentage", used_swap_percentage), fmt::arg("used", used_ram),
|
||||
fmt::arg("swapUsed", used_swap), fmt::arg("avail", available_ram),
|
||||
fmt::arg("swapAvail", available_swap)));
|
||||
} else {
|
||||
label_.set_tooltip_markup(fmt::format("{:.{}f}GiB used", used_ram_gigabytes, 1));
|
||||
label_.set_tooltip_markup(fmt::format("{:.{}f}{} used", used_ram, 1, unit_));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
+44
-34
@@ -6,6 +6,7 @@
|
||||
|
||||
#include <system_error>
|
||||
#include <util/sanitize_str.hpp>
|
||||
#include <util/utf8_string.hpp>
|
||||
using namespace waybar::util;
|
||||
|
||||
#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),
|
||||
connection_(nullptr, &mpd_connection_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()) {
|
||||
spdlog::warn("{}: `port` configuration should be an unsigned int", module_name_);
|
||||
}
|
||||
@@ -51,21 +53,6 @@ auto waybar::modules::MPD::update() -> void {
|
||||
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 result =
|
||||
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");
|
||||
|
||||
auto format = format_;
|
||||
Glib::ustring artist, album_artist, album, title;
|
||||
std::string date, filename, uri;
|
||||
int song_pos = 0, queue_length = 0, volume = 0;
|
||||
std::chrono::seconds elapsedTime, totalTime;
|
||||
|
||||
std::string stateIcon = "";
|
||||
bool no_song = song_.get() == nullptr;
|
||||
if (stopped() || no_song) {
|
||||
if (no_song) spdlog::warn("Bug in mpd: no current song but state is not stopped.");
|
||||
bool is_stopped = stopped();
|
||||
if (is_stopped || no_song) {
|
||||
if (no_song && !is_stopped) spdlog::warn("mpd: no current song while state is not stopped");
|
||||
format =
|
||||
config_["format-stopped"].isString() ? config_["format-stopped"].asString() : "stopped";
|
||||
label_.get_style_context()->add_class("stopped");
|
||||
@@ -145,10 +132,6 @@ void waybar::modules::MPD::setLabel() {
|
||||
|
||||
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));
|
||||
filename = sanitize_string(getFilename());
|
||||
uri = mpd_song_get_uri(song_.get());
|
||||
@@ -170,17 +153,12 @@ void waybar::modules::MPD::setLabel() {
|
||||
std::string repeatIcon = getOptionIcon("repeat", repeatActivated);
|
||||
bool singleActivated = mpd_status_get_single(status_.get());
|
||||
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 {
|
||||
auto text = fmt::format(
|
||||
fmt::runtime(format), fmt::arg("artist", artist.raw()),
|
||||
fmt::arg("albumArtist", album_artist.raw()), fmt::arg("album", album.raw()),
|
||||
fmt::arg("title", title.raw()), fmt::arg("date", date), fmt::arg("volume", volume),
|
||||
fmt::runtime(format), fmt::arg("artist", getArtistStr(true)),
|
||||
fmt::arg("albumArtist", getAlbumArtistStr(true)), fmt::arg("album", getAlbumStr(true)),
|
||||
fmt::arg("title", getTitleStr(true)), fmt::arg("date", date), fmt::arg("volume", volume),
|
||||
fmt::arg("elapsedTime", elapsedTime), fmt::arg("totalTime", totalTime),
|
||||
fmt::arg("songPosition", song_pos), fmt::arg("queueLength", queue_length),
|
||||
fmt::arg("stateIcon", stateIcon), fmt::arg("consumeIcon", consumeIcon),
|
||||
@@ -202,9 +180,9 @@ void waybar::modules::MPD::setLabel() {
|
||||
: "MPD (connected)";
|
||||
try {
|
||||
auto tooltip_text = fmt::format(
|
||||
fmt::runtime(tooltip_format), fmt::arg("artist", artist.raw()),
|
||||
fmt::arg("albumArtist", album_artist.raw()), fmt::arg("album", album.raw()),
|
||||
fmt::arg("title", title.raw()), fmt::arg("date", date), fmt::arg("volume", volume),
|
||||
fmt::runtime(tooltip_format), fmt::arg("artist", getArtistStr(false)),
|
||||
fmt::arg("albumArtist", getAlbumArtistStr(false)), fmt::arg("album", getAlbumStr(false)),
|
||||
fmt::arg("title", getTitleStr(false)), fmt::arg("date", date), fmt::arg("volume", volume),
|
||||
fmt::arg("elapsedTime", elapsedTime), fmt::arg("totalTime", totalTime),
|
||||
fmt::arg("songPosition", song_pos), fmt::arg("queueLength", queue_length),
|
||||
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) {
|
||||
if (ec.category() == std::system_category()) {
|
||||
switch (ec.value()) {
|
||||
|
||||
@@ -152,7 +152,6 @@ bool Playing::on_timer() {
|
||||
return false;
|
||||
}
|
||||
|
||||
ctx_->queryMPD();
|
||||
ctx_->emit();
|
||||
} catch (std::exception const& e) {
|
||||
spdlog::warn("mpd: Playing: error: {}", e.what());
|
||||
|
||||
+14
-57
@@ -7,6 +7,7 @@
|
||||
#include <string>
|
||||
|
||||
#include "util/scope_guard.hpp"
|
||||
#include "util/utf8_string.hpp"
|
||||
|
||||
extern "C" {
|
||||
#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
|
||||
// Modifies str in-place (unless width = std::string::npos)
|
||||
// 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 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;
|
||||
}
|
||||
|
||||
auto Mpris::getAlbumStr(const PlayerInfo& info, bool truncated) -> 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;
|
||||
}
|
||||
|
||||
auto Mpris::getTitleStr(const PlayerInfo& info, bool truncated) -> 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;
|
||||
}
|
||||
|
||||
@@ -292,9 +246,9 @@ auto Mpris::getDynamicStr(const PlayerInfo& info, bool truncated, bool html) ->
|
||||
// keep position format same as length format
|
||||
auto position = getPositionStr(info, truncated && truncate_hours_ && length.length() < 6);
|
||||
|
||||
size_t artistLen = utf8_width(artist);
|
||||
size_t albumLen = utf8_width(album);
|
||||
size_t titleLen = utf8_width(title);
|
||||
size_t artistLen = waybar::util::utf8_width(artist);
|
||||
size_t albumLen = waybar::util::utf8_width(album);
|
||||
size_t titleLen = waybar::util::utf8_width(title);
|
||||
size_t lengthLen = length.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
|
||||
// it will be, we add a "virtual separatorLen" to the dynamicLen, since we are adding the
|
||||
// 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;
|
||||
if (showArtist) artistLen += separatorLen;
|
||||
if (showAlbum) albumLen += separatorLen;
|
||||
@@ -775,9 +729,12 @@ auto Mpris::update() -> void {
|
||||
auto tooltip_text = fmt::format(
|
||||
fmt::runtime(tooltipstr), fmt::arg("player", info.name),
|
||||
fmt::arg("status", info.status_string),
|
||||
fmt::arg("artist", getArtistStr(info, tooltip_len_limits_)),
|
||||
fmt::arg("title", getTitleStr(info, tooltip_len_limits_)),
|
||||
fmt::arg("album", getAlbumStr(info, tooltip_len_limits_)),
|
||||
fmt::arg("artist",
|
||||
std::string(Glib::Markup::escape_text(getArtistStr(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("dynamic", getDynamicStr(info, tooltip_len_limits_, false)),
|
||||
fmt::arg("player_icon", getIconFromJson(config_["player-icons"], info.name)),
|
||||
|
||||
+61
-45
@@ -105,6 +105,7 @@ waybar::modules::Network::Network(const std::string& id, const Json::Value& conf
|
||||
bandwidth_down_total_ = 0;
|
||||
bandwidth_up_total_ = 0;
|
||||
}
|
||||
bandwidth_last_sample_time_ = std::chrono::steady_clock::now();
|
||||
|
||||
if (!config_["interface"].isString()) {
|
||||
// "interface" isn't configured, then try to guess the external
|
||||
@@ -277,10 +278,24 @@ const std::string waybar::modules::Network::getNetworkState() const {
|
||||
auto waybar::modules::Network::update() -> void {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
std::string tooltip_format;
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
auto elapsed_seconds = std::chrono::duration<double>(now - bandwidth_last_sample_time_).count();
|
||||
|
||||
auto bandwidth = readBandwidthUsage();
|
||||
auto bandwidth_down = 0ull;
|
||||
auto bandwidth_up = 0ull;
|
||||
|
||||
// Only recalculate bandwidth when enough time has elapsed since the last
|
||||
// sample. Event-driven dp.emit() calls (link/addr/route changes) can
|
||||
// trigger update() between timer intervals, which would consume the byte
|
||||
// delta prematurely and show near-zero bandwidth.
|
||||
auto min_elapsed = std::chrono::duration<double>(interval_).count() * 0.5;
|
||||
if (elapsed_seconds >= min_elapsed) {
|
||||
if (elapsed_seconds <= 0.0) {
|
||||
elapsed_seconds = std::chrono::duration<double>(interval_).count();
|
||||
}
|
||||
bandwidth_last_sample_time_ = now;
|
||||
|
||||
auto bandwidth = readBandwidthUsage();
|
||||
if (bandwidth.has_value()) {
|
||||
auto down_octets = (*bandwidth).first;
|
||||
auto up_octets = (*bandwidth).second;
|
||||
@@ -290,6 +305,14 @@ auto waybar::modules::Network::update() -> void {
|
||||
|
||||
bandwidth_up = up_octets - bandwidth_up_total_;
|
||||
bandwidth_up_total_ = up_octets;
|
||||
|
||||
bandwidth_down_prev_ = bandwidth_down;
|
||||
bandwidth_up_prev_ = bandwidth_up;
|
||||
}
|
||||
} else {
|
||||
bandwidth_down = bandwidth_down_prev_;
|
||||
bandwidth_up = bandwidth_up_prev_;
|
||||
elapsed_seconds = std::chrono::duration<double>(interval_).count();
|
||||
}
|
||||
|
||||
if (!alt_) {
|
||||
@@ -335,23 +358,18 @@ auto waybar::modules::Network::update() -> void {
|
||||
fmt::arg("ipaddr", final_ipaddr_), fmt::arg("gwaddr", gwaddr_), fmt::arg("cidr", cidr_),
|
||||
fmt::arg("cidr6", cidr6_), fmt::arg("frequency", fmt::format("{:.1f}", frequency_)),
|
||||
fmt::arg("icon", getIcon(signal_strength_, state_)),
|
||||
fmt::arg("bandwidthDownBits",
|
||||
pow_format(bandwidth_down * 8ull / (interval_.count() / 1000.0), "b/s")),
|
||||
fmt::arg("bandwidthUpBits",
|
||||
pow_format(bandwidth_up * 8ull / (interval_.count() / 1000.0), "b/s")),
|
||||
fmt::arg(
|
||||
"bandwidthTotalBits",
|
||||
pow_format((bandwidth_up + bandwidth_down) * 8ull / (interval_.count() / 1000.0), "b/s")),
|
||||
fmt::arg("bandwidthDownOctets",
|
||||
pow_format(bandwidth_down / (interval_.count() / 1000.0), "o/s")),
|
||||
fmt::arg("bandwidthUpOctets", pow_format(bandwidth_up / (interval_.count() / 1000.0), "o/s")),
|
||||
fmt::arg("bandwidthDownBits", pow_format(bandwidth_down * 8ull / elapsed_seconds, "b/s")),
|
||||
fmt::arg("bandwidthUpBits", pow_format(bandwidth_up * 8ull / elapsed_seconds, "b/s")),
|
||||
fmt::arg("bandwidthTotalBits",
|
||||
pow_format((bandwidth_up + bandwidth_down) * 8ull / elapsed_seconds, "b/s")),
|
||||
fmt::arg("bandwidthDownOctets", pow_format(bandwidth_down / elapsed_seconds, "o/s")),
|
||||
fmt::arg("bandwidthUpOctets", pow_format(bandwidth_up / elapsed_seconds, "o/s")),
|
||||
fmt::arg("bandwidthTotalOctets",
|
||||
pow_format((bandwidth_up + bandwidth_down) / (interval_.count() / 1000.0), "o/s")),
|
||||
fmt::arg("bandwidthDownBytes",
|
||||
pow_format(bandwidth_down / (interval_.count() / 1000.0), "B/s")),
|
||||
fmt::arg("bandwidthUpBytes", pow_format(bandwidth_up / (interval_.count() / 1000.0), "B/s")),
|
||||
pow_format((bandwidth_up + bandwidth_down) / elapsed_seconds, "o/s")),
|
||||
fmt::arg("bandwidthDownBytes", pow_format(bandwidth_down / elapsed_seconds, "B/s")),
|
||||
fmt::arg("bandwidthUpBytes", pow_format(bandwidth_up / elapsed_seconds, "B/s")),
|
||||
fmt::arg("bandwidthTotalBytes",
|
||||
pow_format((bandwidth_up + bandwidth_down) / (interval_.count() / 1000.0), "B/s")));
|
||||
pow_format((bandwidth_up + bandwidth_down) / elapsed_seconds, "B/s")));
|
||||
if (text.compare(label_.get_label()) != 0) {
|
||||
label_.set_markup(text);
|
||||
if (text.empty()) {
|
||||
@@ -373,27 +391,18 @@ auto waybar::modules::Network::update() -> void {
|
||||
fmt::arg("ipaddr", final_ipaddr_), fmt::arg("gwaddr", gwaddr_), fmt::arg("cidr", cidr_),
|
||||
fmt::arg("cidr6", cidr6_), fmt::arg("frequency", fmt::format("{:.1f}", frequency_)),
|
||||
fmt::arg("icon", getIcon(signal_strength_, state_)),
|
||||
fmt::arg("bandwidthDownBits",
|
||||
pow_format(bandwidth_down * 8ull / (interval_.count() / 1000.0), "b/s")),
|
||||
fmt::arg("bandwidthUpBits",
|
||||
pow_format(bandwidth_up * 8ull / (interval_.count() / 1000.0), "b/s")),
|
||||
fmt::arg("bandwidthDownBits", pow_format(bandwidth_down * 8ull / elapsed_seconds, "b/s")),
|
||||
fmt::arg("bandwidthUpBits", pow_format(bandwidth_up * 8ull / elapsed_seconds, "b/s")),
|
||||
fmt::arg("bandwidthTotalBits",
|
||||
pow_format((bandwidth_up + bandwidth_down) * 8ull / (interval_.count() / 1000.0),
|
||||
"b/s")),
|
||||
fmt::arg("bandwidthDownOctets",
|
||||
pow_format(bandwidth_down / (interval_.count() / 1000.0), "o/s")),
|
||||
fmt::arg("bandwidthUpOctets",
|
||||
pow_format(bandwidth_up / (interval_.count() / 1000.0), "o/s")),
|
||||
fmt::arg(
|
||||
"bandwidthTotalOctets",
|
||||
pow_format((bandwidth_up + bandwidth_down) / (interval_.count() / 1000.0), "o/s")),
|
||||
fmt::arg("bandwidthDownBytes",
|
||||
pow_format(bandwidth_down / (interval_.count() / 1000.0), "B/s")),
|
||||
fmt::arg("bandwidthUpBytes",
|
||||
pow_format(bandwidth_up / (interval_.count() / 1000.0), "B/s")),
|
||||
fmt::arg(
|
||||
"bandwidthTotalBytes",
|
||||
pow_format((bandwidth_up + bandwidth_down) / (interval_.count() / 1000.0), "B/s")));
|
||||
pow_format((bandwidth_up + bandwidth_down) * 8ull / elapsed_seconds, "b/s")),
|
||||
fmt::arg("bandwidthDownOctets", pow_format(bandwidth_down / elapsed_seconds, "o/s")),
|
||||
fmt::arg("bandwidthUpOctets", pow_format(bandwidth_up / elapsed_seconds, "o/s")),
|
||||
fmt::arg("bandwidthTotalOctets",
|
||||
pow_format((bandwidth_up + bandwidth_down) / elapsed_seconds, "o/s")),
|
||||
fmt::arg("bandwidthDownBytes", pow_format(bandwidth_down / elapsed_seconds, "B/s")),
|
||||
fmt::arg("bandwidthUpBytes", pow_format(bandwidth_up / elapsed_seconds, "B/s")),
|
||||
fmt::arg("bandwidthTotalBytes",
|
||||
pow_format((bandwidth_up + bandwidth_down) / elapsed_seconds, "B/s")));
|
||||
if (label_.get_tooltip_text() != tooltip_text) {
|
||||
label_.set_tooltip_markup(tooltip_text);
|
||||
}
|
||||
@@ -684,12 +693,15 @@ int waybar::modules::Network::handleEvents(struct nl_msg* msg, void* data) {
|
||||
changed_cidr);
|
||||
}
|
||||
} else {
|
||||
if (ifa->ifa_family == AF_INET) {
|
||||
net->ipaddr_.clear();
|
||||
net->ipaddr6_.clear();
|
||||
net->cidr_ = 0;
|
||||
net->cidr6_ = 0;
|
||||
net->netmask_.clear();
|
||||
} else if (ifa->ifa_family == AF_INET6) {
|
||||
net->ipaddr6_.clear();
|
||||
net->cidr6_ = 0;
|
||||
net->netmask6_.clear();
|
||||
}
|
||||
spdlog::debug("network: {} addr deleted {}/{}", net->ifname_,
|
||||
inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta), ipaddr, sizeof(ipaddr)),
|
||||
ifa->ifa_prefixlen);
|
||||
@@ -744,16 +756,20 @@ int waybar::modules::Network::handleEvents(struct nl_msg* msg, void* data) {
|
||||
/* The destination address.
|
||||
* Should be either missing, or maybe all 0s. Accept both.
|
||||
*/
|
||||
const uint32_t nr_zeroes = (family == AF_INET) ? 4 : 16;
|
||||
unsigned char c = 0;
|
||||
size_t dstlen = RTA_PAYLOAD(attr);
|
||||
if (dstlen != nr_zeroes) {
|
||||
auto* dest = (const unsigned char*)RTA_DATA(attr);
|
||||
size_t dest_size = RTA_PAYLOAD(attr);
|
||||
for (size_t i = 0; i < dest_size; ++i) {
|
||||
if (dest[i] != 0) {
|
||||
has_destination = true;
|
||||
break;
|
||||
}
|
||||
for (uint32_t i = 0; i < dstlen; i += 1) {
|
||||
c |= *((unsigned char*)RTA_DATA(attr) + i);
|
||||
}
|
||||
has_destination = (c == 0);
|
||||
|
||||
if (rtm->rtm_dst_len != 0) {
|
||||
// We have found a destination like 0.0.0.0/24, this is not a
|
||||
// default gateway route.
|
||||
has_destination = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case RTA_OIF:
|
||||
|
||||
@@ -103,7 +103,9 @@ void Workspaces::doUpdate() {
|
||||
fmt::arg("output", ws["output"].asString()));
|
||||
}
|
||||
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 {
|
||||
button.set_label(name);
|
||||
}
|
||||
@@ -114,6 +116,11 @@ void Workspaces::doUpdate() {
|
||||
button.show();
|
||||
else
|
||||
button.hide();
|
||||
} else if (config_["hide-empty"].asBool()) {
|
||||
if (ws["active_window_id"].isNull() && !ws["is_focused"].asBool())
|
||||
button.hide();
|
||||
else
|
||||
button.show();
|
||||
} else {
|
||||
button.show();
|
||||
}
|
||||
|
||||
@@ -71,6 +71,7 @@ Privacy::Privacy(const std::string& id, const Json::Value& config, Gtk::Orientat
|
||||
auto* item = Gtk::make_managed<PrivacyItem>(module, nodeType, nodePtr, orientation, pos,
|
||||
iconSize, transition_duration);
|
||||
box_.add(*item);
|
||||
modules_.push_back(item);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,9 +149,7 @@ auto Privacy::update() -> void {
|
||||
bool useAudioOut = false;
|
||||
|
||||
mutex_.lock();
|
||||
for (Gtk::Widget* widget : box_.get_children()) {
|
||||
auto* module = dynamic_cast<PrivacyItem*>(widget);
|
||||
if (module == nullptr) continue;
|
||||
for (PrivacyItem* module : modules_) {
|
||||
switch (module->privacy_type) {
|
||||
case util::PipewireBackend::PRIVACY_NODE_TYPE_VIDEO_INPUT:
|
||||
setScreenshare = true;
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
|
||||
namespace waybar::modules::SNI {
|
||||
|
||||
static const unsigned RETRY_DELAY_MS = 200;
|
||||
static const unsigned MAX_RETRIES = 10;
|
||||
|
||||
Host::Host(const std::size_t id, const Json::Value& config, const Bar& bar,
|
||||
const std::function<void(std::unique_ptr<Item>&)>& on_add,
|
||||
const std::function<void(std::unique_ptr<Item>&)>& on_remove,
|
||||
@@ -22,6 +25,7 @@ Host::Host(const std::size_t id, const Json::Value& config, const Bar& bar,
|
||||
on_update_(on_update) {}
|
||||
|
||||
Host::~Host() {
|
||||
retry_connection_.disconnect();
|
||||
if (bus_name_id_ > 0) {
|
||||
Gio::DBus::unown_name(bus_name_id_);
|
||||
bus_name_id_ = 0;
|
||||
@@ -53,6 +57,8 @@ void Host::nameAppeared(const Glib::RefPtr<Gio::DBus::Connection>& conn, const G
|
||||
}
|
||||
|
||||
void Host::nameVanished(const Glib::RefPtr<Gio::DBus::Connection>& conn, const Glib::ustring name) {
|
||||
retry_connection_.disconnect();
|
||||
retry_count_ = 0;
|
||||
g_cancellable_cancel(cancellable_);
|
||||
g_clear_object(&cancellable_);
|
||||
g_clear_object(&watcher_);
|
||||
@@ -72,11 +78,30 @@ void Host::proxyReady(GObject* src, GAsyncResult* res, gpointer data) {
|
||||
return;
|
||||
}
|
||||
auto host = static_cast<SNI::Host*>(data);
|
||||
host->watcher_ = watcher;
|
||||
if (error != nullptr) {
|
||||
spdlog::error("Host: {}", error->message);
|
||||
g_clear_object(&host->cancellable_);
|
||||
if (host->retry_count_ >= MAX_RETRIES) {
|
||||
spdlog::warn("Host: giving up on watcher proxy creation after {} retries", host->retry_count_);
|
||||
return;
|
||||
}
|
||||
host->retry_count_ += 1;
|
||||
// Store the timeout connection so it is disconnected in ~Host, avoiding a
|
||||
// use-after-free if the Host is destroyed before the retry fires.
|
||||
host->retry_connection_ = Glib::signal_timeout().connect(
|
||||
[host]() {
|
||||
if (host->watcher_ != nullptr) {
|
||||
return false;
|
||||
}
|
||||
auto conn = Gio::DBus::Connection::get_sync(Gio::DBus::BusType::BUS_TYPE_SESSION);
|
||||
host->nameAppeared(conn, "org.kde.StatusNotifierWatcher", "");
|
||||
return false;
|
||||
},
|
||||
RETRY_DELAY_MS);
|
||||
return;
|
||||
}
|
||||
host->retry_count_ = 0;
|
||||
host->watcher_ = watcher;
|
||||
sn_watcher_call_register_host(host->watcher_, host->object_path_.c_str(), host->cancellable_,
|
||||
&Host::registerHost, data);
|
||||
}
|
||||
@@ -178,9 +203,11 @@ void Host::addRegisteredItem(const std::string& service) {
|
||||
return bus_name == item->bus_name && object_path == item->object_path;
|
||||
});
|
||||
if (it == items_.end()) {
|
||||
items_.emplace_back(new Item(
|
||||
bus_name, object_path, config_, bar_, [this](Item& item) { itemReady(item); },
|
||||
[this](Item& item) { itemInvalidated(item); }, on_update_));
|
||||
items_.emplace_back(std::make_unique<Item>(
|
||||
bus_name, object_path, config_, bar_,
|
||||
[this](Item& item) { itemReady(item); },
|
||||
[this](Item& item) { itemInvalidated(item); },
|
||||
on_update_));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -190,9 +190,17 @@ void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) {
|
||||
} else if (name == "Status") {
|
||||
setStatus(get_variant<Glib::ustring>(value));
|
||||
} else if (name == "IconName") {
|
||||
if (has_custom_icon_) {
|
||||
spdlog::trace("Item '{}': ignoring IconName update, custom icon is set", id);
|
||||
} else {
|
||||
icon_name = get_variant<std::string>(value);
|
||||
}
|
||||
} else if (name == "IconPixmap") {
|
||||
if (has_custom_icon_) {
|
||||
spdlog::trace("Item '{}': ignoring IconPixmap update, custom icon is set", id);
|
||||
} else {
|
||||
icon_pixmap = this->extractPixBuf(value.gobj());
|
||||
}
|
||||
} else if (name == "OverlayIconName") {
|
||||
overlay_icon_name = get_variant<std::string>(value);
|
||||
} 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);
|
||||
icon_name = ""; // icon_name has priority over pixmap
|
||||
icon_pixmap = custom_pixbuf;
|
||||
has_custom_icon_ = true;
|
||||
} catch (const Glib::Error& e) {
|
||||
spdlog::error("Failed to load custom icon {}: {}", custom_icon, e.what());
|
||||
}
|
||||
} else { // if file doesn't exist it's most likely an icon_name
|
||||
icon_name = custom_icon;
|
||||
has_custom_icon_ = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,9 +24,6 @@ Tray::Tray(const std::string& id, const Bar& bar, const Json::Value& config)
|
||||
if (config_["spacing"].isUInt()) {
|
||||
box_.set_spacing(config_["spacing"].asUInt());
|
||||
}
|
||||
if (config["show-passive-items"].isBool()) {
|
||||
show_passive_ = config["show-passive-items"].asBool();
|
||||
}
|
||||
nb_hosts_ += 1;
|
||||
if (config_["icons"].isObject()) {
|
||||
IconManager::instance().setIconsConfig(config_["icons"]);
|
||||
@@ -42,6 +39,8 @@ void Tray::onAdd(std::unique_ptr<Item>& item) {
|
||||
} else {
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -51,17 +50,9 @@ void Tray::onRemove(std::unique_ptr<Item>& item) {
|
||||
}
|
||||
|
||||
auto Tray::update() -> void {
|
||||
// Show tray only when items are available
|
||||
std::vector<Gtk::Widget*> children = box_.get_children();
|
||||
if (show_passive_) {
|
||||
event_box_.set_visible(!children.empty());
|
||||
} 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
|
||||
event_box_.set_visible(std::any_of(children.begin(), children.end(),
|
||||
[](Gtk::Widget* child) { return child->get_visible(); }));
|
||||
AModule::update();
|
||||
}
|
||||
|
||||
|
||||
@@ -54,21 +54,28 @@ void Language::onCmd(const struct Ipc::ipc_response& res) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
auto payload = parser_.parse(res.payload);
|
||||
std::vector<std::string> used_layouts;
|
||||
// Display current layout of a device with a maximum count of layouts, expecting that all will
|
||||
// be OK
|
||||
// Union layout names across every keyboard input so hot-plugged devices contribute their
|
||||
// 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;
|
||||
for (Json::ArrayIndex i = 0; i < payload.size(); i++) {
|
||||
auto size = payload[i][XKB_LAYOUT_NAMES_KEY].size();
|
||||
if (size > max) {
|
||||
max = size;
|
||||
if (payload[i]["type"].asString() != "keyboard") continue;
|
||||
const auto& names = payload[i][XKB_LAYOUT_NAMES_KEY];
|
||||
if (names.size() > max) {
|
||||
max = names.size();
|
||||
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]) {
|
||||
used_layouts.push_back(layout.asString());
|
||||
}
|
||||
|
||||
// Rebuild from scratch so init_layouts_map's duplicate-suffix pass doesn't compound across
|
||||
// refreshes (e.g. "us" -> "us1" -> "us11").
|
||||
layouts_map_.clear();
|
||||
init_layouts_map(used_layouts);
|
||||
set_current_layout(payload[max_id][XKB_ACTIVE_LAYOUT_NAME_KEY].asString());
|
||||
dp.emit();
|
||||
@@ -82,13 +89,26 @@ void Language::onEvent(const struct Ipc::ipc_response& res) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool refresh_inputs = false;
|
||||
try {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
auto payload = parser_.parse(res.payload)["input"];
|
||||
auto root = parser_.parse(res.payload);
|
||||
auto change = root["change"].asString();
|
||||
auto payload = root["input"];
|
||||
if (payload["type"].asString() == "keyboard") {
|
||||
// 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);
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
spdlog::error("Language: {}", e.what());
|
||||
}
|
||||
@@ -125,8 +145,15 @@ auto Language::update() -> 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);
|
||||
layout_ = layouts_map_[current_layout];
|
||||
layout_ = it->second;
|
||||
label_.get_style_context()->add_class(layout_.short_name);
|
||||
}
|
||||
|
||||
|
||||
@@ -229,7 +229,10 @@ bool Workspaces::filterButtons() {
|
||||
auto ws = std::find_if(workspaces_.begin(), workspaces_.end(),
|
||||
[it](const auto& node) { return node["name"].asString() == it->first; });
|
||||
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);
|
||||
needReorder = true;
|
||||
} else {
|
||||
@@ -329,7 +332,12 @@ auto Workspaces::update() -> void {
|
||||
} else {
|
||||
button.get_style_context()->remove_class("current_output");
|
||||
}
|
||||
std::string output = (*it)["name"].asString();
|
||||
std::string output;
|
||||
if (!config_["disable-markup"].asBool()) {
|
||||
output = g_markup_escape_text((*it)["name"].asString().c_str(), -1);
|
||||
} else {
|
||||
output = (*it)["name"].asString();
|
||||
}
|
||||
std::string windows = "";
|
||||
if (config_["window-rewrite"].isObject()) {
|
||||
updateWindows((*it), windows);
|
||||
@@ -369,11 +377,14 @@ Gtk::Button& Workspaces::addButton(const Json::Value& node) {
|
||||
node["name"].asString(), node["target_output"].asString(),
|
||||
"--no-auto-back-and-forth", node["name"].asString()));
|
||||
} else {
|
||||
ipc_.sendCmd(IPC_COMMAND, fmt::format("workspace {} \"{}\"",
|
||||
config_["disable-auto-back-and-forth"].asBool()
|
||||
std::string flag = config_["disable-auto-back-and-forth"].asBool()
|
||||
? "--no-auto-back-and-forth"
|
||||
: "",
|
||||
node["name"].asString()));
|
||||
: "";
|
||||
if (node["num"].asInt() >= 0) {
|
||||
ipc_.sendCmd(IPC_COMMAND, fmt::format(workspace_switch_number_cmd_, flag, node["num"].asInt()));
|
||||
} else {
|
||||
ipc_.sendCmd(IPC_COMMAND, fmt::format(workspace_switch_cmd_, flag, node["name"].asString()));
|
||||
}
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
spdlog::error("Workspaces: {}", e.what());
|
||||
|
||||
@@ -84,6 +84,9 @@ SystemdFailedUnits::SystemdFailedUnits(const std::string& id, const Json::Value&
|
||||
}
|
||||
}
|
||||
|
||||
if (!user_props_proxy_ && !system_props_proxy_)
|
||||
throw std::runtime_error("Neither system nor user status is requested.");
|
||||
|
||||
updateData();
|
||||
/* Always update for the first time. */
|
||||
dp.emit();
|
||||
@@ -103,7 +106,7 @@ auto SystemdFailedUnits::notify_cb(const Glib::ustring& sender_name,
|
||||
void SystemdFailedUnits::RequestSystemState() {
|
||||
auto load = [](const char* kind, Glib::RefPtr<Gio::DBus::Proxy>& proxy) -> std::string {
|
||||
try {
|
||||
if (!proxy) return "unknown";
|
||||
if (!proxy) return "ignored";
|
||||
auto parameters = Glib::VariantContainerBase(
|
||||
g_variant_new("(ss)", "org.freedesktop.systemd1.Manager", "SystemState"));
|
||||
Glib::VariantContainerBase data = proxy->call_sync("Get", parameters);
|
||||
@@ -122,7 +125,9 @@ void SystemdFailedUnits::RequestSystemState() {
|
||||
|
||||
system_state_ = load("systemwide", system_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";
|
||||
else
|
||||
overall_state_ = "degraded";
|
||||
|
||||
@@ -155,3 +155,7 @@ bool waybar::modules::Temperature::isCritical(uint16_t temperature_c) {
|
||||
return config_["critical-threshold"].isInt() &&
|
||||
temperature_c >= config_["critical-threshold"].asInt();
|
||||
}
|
||||
|
||||
void waybar::modules::Temperature::suspend() { thread_.pause(); }
|
||||
|
||||
void waybar::modules::Temperature::resume() { thread_.resume(); }
|
||||
|
||||
@@ -347,10 +347,13 @@ auto IPC::update_state_handler(const std::string& event, const Json::Value& data
|
||||
|
||||
if (event == "output-wset-changed") {
|
||||
// data: { event, new-wset: wset.name, output: id, new-wset-data: wset, output-data: output }
|
||||
try {
|
||||
auto& output = state.outputs.at(data["output-data"]["name"].asString());
|
||||
auto wset_idx = data["new-wset-data"]["index"].asUInt();
|
||||
state.wsets.at(wset_idx).output = output;
|
||||
output.wset_idx = wset_idx;
|
||||
} catch (const std::exception&) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -211,6 +211,7 @@ void waybar::modules::Wireplumber::onMixerChanged(waybar::modules::Wireplumber*
|
||||
return;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
spdlog::warn("[{}]: (onMixerChanged: {}) - Object with id {} not found", self->name_,
|
||||
|
||||
@@ -198,7 +198,7 @@ void Task::handle_title(const char* title) {
|
||||
title_ = title;
|
||||
hide_if_ignored();
|
||||
|
||||
if (!with_icon_ && !with_name_ || app_info_) {
|
||||
if ((!with_icon_ && !with_name_) || app_info_) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -99,6 +99,7 @@ void AudioBackend::contextStateCb(pa_context* c, void* data) {
|
||||
nullptr, nullptr);
|
||||
break;
|
||||
case PA_CONTEXT_FAILED:
|
||||
if (pa_context_errno(c) != PA_ERR_CONNECTIONREFUSED) {
|
||||
// When pulseaudio server restarts, the connection is "failed". Try to reconnect.
|
||||
// pa_threaded_mainloop_lock is already acquired in callback threads.
|
||||
// So there is no need to lock it again.
|
||||
@@ -108,6 +109,7 @@ void AudioBackend::contextStateCb(pa_context* c, void* data) {
|
||||
backend->context_ = nullptr;
|
||||
}
|
||||
backend->connectContext();
|
||||
}
|
||||
break;
|
||||
case PA_CONTEXT_CONNECTING:
|
||||
case PA_CONTEXT_AUTHORIZING:
|
||||
|
||||
@@ -23,7 +23,8 @@ namespace {
|
||||
const std::regex IMPORT_REGEX(R"(@import\s+(?:url\()?(?:"|')([^"')]+)(?:"|')\)?;)");
|
||||
}
|
||||
|
||||
waybar::CssReloadHelper::CssReloadHelper(std::string cssFile, std::function<void()> callback)
|
||||
waybar::CssReloadHelper::CssReloadHelper(std::string cssFile,
|
||||
std::function<void(const std::string&)> callback)
|
||||
: m_cssFile(std::move(cssFile)), m_callback(std::move(callback)) {}
|
||||
|
||||
std::string waybar::CssReloadHelper::getFileContents(const std::string& filename) {
|
||||
@@ -88,6 +89,12 @@ void waybar::CssReloadHelper::monitorChanges() {
|
||||
}
|
||||
}
|
||||
|
||||
void waybar::CssReloadHelper::changeCssFile(const std::string& newCssFile) {
|
||||
m_fileMonitors.clear();
|
||||
m_cssFile = newCssFile;
|
||||
monitorChanges();
|
||||
}
|
||||
|
||||
void waybar::CssReloadHelper::handleFileChange(Glib::RefPtr<Gio::File> const& file,
|
||||
Glib::RefPtr<Gio::File> const& other_type,
|
||||
Gio::FileMonitorEvent event_type) {
|
||||
@@ -95,7 +102,7 @@ void waybar::CssReloadHelper::handleFileChange(Glib::RefPtr<Gio::File> const& fi
|
||||
// fire for one
|
||||
if (event_type == Gio::FileMonitorEvent::FILE_MONITOR_EVENT_CHANGES_DONE_HINT) {
|
||||
spdlog::debug("Reloading style, file changed: {}", file->get_path());
|
||||
m_callback();
|
||||
m_callback(m_cssFile);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ std::string rewriteString(const std::string& value, const Json::Value& rules) {
|
||||
// malformated regexes will cause an exception.
|
||||
// in this case, log error and try the next rule.
|
||||
const std::regex rule{it.key().asString(), std::regex_constants::icase};
|
||||
if (std::regex_match(value, rule)) {
|
||||
if (std::regex_match(res, rule)) {
|
||||
res = std::regex_replace(res, rule, it->asString());
|
||||
}
|
||||
} catch (const std::regex_error& e) {
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <regex>
|
||||
#include <sstream>
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
struct TransformResult {
|
||||
std::string css;
|
||||
bool was_transformed;
|
||||
};
|
||||
|
||||
TransformResult transform_8bit_to_hex(const std::string& file_path) {
|
||||
std::ifstream f(file_path, std::ios::in | std::ios::binary);
|
||||
const auto size = fs::file_size(file_path);
|
||||
std::string result(size, '\0');
|
||||
if (!f.is_open() || !f.good()) {
|
||||
throw std::runtime_error("Cannot open file: " + file_path);
|
||||
}
|
||||
|
||||
if (size == 0) {
|
||||
return {.css = result, .was_transformed = false};
|
||||
}
|
||||
|
||||
f.read(result.data(), size);
|
||||
|
||||
static std::regex pattern(
|
||||
R"(\#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2}))");
|
||||
std::string final_output;
|
||||
|
||||
auto it = std::sregex_iterator(result.begin(), result.end(), pattern);
|
||||
auto eof = std::sregex_iterator();
|
||||
|
||||
if (it == eof) {
|
||||
return {.css = result, .was_transformed = false};
|
||||
}
|
||||
|
||||
std::smatch match;
|
||||
while (it != eof) {
|
||||
match = *it;
|
||||
|
||||
final_output += match.prefix().str();
|
||||
|
||||
int r = stoi(match[1].str(), nullptr, 16);
|
||||
int g = stoi(match[2].str(), nullptr, 16);
|
||||
int b = stoi(match[3].str(), nullptr, 16);
|
||||
double a = (stoi(match[4].str(), nullptr, 16) / 255.0);
|
||||
|
||||
std::stringstream ss;
|
||||
ss << "rgba(" << r << "," << g << "," << b << "," << std::fixed << std::setprecision(2) << a
|
||||
<< ")";
|
||||
final_output += ss.str();
|
||||
|
||||
++it;
|
||||
}
|
||||
|
||||
final_output += match.suffix().str();
|
||||
|
||||
return {.css = final_output, .was_transformed = true};
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
#include <glib.h>
|
||||
|
||||
#include <string>
|
||||
#include <util/utf8_string.hpp>
|
||||
|
||||
namespace waybar::util {
|
||||
|
||||
namespace {
|
||||
// Wide characters count as two, zero-width characters count as zero
|
||||
// Modifies str in-place (unless width = std::string::npos)
|
||||
// Returns the total width of the string pre-truncating
|
||||
size_t measure_and_truncate(std::string& str, size_t width = std::string::npos) {
|
||||
if (str.length() == 0) return 0;
|
||||
|
||||
const gchar* trunc_end = nullptr;
|
||||
|
||||
size_t total_width = 0;
|
||||
|
||||
for (gchar *data = str.data(), *end = data + str.size(); data != nullptr;) {
|
||||
gunichar c = g_utf8_get_char_validated(data, end - data);
|
||||
if (c == -1U || c == -2U) {
|
||||
// invalid unicode, treat string as ascii
|
||||
if (width != std::string::npos && str.length() > width) str.resize(width);
|
||||
return str.length();
|
||||
} else if (g_unichar_iswide(c)) {
|
||||
total_width += 2;
|
||||
} else if (!g_unichar_iszerowidth(c) && c != 0xAD) { // neither zero-width nor soft hyphen
|
||||
total_width += 1;
|
||||
}
|
||||
|
||||
data = g_utf8_find_next_char(data, end);
|
||||
if (width != std::string::npos && total_width <= width && !g_unichar_isspace(c))
|
||||
trunc_end = data;
|
||||
}
|
||||
|
||||
if (trunc_end) str.resize(trunc_end - str.data());
|
||||
|
||||
return total_width;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
size_t utf8_width(const std::string& str) {
|
||||
return measure_and_truncate(const_cast<std::string&>(str));
|
||||
}
|
||||
|
||||
void utf8_truncate(std::string& s, const std::string& ellipsis, size_t max_len) {
|
||||
if (max_len == 0) {
|
||||
s.resize(0);
|
||||
return;
|
||||
}
|
||||
size_t len = measure_and_truncate(s, max_len);
|
||||
if (len > max_len) {
|
||||
size_t ellipsis_len = utf8_width(ellipsis);
|
||||
if (max_len >= ellipsis_len) {
|
||||
if (ellipsis_len) measure_and_truncate(s, max_len - ellipsis_len);
|
||||
s += ellipsis;
|
||||
} else {
|
||||
s.resize(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace waybar::util
|
||||
@@ -4,9 +4,9 @@
|
||||
#depth = 1
|
||||
|
||||
[wrap-file]
|
||||
directory = cava-0.10.7-beta
|
||||
source_url = https://github.com/LukashonakV/cava/archive/v0.10.7-beta.tar.gz
|
||||
directory = cava-0.10.7
|
||||
source_url = https://github.com/LukashonakV/cava/archive/0.10.7.tar.gz
|
||||
source_filename = cava-0.10.7.tar.gz
|
||||
source_hash = 8915d7214f2046554c158fe6f2ae518881dfb573e421ea848727be11a5dfa8c4
|
||||
source_hash = 50cc6413e9c96c503657f814744a2baf429a24ff9fed31a8343e0ed285269eff
|
||||
[provide]
|
||||
libcava = cava_dep
|
||||
|
||||
@@ -15,6 +15,10 @@ namespace {
|
||||
class IPCTestHelper : public hyprland::IPC {
|
||||
public:
|
||||
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() {
|
||||
@@ -133,3 +137,78 @@ TEST_CASE("getSocket1Reply failure paths do not leak fds", "[getSocket1Reply][fd
|
||||
REQUIRE(after_connect_failures == baseline);
|
||||
}
|
||||
#endif
|
||||
|
||||
// --- Tests for new Lua IPC dispatch functions ---
|
||||
|
||||
TEST_CASE("buildLuaDispatch workspace", "[buildLuaDispatch]") {
|
||||
SECTION("numeric workspace") {
|
||||
auto result = IPCTestHelper::buildLuaDispatch("workspace", "1");
|
||||
REQUIRE(result == "/dispatch hl.dsp.focus({ workspace = \"1\" })");
|
||||
}
|
||||
SECTION("named workspace") {
|
||||
auto result = IPCTestHelper::buildLuaDispatch("workspace", "name:term");
|
||||
REQUIRE(result == "/dispatch hl.dsp.focus({ workspace = \"name:term\" })");
|
||||
}
|
||||
SECTION("relative workspace") {
|
||||
auto result = IPCTestHelper::buildLuaDispatch("workspace", "e+1");
|
||||
REQUIRE(result == "/dispatch hl.dsp.focus({ workspace = \"e+1\" })");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("buildLuaDispatch focusworkspaceoncurrentmonitor", "[buildLuaDispatch]") {
|
||||
auto result =
|
||||
IPCTestHelper::buildLuaDispatch("focusworkspaceoncurrentmonitor", "3");
|
||||
REQUIRE(
|
||||
result ==
|
||||
"/dispatch hl.dsp.focus({ workspace = \"3\", on_current_monitor = true })");
|
||||
}
|
||||
|
||||
TEST_CASE("buildLuaDispatch togglespecialworkspace", "[buildLuaDispatch]") {
|
||||
SECTION("with name") {
|
||||
auto result =
|
||||
IPCTestHelper::buildLuaDispatch("togglespecialworkspace", "scratchpad");
|
||||
REQUIRE(result ==
|
||||
"/dispatch hl.dsp.workspace.toggle_special(\"scratchpad\")");
|
||||
}
|
||||
SECTION("empty arg") {
|
||||
auto result =
|
||||
IPCTestHelper::buildLuaDispatch("togglespecialworkspace", "");
|
||||
REQUIRE(result == "/dispatch hl.dsp.workspace.toggle_special()");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("buildLuaDispatch unknown dispatcher fallback", "[buildLuaDispatch]") {
|
||||
auto result =
|
||||
IPCTestHelper::buildLuaDispatch("unknown_dispatcher", "some_arg");
|
||||
REQUIRE(result ==
|
||||
"/dispatch hl.dsp.unknown_dispatcher(\"some_arg\")");
|
||||
}
|
||||
|
||||
TEST_CASE("dispatch throws when Hyprland is not running", "[dispatch]") {
|
||||
unsetenv("HYPRLAND_INSTANCE_SIGNATURE");
|
||||
IPCTestHelper::resetSocketFolder();
|
||||
IPCTestHelper::resetLuaProtocolDetection();
|
||||
|
||||
CHECK_THROWS(hyprland::IPC::dispatch("workspace", "1"));
|
||||
}
|
||||
|
||||
TEST_CASE("isLuaProtocol uses cached value and avoids socket call",
|
||||
"[isLuaProtocol]") {
|
||||
unsetenv("HYPRLAND_INSTANCE_SIGNATURE");
|
||||
IPCTestHelper::resetSocketFolder();
|
||||
|
||||
SECTION("cached false") {
|
||||
IPCTestHelper::setLuaProtocolDetected(false);
|
||||
// Should return false without throwing (no socket call needed)
|
||||
REQUIRE(IPCTestHelper::isLuaProtocol() == false);
|
||||
}
|
||||
|
||||
SECTION("cached true") {
|
||||
IPCTestHelper::setLuaProtocolDetected(true);
|
||||
// Should return true without throwing (no socket call needed)
|
||||
REQUIRE(IPCTestHelper::isLuaProtocol() == true);
|
||||
}
|
||||
|
||||
// Cleanup: reset detection so other tests aren't affected
|
||||
IPCTestHelper::resetLuaProtocolDetection();
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
class CssReloadHelperTest : public waybar::CssReloadHelper {
|
||||
public:
|
||||
CssReloadHelperTest() : CssReloadHelper("/tmp/waybar_test.css", [this]() { callback(); }) {}
|
||||
CssReloadHelperTest() : CssReloadHelper("/tmp/waybar_test.css", [this](const std::string&) { callback(); }) {}
|
||||
|
||||
void callback() { m_callbackCounter++; }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user