Merge remote-tracking branch 'origin/master' into agd/ignore-tray-applets
# Conflicts: # include/modules/sni/tray.hpp # src/modules/sni/host.cpp # src/modules/sni/tray.cpp
This commit is contained in:
@@ -52,3 +52,4 @@ result-*
|
||||
|
||||
.ccls-cache
|
||||
_codeql_detected_source_root
|
||||
heaptrack*
|
||||
|
||||
@@ -14,10 +14,14 @@ class AIconLabel : public ALabel {
|
||||
bool enable_click = false, bool enable_scroll = false);
|
||||
virtual ~AIconLabel() = default;
|
||||
auto update() -> void override;
|
||||
static std::tuple<std::string, std::string> extractIcon(const std::string& input);
|
||||
|
||||
protected:
|
||||
Gtk::Image image_;
|
||||
Gtk::Box box_;
|
||||
unsigned app_icon_size_{24};
|
||||
|
||||
bool label_contains_icon{false};
|
||||
|
||||
bool iconEnabled() const;
|
||||
};
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
#include <gtkmm/label.h>
|
||||
#include <json/json.h>
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "AModule.hpp"
|
||||
|
||||
namespace waybar {
|
||||
@@ -25,12 +27,19 @@ class ALabel : public AModule {
|
||||
bool alt_ = false;
|
||||
std::string default_format_;
|
||||
|
||||
bool setLabelMarkup(const Glib::ustring& markup);
|
||||
bool setTooltipMarkup(const Glib::ustring& markup);
|
||||
|
||||
bool handleToggle(GdkEventButton* const& e) override;
|
||||
virtual std::string getState(uint8_t value, bool lesser = false);
|
||||
|
||||
std::map<std::string, GtkMenuItem*> submenus_;
|
||||
std::map<std::string, std::string> menuActionsMap_;
|
||||
static void handleGtkMenuEvent(GtkMenuItem* menuitem, gpointer data);
|
||||
|
||||
private:
|
||||
std::optional<Glib::ustring> last_label_markup_;
|
||||
std::optional<Glib::ustring> last_tooltip_markup_;
|
||||
};
|
||||
|
||||
} // namespace waybar
|
||||
|
||||
+8
-1
@@ -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,7 +52,9 @@ class AModule : public IModule {
|
||||
virtual bool handleMouseLeave(GdkEventCrossing* const& ev);
|
||||
virtual bool handleScroll(GdkEventScroll*);
|
||||
virtual bool handleRelease(GdkEventButton* const& ev);
|
||||
GObject* menu_;
|
||||
|
||||
bool disable_on_sleep_{false};
|
||||
GObject* menu_ = nullptr;
|
||||
|
||||
private:
|
||||
bool handleUserEvent(GdkEventButton* const& ev);
|
||||
@@ -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;
|
||||
@@ -99,6 +101,7 @@ class Bar : public sigc::trackable {
|
||||
void setMode(const bar_mode&);
|
||||
void setPassThrough(bool passthrough);
|
||||
void setPosition(Gtk::PositionType position);
|
||||
void forceLayerCommit();
|
||||
void onConfigure(GdkEventConfigure* ev);
|
||||
void configureGlobalOffset(int width, int height);
|
||||
void onOutputGeometryChanged();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
namespace waybar {
|
||||
|
||||
class Group : public AModule {
|
||||
sigc::connection reveal_timeout_;
|
||||
|
||||
public:
|
||||
Group(const std::string&, const std::string&, const Json::Value&, bool);
|
||||
~Group() override = default;
|
||||
@@ -26,10 +28,12 @@ class Group : public AModule {
|
||||
bool is_first_widget = true;
|
||||
bool is_drawer = false;
|
||||
bool click_to_reveal = false;
|
||||
int reveal_delay = 0;
|
||||
std::string add_class_to_drawer_children;
|
||||
bool handleMouseEnter(GdkEventCrossing* const& ev) override;
|
||||
bool handleMouseLeave(GdkEventCrossing* const& ev) override;
|
||||
bool handleToggle(GdkEventButton* const& ev) override;
|
||||
bool handleScroll(GdkEventScroll* e) override;
|
||||
void show_group();
|
||||
void hide_group();
|
||||
};
|
||||
|
||||
@@ -6,9 +6,10 @@
|
||||
#if defined(__linux__)
|
||||
#include <sys/inotify.h>
|
||||
#endif
|
||||
#include <sys/poll.h>
|
||||
#include <poll.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -51,6 +52,11 @@ class Battery : public ALabel {
|
||||
bool warnFirstTime_{true};
|
||||
bool weightedAverage_{true};
|
||||
const Bar& bar_;
|
||||
bool smoothPowerEnable_{false};
|
||||
double time_constant_s_{260.0};
|
||||
double smooth_power_{0.0}; // µW
|
||||
std::chrono::steady_clock::time_point last_t_{std::chrono::steady_clock::now()};
|
||||
std::string old_status_raw_{""};
|
||||
|
||||
util::SleeperThread thread_;
|
||||
util::SleeperThread thread_battery_update_;
|
||||
|
||||
@@ -35,6 +35,7 @@ class Custom : public ALabel {
|
||||
std::string id_;
|
||||
std::string alt_;
|
||||
std::string tooltip_;
|
||||
std::string last_tooltip_markup_;
|
||||
const bool tooltip_format_enabled_;
|
||||
std::vector<std::string> class_;
|
||||
int percentage_;
|
||||
|
||||
@@ -22,7 +22,7 @@ class Disk : public ALabel {
|
||||
std::string path_;
|
||||
std::string unit_;
|
||||
|
||||
float calc_specific_divisor(const std::string divisor);
|
||||
float calc_specific_divisor(const std::string& divisor);
|
||||
};
|
||||
|
||||
} // namespace waybar::modules
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <filesystem>
|
||||
#include <list>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
@@ -34,19 +36,33 @@ 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&);
|
||||
|
||||
std::thread ipcThread_;
|
||||
std::mutex callbackMutex_;
|
||||
std::mutex socketMutex_;
|
||||
util::JsonParser parser_;
|
||||
std::list<std::pair<std::string, EventHandler*>> callbacks_;
|
||||
int socketfd_; // the hyprland socket file descriptor
|
||||
pid_t socketOwnerPid_;
|
||||
bool running_ = true; // the ipcThread will stop running when this is false
|
||||
int socketfd_ = -1; // the hyprland socket file descriptor
|
||||
pid_t socketOwnerPid_ = -1;
|
||||
std::atomic<bool> running_ = true; // the ipcThread will stop running when this is false
|
||||
};
|
||||
}; // namespace waybar::modules::hyprland
|
||||
|
||||
@@ -20,8 +20,8 @@ class Window : public waybar::AAppIconLabel, public EventHandler {
|
||||
|
||||
private:
|
||||
struct Workspace {
|
||||
int id;
|
||||
int windows;
|
||||
int id = 0;
|
||||
int windows = 0;
|
||||
std::string last_window;
|
||||
std::string last_window_title;
|
||||
|
||||
@@ -29,14 +29,14 @@ class Window : public waybar::AAppIconLabel, public EventHandler {
|
||||
};
|
||||
|
||||
struct WindowData {
|
||||
bool floating;
|
||||
bool floating = false;
|
||||
int monitor = -1;
|
||||
std::string class_name;
|
||||
std::string initial_class_name;
|
||||
std::string title;
|
||||
std::string initial_title;
|
||||
bool fullscreen;
|
||||
bool grouped;
|
||||
bool fullscreen = false;
|
||||
bool grouped = false;
|
||||
|
||||
static auto parse(const Json::Value&) -> WindowData;
|
||||
};
|
||||
@@ -47,7 +47,7 @@ class Window : public waybar::AAppIconLabel, public EventHandler {
|
||||
void queryActiveWorkspace();
|
||||
void setClass(const std::string&, bool enable);
|
||||
|
||||
bool separateOutputs_;
|
||||
bool separateOutputs_ = false;
|
||||
std::mutex mutex_;
|
||||
const Bar& bar_;
|
||||
util::JsonParser parser_;
|
||||
@@ -55,11 +55,11 @@ class Window : public waybar::AAppIconLabel, public EventHandler {
|
||||
Workspace workspace_;
|
||||
std::string soloClass_;
|
||||
std::string lastSoloClass_;
|
||||
bool solo_;
|
||||
bool allFloating_;
|
||||
bool swallowing_;
|
||||
bool fullscreen_;
|
||||
bool focused_;
|
||||
bool solo_ = false;
|
||||
bool allFloating_ = false;
|
||||
bool swallowing_ = false;
|
||||
bool fullscreen_ = false;
|
||||
bool focused_ = false;
|
||||
|
||||
IPC& m_ipc;
|
||||
};
|
||||
|
||||
@@ -40,10 +40,11 @@ struct WindowRepr {
|
||||
|
||||
class WindowCreationPayload {
|
||||
public:
|
||||
WindowCreationPayload(std::string workspace_name, WindowAddress window_address,
|
||||
WindowCreationPayload(const std::string& workspace_name, WindowAddress window_address,
|
||||
WindowRepr window_repr);
|
||||
WindowCreationPayload(std::string workspace_name, WindowAddress window_address,
|
||||
std::string window_class, std::string window_title, bool is_active);
|
||||
WindowCreationPayload(const std::string& workspace_name, WindowAddress window_address,
|
||||
const std::string& window_class, const std::string& window_title,
|
||||
bool is_active);
|
||||
WindowCreationPayload(Json::Value const& client_data);
|
||||
|
||||
int incrementTimeSpentUncreated();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <gtkmm/enums.h>
|
||||
#include <gtkmm/label.h>
|
||||
#include <json/value.h>
|
||||
#include <sigc++/connection.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
@@ -43,6 +44,7 @@ class Workspaces : public AModule, public EventHandler {
|
||||
auto moveToMonitor() const -> bool { return m_moveToMonitor; }
|
||||
auto enableTaskbar() const -> bool { return m_enableTaskbar; }
|
||||
auto taskbarWithIcon() const -> bool { return m_taskbarWithIcon; }
|
||||
auto barScroll() const -> bool { return m_barScroll; }
|
||||
|
||||
auto getBarOutput() const -> std::string { return m_bar.output->name; }
|
||||
auto formatBefore() const -> std::string { return m_formatBefore; }
|
||||
@@ -58,7 +60,7 @@ class Workspaces : public AModule, public EventHandler {
|
||||
enum class ActiveWindowPosition { NONE, FIRST, LAST };
|
||||
auto activeWindowPosition() const -> ActiveWindowPosition { return m_activeWindowPosition; }
|
||||
|
||||
std::string getRewrite(std::string window_class, std::string window_title);
|
||||
std::string getRewrite(const std::string& window_class, const std::string& window_title);
|
||||
std::string& getWindowSeparator() { return m_formatWindowSeparator; }
|
||||
bool isWorkspaceIgnored(std::string const& workspace_name);
|
||||
|
||||
@@ -122,6 +124,8 @@ class Workspaces : public AModule, public EventHandler {
|
||||
static std::pair<std::string, std::string> splitDoublePayload(std::string const& payload);
|
||||
static std::tuple<std::string, std::string, std::string> splitTriplePayload(
|
||||
std::string const& payload);
|
||||
// scroll events
|
||||
bool handleScroll(GdkEventScroll* e) override;
|
||||
|
||||
// Update methods
|
||||
void doUpdate();
|
||||
@@ -145,6 +149,7 @@ class Workspaces : public AModule, public EventHandler {
|
||||
bool m_specialVisibleOnly = false;
|
||||
bool m_persistentOnly = false;
|
||||
bool m_moveToMonitor = false;
|
||||
bool m_barScroll = false;
|
||||
Json::Value m_persistentWorkspaceConfig;
|
||||
|
||||
// Map for windows stored in workspaces not present in the current bar.
|
||||
@@ -204,6 +209,7 @@ class Workspaces : public AModule, public EventHandler {
|
||||
std::mutex m_mutex;
|
||||
const Bar& m_bar;
|
||||
Gtk::Box m_box;
|
||||
sigc::connection m_scrollEventConnection_;
|
||||
IPC& m_ipc;
|
||||
};
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <fmt/chrono.h>
|
||||
#include <gtkmm/label.h>
|
||||
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
#include <unordered_map>
|
||||
|
||||
@@ -35,12 +36,12 @@ class KeyboardState : public AModule {
|
||||
std::string capslock_format_;
|
||||
std::string scrolllock_format_;
|
||||
const std::chrono::seconds interval_;
|
||||
std::string icon_locked_;
|
||||
std::string icon_unlocked_;
|
||||
std::unordered_map<std::string, std::vector<std::string>> key_icon_states_;
|
||||
std::string devices_path_;
|
||||
|
||||
struct libinput* libinput_;
|
||||
std::unordered_map<std::string, struct libinput_device*> libinput_devices_;
|
||||
std::mutex devices_mutex_; // protects libinput_devices_
|
||||
std::set<int> binding_keys;
|
||||
|
||||
util::SleeperThread libinput_thread_, hotplug_thread_;
|
||||
|
||||
@@ -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&);
|
||||
@@ -44,7 +45,11 @@ class MPD : public ALabel {
|
||||
std::string getFilename() const;
|
||||
void setLabel();
|
||||
std::string getStateIcon() const;
|
||||
std::string getOptionIcon(std::string optionName, bool activated) const;
|
||||
std::string getOptionIcon(const std::string& optionName, bool activated) const;
|
||||
std::string getArtistStr(bool truncated) const;
|
||||
std::string getAlbumArtistStr(bool truncated) const;
|
||||
std::string getAlbumStr(bool truncated) const;
|
||||
std::string getTitleStr(bool truncated) const;
|
||||
|
||||
// GUI-side methods
|
||||
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(); }
|
||||
|
||||
|
||||
@@ -78,6 +78,7 @@ class Mpris : public ALabel {
|
||||
|
||||
PlayerctlPlayerManager* manager;
|
||||
PlayerctlPlayer* player;
|
||||
PlayerctlPlayer* last_active_player_ = nullptr;
|
||||
std::string lastStatus;
|
||||
std::string lastPlayer;
|
||||
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -17,7 +17,7 @@ class EventHandler {
|
||||
|
||||
class IPC {
|
||||
public:
|
||||
IPC() { startIPC(); }
|
||||
IPC();
|
||||
|
||||
void registerForIPC(const std::string& ev, EventHandler* ev_handler);
|
||||
void unregisterForIPC(EventHandler* handler);
|
||||
|
||||
@@ -9,9 +9,18 @@ namespace waybar::modules {
|
||||
|
||||
struct Profile {
|
||||
std::string name;
|
||||
// Legacy driver field, kept for backward compatibility with the
|
||||
// `{driver}` format placeholder and with older power-profiles-daemon
|
||||
// versions that only expose a single `Driver` DBus property.
|
||||
std::string driver;
|
||||
std::string cpuDriver;
|
||||
std::string platformDriver;
|
||||
|
||||
Profile(std::string n, std::string d) : name(std::move(n)), driver(std::move(d)) {}
|
||||
Profile(std::string n, std::string d, std::string cd, std::string pd)
|
||||
: name(std::move(n)),
|
||||
driver(std::move(d)),
|
||||
cpuDriver(std::move(cd)),
|
||||
platformDriver(std::move(pd)) {}
|
||||
};
|
||||
|
||||
class PowerProfilesDaemon : public ALabel {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -6,11 +6,6 @@
|
||||
#include "util/audio_backend.hpp"
|
||||
namespace waybar::modules {
|
||||
|
||||
enum class PulseaudioSliderTarget {
|
||||
Sink,
|
||||
Source,
|
||||
};
|
||||
|
||||
class PulseaudioSlider : public ASlider {
|
||||
public:
|
||||
PulseaudioSlider(const std::string&, const Json::Value&);
|
||||
@@ -21,7 +16,15 @@ class PulseaudioSlider : public ASlider {
|
||||
|
||||
private:
|
||||
std::shared_ptr<util::AudioBackend> backend = nullptr;
|
||||
PulseaudioSliderTarget target = PulseaudioSliderTarget::Sink;
|
||||
util::PulseaudioTarget target = util::PulseaudioTarget::Sink;
|
||||
|
||||
bool zero_on_mute = true;
|
||||
bool unmute_on_volume_change = true;
|
||||
// zero_on_mute and unmute_on_volume_change default to true
|
||||
// in order to maintain previous behaviour when using a
|
||||
// config in which these values are undefined
|
||||
|
||||
bool previously_muted = false;
|
||||
};
|
||||
|
||||
} // namespace waybar::modules
|
||||
@@ -14,10 +14,9 @@ namespace waybar::modules::SNI {
|
||||
|
||||
class Host {
|
||||
public:
|
||||
Host(const std::size_t id, const Json::Value&, const Bar&,
|
||||
const std::vector<std::string>&,
|
||||
Host(const std::size_t id, const Json::Value&, const Bar&, const std::vector<std::string>&,
|
||||
const std::function<void(std::unique_ptr<Item>&)>&,
|
||||
const std::function<void(std::unique_ptr<Item>&)>&);
|
||||
const std::function<void(std::unique_ptr<Item>&)>&, const std::function<void()>&);
|
||||
~Host();
|
||||
|
||||
void checkIgnoreList(const std::vector<std::string>& ignore_list,
|
||||
@@ -32,9 +31,13 @@ class Host {
|
||||
static void registerHost(GObject*, GAsyncResult*, gpointer);
|
||||
static void itemRegistered(SnWatcher*, const gchar*, gpointer);
|
||||
static void itemUnregistered(SnWatcher*, const gchar*, gpointer);
|
||||
void itemReady(Item&);
|
||||
void itemInvalidated(Item&);
|
||||
void removeItem(std::vector<std::unique_ptr<Item>>::iterator);
|
||||
void clearItems();
|
||||
|
||||
std::tuple<std::string, std::string> getBusNameAndObjectPath(const std::string);
|
||||
void addRegisteredItem(std::string service);
|
||||
void addRegisteredItem(const std::string& service);
|
||||
|
||||
std::vector<std::unique_ptr<Item>> items_;
|
||||
const std::string bus_name_;
|
||||
@@ -43,11 +46,14 @@ 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::vector<std::string> ignore_list_;
|
||||
const std::function<void(std::unique_ptr<Item>&)> on_add_;
|
||||
const std::function<void(std::unique_ptr<Item>&)> on_remove_;
|
||||
const std::function<void()> on_update_;
|
||||
};
|
||||
|
||||
} // namespace waybar::modules::SNI
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <libdbusmenu-gtk/dbusmenu-gtk.h>
|
||||
#include <sigc++/trackable.h>
|
||||
|
||||
#include <functional>
|
||||
#include <set>
|
||||
#include <string_view>
|
||||
|
||||
@@ -25,9 +26,13 @@ struct ToolTip {
|
||||
|
||||
class Item : public sigc::trackable {
|
||||
public:
|
||||
Item(const std::string&, const std::string&, const Json::Value&, const Bar&);
|
||||
Item(const std::string&, const std::string&, const Json::Value&, const Bar&,
|
||||
const std::function<void(Item&)>&, const std::function<void(Item&)>&,
|
||||
const std::function<void()>&);
|
||||
~Item();
|
||||
|
||||
bool isReady() const;
|
||||
|
||||
std::string bus_name;
|
||||
std::string object_path;
|
||||
|
||||
@@ -41,9 +46,12 @@ 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;
|
||||
std::string attention_icon_name;
|
||||
Glib::RefPtr<Gdk::Pixbuf> attention_icon_pixmap;
|
||||
std::string attention_movie_name;
|
||||
std::string icon_theme_path;
|
||||
std::string menu;
|
||||
@@ -62,6 +70,8 @@ class Item : public sigc::trackable {
|
||||
void proxyReady(Glib::RefPtr<Gio::AsyncResult>& result);
|
||||
void setProperty(const Glib::ustring& name, Glib::VariantBase& value);
|
||||
void setStatus(const Glib::ustring& value);
|
||||
void setReady();
|
||||
void invalidate();
|
||||
void setCustomIcon(const std::string& id);
|
||||
void getUpdatedProperties();
|
||||
void processUpdatedProperties(Glib::RefPtr<Gio::AsyncResult>& result);
|
||||
@@ -69,8 +79,13 @@ class Item : public sigc::trackable {
|
||||
const Glib::VariantContainerBase& arguments);
|
||||
|
||||
void updateImage();
|
||||
Glib::RefPtr<Gdk::Pixbuf> extractPixBuf(GVariant* variant);
|
||||
static Glib::RefPtr<Gdk::Pixbuf> extractPixBuf(GVariant* variant);
|
||||
Glib::RefPtr<Gdk::Pixbuf> getIconPixbuf();
|
||||
Glib::RefPtr<Gdk::Pixbuf> getAttentionIconPixbuf();
|
||||
Glib::RefPtr<Gdk::Pixbuf> getOverlayIconPixbuf();
|
||||
Glib::RefPtr<Gdk::Pixbuf> loadIconFromNameOrFile(const std::string& name, bool log_failure);
|
||||
static Glib::RefPtr<Gdk::Pixbuf> overlayPixbufs(const Glib::RefPtr<Gdk::Pixbuf>&,
|
||||
const Glib::RefPtr<Gdk::Pixbuf>&);
|
||||
Glib::RefPtr<Gdk::Pixbuf> getIconByName(const std::string& name, int size);
|
||||
double getScaledIconSize();
|
||||
static void onMenuDestroyed(Item* self, GObject* old_menu_pointer);
|
||||
@@ -86,8 +101,13 @@ class Item : public sigc::trackable {
|
||||
gdouble distance_scrolled_y_ = 0;
|
||||
// visibility of items with Status == Passive
|
||||
bool show_passive_ = false;
|
||||
bool ready_ = false;
|
||||
Glib::ustring status_ = "active";
|
||||
|
||||
const Bar& bar_;
|
||||
const std::function<void(Item&)> on_ready_;
|
||||
const std::function<void(Item&)> on_invalidate_;
|
||||
const std::function<void()> on_updated_;
|
||||
|
||||
Glib::RefPtr<Gio::DBus::Proxy> proxy_;
|
||||
Glib::RefPtr<Gio::Cancellable> cancellable_;
|
||||
|
||||
@@ -21,9 +21,9 @@ class Tray : public AModule {
|
||||
void onRemove(std::unique_ptr<Item>& item);
|
||||
void checkIgnoreList(std::unique_ptr<Item>* item);
|
||||
std::vector<std::string> parseIgnoreList(const Json::Value& config);
|
||||
void queueUpdate();
|
||||
|
||||
static inline std::size_t nb_hosts_ = 0;
|
||||
bool show_passive_ = false;
|
||||
Gtk::Box box_;
|
||||
SNI::Watcher::singleton watcher_;
|
||||
std::vector<std::string> ignore_list_;
|
||||
|
||||
@@ -37,7 +37,7 @@ class BarIpcClient {
|
||||
void onModeUpdate(bool visible_by_modifier);
|
||||
void onUrgencyUpdate(bool visible_by_urgency);
|
||||
void update();
|
||||
bool isModuleEnabled(std::string name);
|
||||
bool isModuleEnabled(const std::string& name);
|
||||
|
||||
Bar& bar_;
|
||||
util::JsonParser parser_;
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
#include <string>
|
||||
|
||||
#include "ipc.hpp"
|
||||
#include "util/SafeSignal.hpp"
|
||||
#include "util/scoped_fd.hpp"
|
||||
#include "util/sleeper_thread.hpp"
|
||||
|
||||
namespace waybar::modules::sway {
|
||||
@@ -27,8 +29,8 @@ class Ipc {
|
||||
std::string payload;
|
||||
};
|
||||
|
||||
sigc::signal<void, const struct ipc_response&> signal_event;
|
||||
sigc::signal<void, const struct ipc_response&> signal_cmd;
|
||||
::waybar::SafeSignal<const struct ipc_response&> signal_event;
|
||||
::waybar::SafeSignal<const struct ipc_response&> signal_cmd;
|
||||
|
||||
void sendCmd(uint32_t type, const std::string& payload = "");
|
||||
void subscribe(const std::string& payload);
|
||||
@@ -44,8 +46,8 @@ class Ipc {
|
||||
struct ipc_response send(int fd, uint32_t type, const std::string& payload = "");
|
||||
struct ipc_response recv(int fd);
|
||||
|
||||
int fd_;
|
||||
int fd_event_;
|
||||
util::ScopedFd fd_;
|
||||
util::ScopedFd fd_event_;
|
||||
std::mutex mutex_;
|
||||
util::SleeperThread thread_;
|
||||
};
|
||||
|
||||
@@ -47,7 +47,7 @@ class Language : public ALabel, public sigc::trackable {
|
||||
void onEvent(const struct Ipc::ipc_response&);
|
||||
void onCmd(const struct Ipc::ipc_response&);
|
||||
|
||||
auto set_current_layout(std::string current_layout) -> void;
|
||||
auto set_current_layout(const std::string& current_layout) -> void;
|
||||
auto init_layouts_map(const std::vector<std::string>& used_layouts) -> void;
|
||||
|
||||
const static std::string XKB_LAYOUT_NAMES_KEY;
|
||||
|
||||
@@ -24,10 +24,11 @@ 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 {} "{}")";
|
||||
|
||||
static int convertWorkspaceNameToNum(std::string name);
|
||||
static int convertWorkspaceNameToNum(const std::string& name);
|
||||
static int windowRewritePriorityFunction(std::string const& window_rule);
|
||||
|
||||
void onCmd(const struct Ipc::ipc_response&);
|
||||
@@ -40,7 +41,7 @@ class Workspaces : public AModule, public sigc::trackable {
|
||||
std::string getIcon(const std::string&, const Json::Value&);
|
||||
std::string getCycleWorkspace(std::vector<Json::Value>::iterator, bool prev) const;
|
||||
uint16_t getWorkspaceIndex(const std::string& name) const;
|
||||
static std::string trimWorkspaceName(std::string);
|
||||
static std::string trimWorkspaceName(const std::string&);
|
||||
bool handleScroll(GdkEventScroll* /*unused*/) override;
|
||||
|
||||
const Bar& bar_;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <giomm/dbusproxy.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "ALabel.hpp"
|
||||
|
||||
@@ -11,23 +12,42 @@ namespace waybar::modules {
|
||||
class SystemdFailedUnits : public ALabel {
|
||||
public:
|
||||
SystemdFailedUnits(const std::string&, const Json::Value&);
|
||||
virtual ~SystemdFailedUnits();
|
||||
virtual ~SystemdFailedUnits() = default;
|
||||
auto update() -> void override;
|
||||
|
||||
private:
|
||||
bool hide_on_ok;
|
||||
std::string format_ok;
|
||||
struct FailedUnit {
|
||||
std::string name;
|
||||
std::string description;
|
||||
std::string load_state;
|
||||
std::string active_state;
|
||||
std::string sub_state;
|
||||
std::string scope;
|
||||
};
|
||||
|
||||
bool update_pending;
|
||||
std::string system_state, user_state, overall_state;
|
||||
uint32_t nr_failed_system, nr_failed_user, nr_failed;
|
||||
std::string last_status;
|
||||
Glib::RefPtr<Gio::DBus::Proxy> system_proxy, user_proxy;
|
||||
bool hide_on_ok_;
|
||||
std::string format_ok_;
|
||||
std::string tooltip_format_;
|
||||
std::string tooltip_format_ok_;
|
||||
std::string tooltip_unit_format_;
|
||||
|
||||
bool update_pending_;
|
||||
std::string system_state_, user_state_, overall_state_;
|
||||
uint32_t nr_failed_system_, nr_failed_user_, nr_failed_;
|
||||
std::string last_status_;
|
||||
Glib::RefPtr<Gio::DBus::Proxy> system_props_proxy_, user_props_proxy_;
|
||||
Glib::RefPtr<Gio::DBus::Proxy> system_manager_proxy_, user_manager_proxy_;
|
||||
std::vector<FailedUnit> failed_units_;
|
||||
|
||||
void notify_cb(const Glib::ustring& sender_name, const Glib::ustring& signal_name,
|
||||
const Glib::VariantContainerBase& arguments);
|
||||
void RequestFailedUnits();
|
||||
void RequestFailedUnitsList();
|
||||
void RequestSystemState();
|
||||
std::vector<FailedUnit> LoadFailedUnitsList(const char* kind,
|
||||
Glib::RefPtr<Gio::DBus::Proxy>& proxy,
|
||||
const std::string& scope);
|
||||
std::string BuildTooltipFailedList() const;
|
||||
void updateData();
|
||||
};
|
||||
|
||||
|
||||
@@ -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,6 +12,8 @@
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
#include "util/scoped_fd.hpp"
|
||||
|
||||
namespace waybar::modules::wayfire {
|
||||
|
||||
using EventHandler = std::function<void(const std::string& event)>;
|
||||
@@ -71,25 +73,9 @@ struct State {
|
||||
auto update_view(const Json::Value& view) -> void;
|
||||
};
|
||||
|
||||
struct Sock {
|
||||
int fd;
|
||||
using Sock = util::ScopedFd;
|
||||
|
||||
Sock(int fd) : fd{fd} {}
|
||||
~Sock() { close(fd); }
|
||||
Sock(const Sock&) = delete;
|
||||
auto operator=(const Sock&) = delete;
|
||||
Sock(Sock&& rhs) noexcept {
|
||||
fd = rhs.fd;
|
||||
rhs.fd = -1;
|
||||
}
|
||||
auto& operator=(Sock&& rhs) noexcept {
|
||||
fd = rhs.fd;
|
||||
rhs.fd = -1;
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
class IPC {
|
||||
class IPC : public std::enable_shared_from_this<IPC> {
|
||||
static std::weak_ptr<IPC> instance;
|
||||
Json::CharReaderBuilder reader_builder;
|
||||
Json::StreamWriterBuilder writer_builder;
|
||||
@@ -98,7 +84,7 @@ class IPC {
|
||||
State state;
|
||||
std::mutex state_mutex;
|
||||
|
||||
IPC() { start(); }
|
||||
IPC() = default;
|
||||
|
||||
static auto connect() -> Sock;
|
||||
auto receive(Sock& sock) -> Json::Value;
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "AModule.hpp"
|
||||
#include "bar.hpp"
|
||||
#include "client.hpp"
|
||||
#include "ext-workspace-v1-client-protocol.h"
|
||||
#include "giomm/desktopappinfo.h"
|
||||
#include "util/icon_loader.hpp"
|
||||
#include "util/json.hpp"
|
||||
@@ -80,6 +81,7 @@ class Task {
|
||||
std::string title_;
|
||||
std::string app_id_;
|
||||
uint32_t state_ = 0;
|
||||
struct ext_workspace_handle_v1* workspace_ = nullptr;
|
||||
|
||||
int32_t drag_start_x;
|
||||
int32_t drag_start_y;
|
||||
@@ -102,6 +104,9 @@ class Task {
|
||||
bool minimized() const { return state_ & MINIMIZED; }
|
||||
bool active() const { return state_ & ACTIVE; }
|
||||
bool fullscreen() const { return state_ & FULLSCREEN; }
|
||||
bool visible() const { return button_visible_; }
|
||||
struct ext_workspace_handle_v1* workspace() const { return workspace_; }
|
||||
void set_workspace(struct ext_workspace_handle_v1* workspace) { workspace_ = workspace; }
|
||||
|
||||
public:
|
||||
/* Callbacks for the wlr protocol */
|
||||
@@ -142,6 +147,12 @@ using TaskPtr = std::unique_ptr<Task>;
|
||||
|
||||
class Taskbar : public waybar::AModule {
|
||||
public:
|
||||
struct WorkspaceState {
|
||||
Taskbar* taskbar;
|
||||
struct ext_workspace_handle_v1* handle;
|
||||
uint32_t state = 0;
|
||||
};
|
||||
|
||||
Taskbar(const std::string&, const waybar::Bar&, const Json::Value&);
|
||||
~Taskbar();
|
||||
void update();
|
||||
@@ -156,22 +167,35 @@ class Taskbar : public waybar::AModule {
|
||||
std::map<std::string, std::string> app_ids_replace_map_;
|
||||
|
||||
struct zwlr_foreign_toplevel_manager_v1* manager_;
|
||||
struct ext_workspace_manager_v1* workspace_manager_;
|
||||
struct wl_seat* seat_;
|
||||
std::vector<struct ext_workspace_group_handle_v1*> workspace_groups_;
|
||||
std::vector<std::unique_ptr<WorkspaceState>> workspaces_;
|
||||
struct ext_workspace_handle_v1* current_workspace_ = nullptr;
|
||||
|
||||
public:
|
||||
/* Callbacks for global registration */
|
||||
void register_manager(struct wl_registry*, uint32_t name, uint32_t version);
|
||||
void register_workspace_manager(struct wl_registry*, uint32_t name, uint32_t version);
|
||||
void register_seat(struct wl_registry*, uint32_t name, uint32_t version);
|
||||
|
||||
/* Callbacks for the wlr protocol */
|
||||
void handle_toplevel_create(struct zwlr_foreign_toplevel_handle_v1*);
|
||||
void handle_finished();
|
||||
void handle_workspace_group_create(struct ext_workspace_group_handle_v1*);
|
||||
void handle_workspace_group_removed(struct ext_workspace_group_handle_v1*);
|
||||
void handle_workspace_create(struct ext_workspace_handle_v1*);
|
||||
void handle_workspace_done();
|
||||
void handle_workspace_finished();
|
||||
void handle_workspace_removed(struct ext_workspace_handle_v1*);
|
||||
|
||||
public:
|
||||
void add_button(Gtk::Button&);
|
||||
void move_button(Gtk::Button&, int);
|
||||
void remove_button(Gtk::Button&);
|
||||
void remove_task(uint32_t);
|
||||
void assign_current_workspace(Task&);
|
||||
void update_bar_css_classes();
|
||||
|
||||
bool show_output(struct wl_output*) const;
|
||||
bool all_outputs() const;
|
||||
@@ -179,6 +203,9 @@ class Taskbar : public waybar::AModule {
|
||||
const IconLoader& icon_loader() const;
|
||||
const std::unordered_set<std::string>& ignore_list() const;
|
||||
const std::map<std::string, std::string>& app_ids_replace_map() const;
|
||||
|
||||
private:
|
||||
void set_bar_css_class(const std::string&, bool);
|
||||
};
|
||||
|
||||
} /* namespace waybar::modules::wlr */
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <glibmm/dispatcher.h>
|
||||
#include <sigc++/signal.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
@@ -11,11 +12,6 @@
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#ifdef __OpenBSD__
|
||||
#define SIGRTMIN SIGUSR1 - 1
|
||||
#define SIGRTMAX SIGUSR1 + 1
|
||||
#endif
|
||||
|
||||
namespace waybar {
|
||||
|
||||
/**
|
||||
@@ -27,6 +23,12 @@ struct SafeSignal : sigc::signal<void(std::decay_t<Args>...)> {
|
||||
public:
|
||||
SafeSignal() { dp_.connect(sigc::mem_fun(*this, &SafeSignal::handle_event)); }
|
||||
|
||||
void set_max_queued_events(std::size_t max_queued_events) {
|
||||
std::unique_lock lock(mutex_);
|
||||
max_queued_events_ = max_queued_events;
|
||||
trim_queue_locked();
|
||||
}
|
||||
|
||||
template <typename... EmitArgs>
|
||||
void emit(EmitArgs&&... args) {
|
||||
if (main_tid_ == std::this_thread::get_id()) {
|
||||
@@ -41,6 +43,9 @@ struct SafeSignal : sigc::signal<void(std::decay_t<Args>...)> {
|
||||
} else {
|
||||
{
|
||||
std::unique_lock lock(mutex_);
|
||||
if (max_queued_events_ != 0 && queue_.size() >= max_queued_events_) {
|
||||
queue_.pop();
|
||||
}
|
||||
queue_.emplace(std::forward<EmitArgs>(args)...);
|
||||
}
|
||||
dp_.emit();
|
||||
@@ -60,6 +65,15 @@ struct SafeSignal : sigc::signal<void(std::decay_t<Args>...)> {
|
||||
using signal_t::emit_reverse;
|
||||
using signal_t::make_slot;
|
||||
|
||||
void trim_queue_locked() {
|
||||
if (max_queued_events_ == 0) {
|
||||
return;
|
||||
}
|
||||
while (queue_.size() > max_queued_events_) {
|
||||
queue_.pop();
|
||||
}
|
||||
}
|
||||
|
||||
void handle_event() {
|
||||
for (std::unique_lock lock(mutex_); !queue_.empty(); lock.lock()) {
|
||||
auto args = queue_.front();
|
||||
@@ -72,6 +86,7 @@ struct SafeSignal : sigc::signal<void(std::decay_t<Args>...)> {
|
||||
Glib::Dispatcher dp_;
|
||||
std::mutex mutex_;
|
||||
std::queue<arg_tuple_t> queue_;
|
||||
std::size_t max_queued_events_ = 4096;
|
||||
const std::thread::id main_tid_ = std::this_thread::get_id();
|
||||
// cache functor for signal emission to avoid recreating it on each event
|
||||
const slot_t cached_fn_ = make_slot();
|
||||
|
||||
@@ -14,6 +14,11 @@
|
||||
|
||||
namespace waybar::util {
|
||||
|
||||
enum class PulseaudioTarget {
|
||||
Sink,
|
||||
Source,
|
||||
};
|
||||
|
||||
class AudioBackend {
|
||||
private:
|
||||
static void subscribeCb(pa_context*, pa_subscription_event_type_t, uint32_t, void*);
|
||||
@@ -92,6 +97,10 @@ class AudioBackend {
|
||||
void toggleSourceMute();
|
||||
void toggleSourceMute(bool);
|
||||
|
||||
uint16_t getVolume(PulseaudioTarget) const;
|
||||
bool getMuted(PulseaudioTarget) const;
|
||||
void unmute(PulseaudioTarget);
|
||||
|
||||
bool isBluetooth();
|
||||
};
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@ extern std::list<pid_t> reap;
|
||||
|
||||
namespace waybar::util::command {
|
||||
|
||||
constexpr int kExecFailureExitCode = 127;
|
||||
|
||||
struct res {
|
||||
int exit_code;
|
||||
std::string out;
|
||||
@@ -59,6 +61,7 @@ inline int close(FILE* fp, pid_t pid) {
|
||||
spdlog::debug("Cmd continued");
|
||||
} else if (ret == -1) {
|
||||
spdlog::debug("waitpid failed: {}", strerror(errno));
|
||||
break;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
@@ -113,7 +116,9 @@ inline FILE* open(const std::string& cmd, int& pid, const std::string& output_na
|
||||
setenv("WAYBAR_OUTPUT_NAME", output_name.c_str(), 1);
|
||||
}
|
||||
execlp("/bin/sh", "sh", "-c", cmd.c_str(), (char*)0);
|
||||
exit(0);
|
||||
const int saved_errno = errno;
|
||||
spdlog::error("execlp(/bin/sh) failed in open: {}", strerror(saved_errno));
|
||||
_exit(kExecFailureExitCode);
|
||||
} else {
|
||||
::close(fd[1]);
|
||||
}
|
||||
@@ -161,7 +166,9 @@ inline int32_t forkExec(const std::string& cmd, const std::string& output_name)
|
||||
setenv("WAYBAR_OUTPUT_NAME", output_name.c_str(), 1);
|
||||
}
|
||||
execl("/bin/sh", "sh", "-c", cmd.c_str(), (char*)0);
|
||||
exit(0);
|
||||
const int saved_errno = errno;
|
||||
spdlog::error("execl(/bin/sh) failed in forkExec: {}", strerror(saved_errno));
|
||||
_exit(kExecFailureExitCode);
|
||||
} else {
|
||||
reap_mtx.lock();
|
||||
reap.push_back(pid);
|
||||
@@ -172,8 +179,6 @@ inline int32_t forkExec(const std::string& cmd, const std::string& output_name)
|
||||
return pid;
|
||||
}
|
||||
|
||||
inline int32_t forkExec(const std::string& cmd) {
|
||||
return forkExec(cmd, "");
|
||||
}
|
||||
inline int32_t forkExec(const std::string& cmd) { return forkExec(cmd, ""); }
|
||||
|
||||
} // namespace waybar::util::command
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
namespace waybar::util {
|
||||
|
||||
class ScopedFd {
|
||||
public:
|
||||
explicit ScopedFd(int fd = -1) : fd_(fd) {}
|
||||
~ScopedFd() {
|
||||
if (fd_ != -1) {
|
||||
close(fd_);
|
||||
}
|
||||
}
|
||||
|
||||
// ScopedFd is non-copyable
|
||||
ScopedFd(const ScopedFd&) = delete;
|
||||
ScopedFd& operator=(const ScopedFd&) = delete;
|
||||
|
||||
// ScopedFd is moveable
|
||||
ScopedFd(ScopedFd&& other) noexcept : fd_(other.fd_) { other.fd_ = -1; }
|
||||
ScopedFd& operator=(ScopedFd&& other) noexcept {
|
||||
if (this != &other) {
|
||||
if (fd_ != -1) {
|
||||
close(fd_);
|
||||
}
|
||||
fd_ = other.fd_;
|
||||
other.fd_ = -1;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
int get() const { return fd_; }
|
||||
|
||||
operator int() const { return fd_; }
|
||||
|
||||
void reset(int fd = -1) {
|
||||
if (fd_ != -1) {
|
||||
close(fd_);
|
||||
}
|
||||
fd_ = fd;
|
||||
}
|
||||
|
||||
int release() {
|
||||
int fd = fd_;
|
||||
fd_ = -1;
|
||||
return fd;
|
||||
}
|
||||
|
||||
private:
|
||||
int fd_;
|
||||
};
|
||||
|
||||
} // namespace waybar::util
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <ctime>
|
||||
@@ -31,8 +32,8 @@ class SleeperThread {
|
||||
|
||||
SleeperThread(std::function<void()> func)
|
||||
: thread_{[this, func] {
|
||||
while (do_run_) {
|
||||
signal_ = false;
|
||||
while (do_run_.load(std::memory_order_relaxed)) {
|
||||
signal_.store(false, std::memory_order_relaxed);
|
||||
func();
|
||||
}
|
||||
}} {
|
||||
@@ -42,9 +43,18 @@ class SleeperThread {
|
||||
}
|
||||
|
||||
SleeperThread& operator=(std::function<void()> func) {
|
||||
if (thread_.joinable()) {
|
||||
stop();
|
||||
thread_.join();
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lck(mutex_);
|
||||
do_run_.store(true, std::memory_order_relaxed);
|
||||
signal_.store(false, std::memory_order_relaxed);
|
||||
}
|
||||
thread_ = std::thread([this, func] {
|
||||
while (do_run_) {
|
||||
signal_ = false;
|
||||
while (do_run_.load(std::memory_order_relaxed)) {
|
||||
signal_.store(false, std::memory_order_relaxed);
|
||||
func();
|
||||
}
|
||||
});
|
||||
@@ -56,24 +66,34 @@ class SleeperThread {
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool isRunning() const { return do_run_; }
|
||||
bool isRunning() const { return do_run_.load(std::memory_order_relaxed); }
|
||||
|
||||
auto sleep() {
|
||||
std::unique_lock lk(mutex_);
|
||||
CancellationGuard cancel_lock;
|
||||
return condvar_.wait(lk, [this] { return signal_ || !do_run_; });
|
||||
return condvar_.wait(lk, [this] {
|
||||
return signal_.load(std::memory_order_relaxed) || !do_run_.load(std::memory_order_relaxed);
|
||||
});
|
||||
}
|
||||
|
||||
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();
|
||||
if (now < max_time_point - dur) {
|
||||
wait_end = now + dur;
|
||||
}
|
||||
return condvar_.wait_until(lk, wait_end, [this] { return signal_ || !do_run_; });
|
||||
return condvar_.wait_until(lk, wait_end, [this] {
|
||||
return signal_.load(std::memory_order_relaxed) || !do_run_.load(std::memory_order_relaxed);
|
||||
});
|
||||
}
|
||||
|
||||
auto sleep_until(
|
||||
@@ -81,22 +101,30 @@ class SleeperThread {
|
||||
time_point) {
|
||||
std::unique_lock lk(mutex_);
|
||||
CancellationGuard cancel_lock;
|
||||
return condvar_.wait_until(lk, time_point, [this] { return signal_ || !do_run_; });
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
void wake_up() {
|
||||
{
|
||||
std::lock_guard<std::mutex> lck(mutex_);
|
||||
signal_ = true;
|
||||
signal_.store(true, std::memory_order_relaxed);
|
||||
}
|
||||
condvar_.notify_all();
|
||||
}
|
||||
|
||||
auto stop() {
|
||||
void stop() {
|
||||
{
|
||||
std::lock_guard<std::mutex> lck(mutex_);
|
||||
signal_ = true;
|
||||
do_run_ = false;
|
||||
signal_.store(true, std::memory_order_relaxed);
|
||||
do_run_.store(false, std::memory_order_relaxed);
|
||||
}
|
||||
condvar_.notify_all();
|
||||
auto handle = thread_.native_handle();
|
||||
@@ -106,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();
|
||||
@@ -118,9 +157,10 @@ class SleeperThread {
|
||||
std::thread thread_;
|
||||
std::condition_variable condvar_;
|
||||
std::mutex mutex_;
|
||||
bool do_run_ = true;
|
||||
bool signal_ = false;
|
||||
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 ++
|
||||
@@ -182,6 +187,7 @@ Every entry in the *events* object consists of a *<event-name>* (typeof: *string
|
||||
|
||||
- *on-<status>-<state>*
|
||||
- *on-<status>-<capacity>*
|
||||
- *on-<status>*
|
||||
|
||||
Where:
|
||||
|
||||
@@ -203,7 +209,9 @@ Where:
|
||||
"events": {
|
||||
"on-discharging-warning": "notify-send -u normal 'Low Battery'",
|
||||
"on-discharging-critical": "notify-send -u critical 'Very Low Battery'",
|
||||
"on-charging-100": "notify-send -u normal 'Battery Full!'"
|
||||
"on-charging-100": "notify-send -u normal 'Battery Full!'",
|
||||
"on-discharging": "notify-send -u normal 'Power Switch' Discharging",
|
||||
"on-charging": "notify-send -u normal 'Power Switch' Charging'"
|
||||
},
|
||||
"format": "{capacity}% {icon}",
|
||||
"format-icons": ["", "", "", "", ""],
|
||||
|
||||
+14
-2
@@ -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",
|
||||
@@ -259,7 +259,19 @@ View all valid format options in *strftime(3)* or have a look https://en.cpprefe
|
||||
"tooltip-format": "{tz_list}"
|
||||
}
|
||||
```
|
||||
5. Simple calendar tooltip
|
||||
|
||||
```
|
||||
"clock": {
|
||||
"format": "{:%H:%M}",
|
||||
"tooltip-format": "<tt>{calendar}</tt>",
|
||||
"calendar": {
|
||||
"format": {
|
||||
"today": "<span color='#ffcc66'><b>{}</b></span>"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
# STYLE
|
||||
|
||||
- *#clock*
|
||||
@@ -287,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",
|
||||
|
||||
@@ -92,6 +92,12 @@ The *cpu* module displays the current CPU utilization.
|
||||
|
||||
*{load}*: Current CPU load.
|
||||
|
||||
*{load1}*: CPU load average over the last minute.
|
||||
|
||||
*{load5}*: CPU load average over the last 5 minutes.
|
||||
|
||||
*{load15}*: CPU load average over the last 15 minutes.
|
||||
|
||||
*{usage}*: Current overall CPU usage.
|
||||
|
||||
*{usage*{n}*}*: Current CPU core n usage. Cores are numbered from zero, so first core will be {usage0} and 4th will be {usage3}.
|
||||
|
||||
@@ -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*
|
||||
|
||||
@@ -130,6 +130,19 @@ This setting is ignored if *workspace-taskbar.enable* is set to true.
|
||||
Otherwise, the workspace will open on the monitor where it was previously assigned.
|
||||
Analog to using `focusworkspaceoncurrentmonitor` dispatcher instead of `workspace` in Hyprland.
|
||||
|
||||
*enable-bar-scroll*: ++
|
||||
typeof: bool ++
|
||||
default: false ++
|
||||
If set to false, you can't scroll to cycle throughout workspaces from the entire bar. If set to true this behaviour is enabled.
|
||||
|
||||
*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: [] ++
|
||||
|
||||
@@ -26,7 +26,8 @@ The *image* module displays an image from a path.
|
||||
*interval*: ++
|
||||
typeof: integer or float ++
|
||||
The interval (in seconds) to re-render the image. ++
|
||||
Minimum value is 0.001 (1ms). Values smaller than 1ms will be set to 1ms. ++
|
||||
If set to a positive value, the minimum is 0.001 (1ms). Values smaller than 1ms will be set to 1ms. ++
|
||||
Zero or negative values are treated as "once". ++
|
||||
This is useful if the contents of *path* changes. ++
|
||||
If no *interval* is defined, the image will only be rendered once.
|
||||
|
||||
|
||||
@@ -26,7 +26,10 @@ You must be a member of the input group to use this module.
|
||||
*format-icons*: ++
|
||||
typeof: object ++
|
||||
default: {"locked": "locked", "unlocked": "unlocked"} ++
|
||||
Based on the keyboard state, the corresponding icon gets selected. The same set of icons is used for number, caps, and scroll lock, but the icon is selected from the set independently for each. See *icons*.
|
||||
Based on the keyboard state, the corresponding icon gets selected. Supports two syntaxes:
|
||||
- Common format-icons: "locked" and "unlocked" keys apply to all lock types.
|
||||
- Per-lock-type format-icons: per-lock-type objects with "numlock", "capslock", "scrolllock" keys, each containing "locked" and "unlocked" icons.
|
||||
See *icons*.
|
||||
|
||||
*numlock*: ++
|
||||
typeof: bool ++
|
||||
@@ -68,15 +71,21 @@ You must be a member of the input group to use this module.
|
||||
|
||||
The following *format-icons* can be set.
|
||||
|
||||
## Common format-icons for all lock types:
|
||||
- *locked*: Will be shown when the keyboard state is locked. Default "locked".
|
||||
- *unlocked*: Will be shown when the keyboard state is not locked. Default "unlocked"
|
||||
- *unlocked*: Will be shown when the keyboard state is not locked. Default "unlocked".
|
||||
|
||||
## Per-lock-type format-icons:
|
||||
- *numlock*, *capslock*, *scrolllock*: Object containing "locked" and "unlocked" keys for lock-type-specific icons. Defaults to {"locked": "locked", "unlocked": "unlocked"} for each lock type.
|
||||
|
||||
# EXAMPLE:
|
||||
|
||||
## Common format-icons for all lock types:
|
||||
```
|
||||
"keyboard-state": {
|
||||
"numlock": true,
|
||||
"capslock": true,
|
||||
"scrolllock": true,
|
||||
"format": "{name} {icon}",
|
||||
"format-icons": {
|
||||
"locked": "",
|
||||
@@ -85,6 +94,30 @@ The following *format-icons* can be set.
|
||||
}
|
||||
```
|
||||
|
||||
## Per-lock-type format-icons:
|
||||
```
|
||||
"keyboard-state": {
|
||||
"numlock": true,
|
||||
"capslock": true,
|
||||
"scrolllock": true,
|
||||
"format": "{name} {icon}",
|
||||
"format-icons": {
|
||||
"numlock": {
|
||||
"locked": "1",
|
||||
"unlocked": "0"
|
||||
},
|
||||
"capslock": {
|
||||
"locked": "A",
|
||||
"unlocked": "a"
|
||||
},
|
||||
"scrolllock": {
|
||||
"locked": "S",
|
||||
"unlocked": "s"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
# STYLE
|
||||
|
||||
- *#keyboard-state*
|
||||
|
||||
+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
|
||||
|
||||
|
||||
@@ -25,8 +25,8 @@ $XDG_CONFIG_HOME/waybar/config
|
||||
:[ Message displayed on the bar. {icon} and {profile} are respectively substituted with the icon representing the active profile and its full name.
|
||||
|[ *tooltip-format*
|
||||
:[ string
|
||||
:[ "Power profile: {profile}\\nDriver: {driver}"
|
||||
:[ Messaged displayed in the module tooltip. {icon} and {profile} are respectively substituted with the icon representing the active profile and its full name.
|
||||
:[ "Power profile: {profile}\\nCPU driver: {cpu_driver}\\nPlatform driver: {platform_driver}"
|
||||
:[ Messaged displayed in the module tooltip. {icon} and {profile} are respectively substituted with the icon representing the active profile and its full name. {cpu_driver} and {platform_driver} are substituted with the CPU and platform drivers reported by recent power-profiles-daemon versions. {driver} is kept for backward compatibility: it resolves to the legacy single driver on older daemons and falls back to the CPU driver on recent ones.
|
||||
|[ *tooltip*
|
||||
:[ bool
|
||||
:[ true
|
||||
@@ -51,7 +51,7 @@ Compact display (default config):
|
||||
```
|
||||
"power-profiles-daemon": {
|
||||
"format": "{icon}",
|
||||
"tooltip-format": "Power profile: {profile}\nDriver: {driver}",
|
||||
"tooltip-format": "Power profile: {profile}\nCPU driver: {cpu_driver}\nPlatform driver: {platform_driver}",
|
||||
"tooltip": true,
|
||||
"format-icons": {
|
||||
"default": "",
|
||||
@@ -67,7 +67,7 @@ Display the full profile name:
|
||||
```
|
||||
"power-profiles-daemon": {
|
||||
"format": "{icon} {profile}",
|
||||
"tooltip-format": "Power profile: {profile}\nDriver: {driver}",
|
||||
"tooltip-format": "Power profile: {profile}\nCPU driver: {cpu_driver}\nPlatform driver: {platform_driver}",
|
||||
"tooltip": true,
|
||||
"format-icons": {
|
||||
"default": "",
|
||||
|
||||
@@ -32,6 +32,17 @@ The volume can be controlled by dragging the slider across the bar or clicking o
|
||||
default: false ++
|
||||
Enables this module to consume all left over space dynamically.
|
||||
|
||||
*zero-on-mute*: ++
|
||||
typeof: bool ++
|
||||
default: true ++
|
||||
`true` = The slider will be set to `min` when the source/sink is muted. ++
|
||||
`false` = The slider will continue to show the unmuted volume level when the source/sink is muted.
|
||||
|
||||
*unmute-on-volume-change*: ++
|
||||
typeof: bool ++
|
||||
default: true ++
|
||||
Specifies whether to unmute a muted souce/sink when its volume is changed by the user moving the slider.
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
```
|
||||
@@ -41,7 +52,9 @@ The volume can be controlled by dragging the slider across the bar or clicking o
|
||||
"pulseaudio/slider": {
|
||||
"min": 0,
|
||||
"max": 100,
|
||||
"orientation": "horizontal"
|
||||
"orientation": "horizontal",
|
||||
"zero-on-mute": false,
|
||||
"unmute-on-volume-change": false
|
||||
}
|
||||
```
|
||||
|
||||
@@ -52,6 +65,9 @@ The slider is a component with multiple CSS Nodes, of which the following are ex
|
||||
*#pulseaudio-slider*: ++
|
||||
Controls the style of the box *around* the slider and bar.
|
||||
|
||||
*#pulseaudio-slider.muted*: ++
|
||||
Controls the style when the audio source/sink is muted.
|
||||
|
||||
*#pulseaudio-slider slider*: ++
|
||||
Controls the style of the slider handle.
|
||||
|
||||
@@ -85,4 +101,8 @@ The slider is a component with multiple CSS Nodes, of which the following are ex
|
||||
border-radius: 5px;
|
||||
background: green;
|
||||
}
|
||||
|
||||
#pulseaudio-slider.muted highlight {
|
||||
background-color: orange;
|
||||
}
|
||||
```
|
||||
|
||||
@@ -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": " {}"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -8,13 +8,27 @@ waybar-styles - using stylesheets for waybar
|
||||
|
||||
Waybar uses Cascading Style Sheets (CSS) to configure its appearance.
|
||||
|
||||
It uses the first file found in this search order:
|
||||
When the system appearance is light, Waybar first looks for *style-light.css*.
|
||||
When the system appearance is dark, Waybar first looks for *style-dark.css*.
|
||||
If no appearance-specific stylesheet is found, it falls back to *style.css*.
|
||||
|
||||
- *$XDG_CONFIG_HOME/waybar/style.css*
|
||||
- *~/.config/waybar/style.css*
|
||||
- *~/waybar/style.css*
|
||||
- */etc/xdg/waybar/style.css*
|
||||
- *@sysconfdir@/xdg/waybar/style.css*
|
||||
Waybar uses the first file found in this search order for each stylesheet name:
|
||||
|
||||
- *$XDG_CONFIG_HOME/waybar/<stylesheet>*
|
||||
- *~/.config/waybar/<stylesheet>*
|
||||
- *~/waybar/<stylesheet>*
|
||||
- */etc/xdg/waybar/<stylesheet>*
|
||||
- *@sysconfdir@/xdg/waybar/<stylesheet>*
|
||||
|
||||
For example, a light theme is loaded from the first available file among:
|
||||
|
||||
- *$XDG_CONFIG_HOME/waybar/style-light.css*
|
||||
- *~/.config/waybar/style-light.css*
|
||||
- *~/waybar/style-light.css*
|
||||
- */etc/xdg/waybar/style-light.css*
|
||||
- *@sysconfdir@/xdg/waybar/style-light.css*
|
||||
|
||||
If no file is found there, Waybar repeats the same search using *style.css*.
|
||||
|
||||
# EXAMPLE
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@ Addressed by *sway/mode*
|
||||
|
||||
```
|
||||
"sway/mode": {
|
||||
"format": " {}",
|
||||
"format": " {}",
|
||||
"max-length": 50
|
||||
}
|
||||
```
|
||||
|
||||
@@ -22,6 +22,11 @@ Addressed by *sway/workspaces*
|
||||
default: {value} ++
|
||||
The format, how information should be displayed.
|
||||
|
||||
*format-for-negative-index*: ++
|
||||
typeof: string ++
|
||||
default: *format* ++
|
||||
An alternative format, which will be used for workspaces with no explict or negative index ("num" in sway terms). Requires *format* to be set.
|
||||
|
||||
*format-icons*: ++
|
||||
typeof: array ++
|
||||
Based on the workspace name and state, the corresponding icon gets selected. See *icons*.
|
||||
@@ -187,6 +192,13 @@ n.b.: the list of outputs can be obtained from command line using *swaymsg -t ge
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
"sway/workspaces": {
|
||||
"format": "{index} - {name}",
|
||||
"format-for-negative-index": "{name}"
|
||||
}
|
||||
```
|
||||
|
||||
# Style
|
||||
|
||||
- *#workspaces button*
|
||||
|
||||
@@ -19,7 +19,7 @@ Addressed by *systemd-failed-units*
|
||||
|
||||
*format-ok*: ++
|
||||
typeof: string ++
|
||||
This format is used when there is no failing units.
|
||||
This format is used when there are no failing units.
|
||||
|
||||
*user*: ++
|
||||
typeof: bool ++
|
||||
@@ -34,15 +34,30 @@ Addressed by *systemd-failed-units*
|
||||
*hide-on-ok*: ++
|
||||
typeof: bool ++
|
||||
default: *true* ++
|
||||
Option to hide this module when there is no failing units.
|
||||
Option to hide this module when there are no failed units.
|
||||
|
||||
*tooltip-format*: ++
|
||||
typeof: string ++
|
||||
default: *System: {system_state}\nUser: {user_state}\nFailed units ({nr_failed}):\n{failed_units_list}* ++
|
||||
Tooltip format shown when there are failed units.
|
||||
|
||||
*tooltip-format-ok*: ++
|
||||
typeof: string ++
|
||||
default: *System: {system_state}\nUser: {user_state}* ++
|
||||
Tooltip format used when there are no failed units.
|
||||
|
||||
*tooltip-unit-format*: ++
|
||||
typeof: string ++
|
||||
default: *{name}: {description}* ++
|
||||
Format used to render each failed unit inside the tooltip. Each item is prefixed with a bullet.
|
||||
|
||||
*menu*: ++
|
||||
typeof: string ++
|
||||
Action that popups the menu.
|
||||
Action that pops up the menu.
|
||||
|
||||
*menu-file*: ++
|
||||
typeof: string ++
|
||||
Location of the menu descriptor file. There need to be an element of type
|
||||
Location of the menu descriptor file. There needs to be an element of type
|
||||
GtkMenu with id *menu*
|
||||
|
||||
*menu-actions*: ++
|
||||
@@ -62,11 +77,23 @@ Addressed by *systemd-failed-units*
|
||||
|
||||
*{nr_failed}*: Number of total failed units.
|
||||
|
||||
*{systemd_state}:* State of the systemd system session
|
||||
*{system_state}:* State of the systemd system session.
|
||||
|
||||
*{user_state}:* State of the systemd user session
|
||||
*{user_state}:* State of the systemd user session.
|
||||
|
||||
*{overall_state}:* Overall state of the systemd and user session. ("Ok" or "Degraded")
|
||||
*{overall_state}:* Overall state of the systemd and user session. ("ok" or "degraded")
|
||||
|
||||
*{failed_units_list}:* Bulleted list of failed units using *tooltip-unit-format*. Empty when
|
||||
there are no failed units.
|
||||
|
||||
The *tooltip-unit-format* string supports the following replacements:
|
||||
|
||||
*{name}*: Unit name ++
|
||||
*{description}*: Unit description ++
|
||||
*{load_state}*: Unit load state ++
|
||||
*{active_state}*: Unit active state ++
|
||||
*{sub_state}*: Unit sub state ++
|
||||
*{scope}*: Either *system* or *user* depending on where the unit originated
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
@@ -77,6 +104,8 @@ Addressed by *systemd-failed-units*
|
||||
"format-ok": "✓",
|
||||
"system": true,
|
||||
"user": false,
|
||||
"tooltip-format": "{nr_failed} failed units:\n{failed_units_list}",
|
||||
"tooltip-unit-format": "{scope}: {name} ({active_state})",
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -27,6 +27,12 @@ Addressed by *temperature*
|
||||
The path of the hwmon-directory of the device, e.g. */sys/devices/pci0000:00/0000:00:18.3/hwmon*. (Note that the subdirectory *hwmon/hwmon#*, where *#* is a number is not part of the path!) Has to be used together with *input-filename*.
|
||||
This can also be an array of strings, for which, it just works like *hwmon-path*.
|
||||
|
||||
*hwmon-name*: ++
|
||||
typeof: string ++
|
||||
Select a hwmon device by its name (from /sys/class/hwmon/\*/name), e.g. *amdgpu*.
|
||||
Requires *input-filename* to be set.
|
||||
Cannot be used together with *hwmon-path* or *hwmon-path-abs*.
|
||||
|
||||
*input-filename*: ++
|
||||
typeof: string ++
|
||||
The temperature filename of your *hwmon-path-abs*, e.g. *temp1_input*
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -18,6 +18,13 @@ Addressed by *wlr/taskbar*
|
||||
default: false ++
|
||||
If set to false applications on the waybar's current output will be shown. Otherwise, all applications are shown.
|
||||
|
||||
*bar-css-states*: ++
|
||||
typeof: bool ++
|
||||
default: false ++
|
||||
If set to true, application state is exposed as CSS classes on the Waybar
|
||||
window. Maximized and fullscreen state is aggregated across applications
|
||||
known to belong to the active workspace. See *Bar state style* below.
|
||||
|
||||
*format*: ++
|
||||
typeof: string ++
|
||||
default: {icon} ++
|
||||
@@ -52,11 +59,22 @@ Addressed by *wlr/taskbar*
|
||||
default: false ++
|
||||
If set to true, always reorder the tasks in the taskbar so that the currently active one is first. Otherwise don't reorder.
|
||||
|
||||
*active-only*: ++
|
||||
typeof: bool ++
|
||||
default: false ++
|
||||
If set to true, only the currently active application button is shown.
|
||||
Other applications remain tracked and reappear when activated.
|
||||
|
||||
*sort-by-app-id*: ++
|
||||
typeof: bool ++
|
||||
default: false ++
|
||||
If set to true, group tasks by their app_id. Cannot be used with 'active-first'.
|
||||
|
||||
*expand*: ++
|
||||
typeof: bool ++
|
||||
default: false ++
|
||||
If set to true, task buttons stretch to fill the available space in the taskbar and long titles are ellipsized to fit. Only takes effect on a horizontal bar; on a vertical bar the buttons keep their content-based size. If set to false, buttons are sized to their content.
|
||||
|
||||
*on-click*: ++
|
||||
typeof: string ++
|
||||
The action which should be triggered when clicking on the application button with the left mouse button.
|
||||
@@ -156,3 +174,35 @@ Invalid expressions (e.g., mismatched parentheses) are skipped.
|
||||
- *#taskbar button.minimized*
|
||||
- *#taskbar button.active*
|
||||
- *#taskbar button.fullscreen*
|
||||
|
||||
# Bar state style
|
||||
|
||||
When *bar-css-states* is enabled, the following classes are added to
|
||||
*window#waybar*:
|
||||
|
||||
- *window#waybar.toplevel-active*
|
||||
- *window#waybar.toplevel-maximized*
|
||||
- *window#waybar.toplevel-minimized*
|
||||
- *window#waybar.toplevel-fullscreen*
|
||||
|
||||
The active, minimized classes describe the active application. The maximized
|
||||
and fullscreen classes are set if any non-minimized application known to belong
|
||||
to the active workspace has that state.
|
||||
|
||||
Workspace membership is learned when an application is activated and requires
|
||||
the compositor to support *ext-workspace-v1*. Before an application has been
|
||||
activated during the current Waybar session, its workspace may be unknown. On
|
||||
compositors without *ext-workspace-v1*, these classes fall back to the active
|
||||
application's state.
|
||||
|
||||
For example:
|
||||
|
||||
```
|
||||
window#waybar {
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
window#waybar.toplevel-maximized {
|
||||
background-color: rgba(0, 0, 0, 1);
|
||||
}
|
||||
```
|
||||
|
||||
+8
-2
@@ -19,7 +19,7 @@ Valid locations for this file are:
|
||||
A good starting point is the default configuration found at https://github.com/Alexays/Waybar/blob/master/resources/config.jsonc
|
||||
Also, a minimal example configuration can be found at the bottom of this man page.
|
||||
|
||||
The visual display elements for waybar use a CSS stylesheet, see *waybar-styles(5)* for details.
|
||||
The visual display elements for waybar use a CSS stylesheet. Waybar supports *style.css* and the appearance-specific *style-light.css* and *style-dark.css* files; see *waybar-styles(5)* for details.
|
||||
|
||||
# BAR CONFIGURATION
|
||||
|
||||
@@ -36,7 +36,7 @@ The visual display elements for waybar use a CSS stylesheet, see *waybar-styles(
|
||||
*expand-right* ++
|
||||
typeof: bool ++
|
||||
default: false ++
|
||||
Enables the modules-left to consume all left over space dynamically.
|
||||
Enables the modules-right to consume all left over space dynamically.
|
||||
|
||||
*layer* ++
|
||||
typeof: string ++
|
||||
@@ -168,6 +168,7 @@ The visual display elements for waybar use a CSS stylesheet, see *waybar-styles(
|
||||
# MODULE FORMAT
|
||||
|
||||
You can use PangoMarkupFormat (See https://developer.gnome.org/pango/stable/PangoMarkupFormat.html#PangoMarkupFormat).
|
||||
Tooltip appearance is generally controlled globally via CSS and cannot be truly scoped per module. Some modules (such as the clock) provide limited workarounds for customization.
|
||||
|
||||
e.g.
|
||||
|
||||
@@ -363,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(
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ option('dbusmenu-gtk', type: 'feature', value: 'auto', description: 'Enable supp
|
||||
option('man-pages', type: 'feature', value: 'auto', description: 'Generate and install man pages')
|
||||
option('mpd', type: 'feature', value: 'auto', description: 'Enable support for the Music Player Daemon')
|
||||
option('rfkill', type: 'feature', value: 'auto', description: 'Enable support for RFKILL')
|
||||
option('sndio', type: 'feature', value: 'auto', description: 'Enable support for sndio')
|
||||
option('sndio', type: 'feature', value: 'auto', description: 'Enable support for volume control via sndio')
|
||||
option('logind', type: 'feature', value: 'auto', description: 'Enable support for logind')
|
||||
option('tests', type: 'feature', value: 'auto', description: 'Enable tests')
|
||||
option('experimental', type : 'boolean', value : false, description: 'Enable experimental features')
|
||||
|
||||
+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
|
||||
|
||||
+10
-10
@@ -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
|
||||
@@ -155,7 +155,7 @@
|
||||
},
|
||||
"power-profiles-daemon": {
|
||||
"format": "{icon}",
|
||||
"tooltip-format": "Power profile: {profile}\nDriver: {driver}",
|
||||
"tooltip-format": "Power profile: {profile}\nCPU driver: {cpu_driver}\nPlatform driver: {platform_driver}",
|
||||
"tooltip": true,
|
||||
"format-icons": {
|
||||
"default": "",
|
||||
@@ -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":
|
||||
|
||||
+11
-6
@@ -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 {};
|
||||
}
|
||||
@@ -151,19 +151,24 @@ void AAppIconLabel::updateAppIconName(const std::string& app_identifier,
|
||||
}
|
||||
|
||||
void AAppIconLabel::updateAppIcon() {
|
||||
if (update_app_icon_) {
|
||||
if (update_app_icon_ || (!iconEnabled() && image_.get_visible())) {
|
||||
update_app_icon_ = false;
|
||||
if (app_icon_name_.empty()) {
|
||||
image_.set_visible(false);
|
||||
} else if (app_icon_name_.front() == '/') {
|
||||
auto pixbuf = Gdk::Pixbuf::create_from_file(app_icon_name_);
|
||||
try {
|
||||
int scaled_icon_size = app_icon_size_ * image_.get_scale_factor();
|
||||
pixbuf = Gdk::Pixbuf::create_from_file(app_icon_name_, scaled_icon_size, scaled_icon_size);
|
||||
auto pixbuf =
|
||||
Gdk::Pixbuf::create_from_file(app_icon_name_, scaled_icon_size, scaled_icon_size);
|
||||
|
||||
auto surface = Gdk::Cairo::create_surface_from_pixbuf(pixbuf, image_.get_scale_factor(),
|
||||
image_.get_window());
|
||||
image_.set(surface);
|
||||
image_.set_visible(true);
|
||||
} catch (const Glib::Exception& e) {
|
||||
spdlog::warn("Failed to load app icon {}: {}", app_icon_name_, std::string(e.what()));
|
||||
image_.set_visible(false);
|
||||
}
|
||||
} else {
|
||||
image_.set_from_icon_name(app_icon_name_, Gtk::ICON_SIZE_INVALID);
|
||||
image_.set_visible(true);
|
||||
|
||||
+48
-1
@@ -2,6 +2,8 @@
|
||||
|
||||
#include <gdkmm/pixbuf.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <regex>
|
||||
#include <string>
|
||||
|
||||
namespace waybar {
|
||||
|
||||
@@ -9,6 +11,10 @@ AIconLabel::AIconLabel(const Json::Value& config, const std::string& name, const
|
||||
const std::string& format, uint16_t interval, bool ellipsize,
|
||||
bool enable_click, bool enable_scroll)
|
||||
: ALabel(config, name, id, format, interval, ellipsize, enable_click, enable_scroll) {
|
||||
if (config["icon-size"].isUInt()) {
|
||||
app_icon_size_ = config["icon-size"].asUInt();
|
||||
}
|
||||
image_.set_pixel_size(app_icon_size_);
|
||||
event_box_.remove();
|
||||
label_.unset_name();
|
||||
label_.get_style_context()->remove_class(MODULE_CLASS);
|
||||
@@ -55,13 +61,54 @@ AIconLabel::AIconLabel(const Json::Value& config, const std::string& name, const
|
||||
event_box_.add(box_);
|
||||
}
|
||||
|
||||
std::tuple<std::string, std::string> AIconLabel::extractIcon(const std::string& input) {
|
||||
std::string icon_result = "";
|
||||
std::string label_result = input;
|
||||
try {
|
||||
static const std::regex icon_search(R"((?=\\0icon\\1f).+?(?=\\n))");
|
||||
std::smatch icon_match;
|
||||
if (std::regex_search(input, icon_match, icon_search)) {
|
||||
icon_result = icon_match[0].str().substr(9);
|
||||
|
||||
static const std::regex clean_label_pattern(R"(\\0icon\\1f.+?\\n)");
|
||||
label_result = std::regex_replace(input, clean_label_pattern, "");
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
spdlog::warn("Error while parsing icon from label. {}", e.what());
|
||||
}
|
||||
|
||||
return std::make_tuple(icon_result, label_result);
|
||||
}
|
||||
|
||||
auto AIconLabel::update() -> void {
|
||||
label_contains_icon = false;
|
||||
|
||||
auto [iconLabel, cleanLabel] = extractIcon(label_.get_label().c_str());
|
||||
label_contains_icon = iconLabel.length() > 0;
|
||||
|
||||
if (label_contains_icon) {
|
||||
label_.set_markup(cleanLabel);
|
||||
|
||||
if (iconLabel.front() == '/') {
|
||||
int scaled_icon_size = app_icon_size_ * image_.get_scale_factor();
|
||||
auto pixbuf = Gdk::Pixbuf::create_from_file(iconLabel, scaled_icon_size, scaled_icon_size);
|
||||
|
||||
auto surface = Gdk::Cairo::create_surface_from_pixbuf(pixbuf, image_.get_scale_factor(),
|
||||
image_.get_window());
|
||||
image_.set(surface);
|
||||
image_.set_visible(true);
|
||||
} else {
|
||||
image_.set_from_icon_name(iconLabel, Gtk::ICON_SIZE_INVALID);
|
||||
image_.set_visible(true);
|
||||
}
|
||||
}
|
||||
|
||||
image_.set_visible(image_.get_visible() && iconEnabled());
|
||||
ALabel::update();
|
||||
}
|
||||
|
||||
bool AIconLabel::iconEnabled() const {
|
||||
return config_["icon"].isBool() ? config_["icon"].asBool() : false;
|
||||
return label_contains_icon || (config_["icon"].isBool() ? config_["icon"].asBool() : false);
|
||||
}
|
||||
|
||||
} // namespace waybar
|
||||
|
||||
+36
-4
@@ -26,7 +26,7 @@ ALabel::ALabel(const Json::Value& config, const std::string& name, const std::st
|
||||
: std::chrono::milliseconds(
|
||||
(config_["interval"].isNumeric()
|
||||
? std::max(1L, // Minimum 1ms due to millisecond precision
|
||||
static_cast<long>(config_["interval"].asDouble()) * 1000)
|
||||
static_cast<long>(config_["interval"].asDouble() * 1000))
|
||||
: 1000 * (long)interval))),
|
||||
default_format_(format_) {
|
||||
label_.set_name(name);
|
||||
@@ -91,13 +91,17 @@ ALabel::ALabel(const Json::Value& config, const std::string& name, const std::st
|
||||
|
||||
// Make the GtkBuilder and check for errors in his parsing
|
||||
if (gtk_builder_add_from_string(builder, fileContent.str().c_str(), -1, nullptr) == 0U) {
|
||||
g_object_unref(builder);
|
||||
throw std::runtime_error("Error found in the file " + menuFile);
|
||||
}
|
||||
|
||||
menu_ = gtk_builder_get_object(builder, "menu");
|
||||
if (menu_ == nullptr) {
|
||||
g_object_unref(builder);
|
||||
throw std::runtime_error("Failed to get 'menu' object from GtkBuilder");
|
||||
}
|
||||
// Keep the menu alive after dropping the transient GtkBuilder.
|
||||
g_object_ref(menu_);
|
||||
submenus_ = std::map<std::string, GtkMenuItem*>();
|
||||
menuActionsMap_ = std::map<std::string, std::string>();
|
||||
|
||||
@@ -105,11 +109,17 @@ ALabel::ALabel(const Json::Value& config, const std::string& name, const std::st
|
||||
for (Json::Value::const_iterator it = config_["menu-actions"].begin();
|
||||
it != config_["menu-actions"].end(); ++it) {
|
||||
std::string key = it.key().asString();
|
||||
submenus_[key] = GTK_MENU_ITEM(gtk_builder_get_object(builder, key.c_str()));
|
||||
auto* item = gtk_builder_get_object(builder, key.c_str());
|
||||
if (item == nullptr) {
|
||||
spdlog::warn("Menu item '{}' not found in builder file", key);
|
||||
continue;
|
||||
}
|
||||
submenus_[key] = GTK_MENU_ITEM(item);
|
||||
menuActionsMap_[key] = it->asString();
|
||||
g_signal_connect(submenus_[key], "activate", G_CALLBACK(handleGtkMenuEvent),
|
||||
(gpointer)menuActionsMap_[key].c_str());
|
||||
}
|
||||
g_object_unref(builder);
|
||||
} catch (std::runtime_error& e) {
|
||||
spdlog::warn("Error while creating the menu : {}. Menu popup not activated.", e.what());
|
||||
}
|
||||
@@ -129,6 +139,26 @@ ALabel::ALabel(const Json::Value& config, const std::string& name, const std::st
|
||||
|
||||
auto ALabel::update() -> void { AModule::update(); }
|
||||
|
||||
bool ALabel::setLabelMarkup(const Glib::ustring& markup) {
|
||||
if (last_label_markup_ == markup) {
|
||||
return false;
|
||||
}
|
||||
|
||||
label_.set_markup(markup);
|
||||
last_label_markup_ = markup;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ALabel::setTooltipMarkup(const Glib::ustring& markup) {
|
||||
if (last_tooltip_markup_ == markup) {
|
||||
return false;
|
||||
}
|
||||
|
||||
label_.set_tooltip_markup(markup);
|
||||
last_tooltip_markup_ = markup;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string ALabel::getIcon(uint16_t percentage, const std::string& alt, uint16_t max) {
|
||||
auto format_icons = config_["format-icons"];
|
||||
if (format_icons.isObject()) {
|
||||
@@ -141,7 +171,8 @@ std::string ALabel::getIcon(uint16_t percentage, const std::string& alt, uint16_
|
||||
if (format_icons.isArray()) {
|
||||
auto size = format_icons.size();
|
||||
if (size != 0U) {
|
||||
auto idx = std::clamp(percentage / ((max == 0 ? 100 : max) / size), 0U, size - 1);
|
||||
auto divisor = std::max(1U, (max == 0 ? 100U : static_cast<unsigned>(max)) / size);
|
||||
auto idx = std::clamp(percentage / divisor, 0U, size - 1);
|
||||
format_icons = format_icons[idx];
|
||||
}
|
||||
}
|
||||
@@ -167,7 +198,8 @@ std::string ALabel::getIcon(uint16_t percentage, const std::vector<std::string>&
|
||||
if (format_icons.isArray()) {
|
||||
auto size = format_icons.size();
|
||||
if (size != 0U) {
|
||||
auto idx = std::clamp(percentage / ((max == 0 ? 100 : max) / size), 0U, size - 1);
|
||||
auto divisor = std::max(1U, (max == 0 ? 100U : static_cast<unsigned>(max)) / size);
|
||||
auto idx = std::clamp(percentage / divisor, 0U, size - 1);
|
||||
format_icons = format_icons[idx];
|
||||
}
|
||||
}
|
||||
|
||||
+20
-5
@@ -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,11 +91,18 @@ 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);
|
||||
}
|
||||
}
|
||||
if (menu_ != nullptr) {
|
||||
g_object_unref(menu_);
|
||||
menu_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
auto AModule::update() -> void {
|
||||
@@ -114,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;
|
||||
@@ -166,9 +181,9 @@ bool AModule::handleUserEvent(GdkEventButton* const& e) {
|
||||
}
|
||||
|
||||
// Check that a menu has been configured
|
||||
if (config_["menu"].isString()) {
|
||||
if (rec != eventMap_.cend() && config_["menu"].isString()) {
|
||||
// Check if the event is the one specified for the "menu" option
|
||||
if (rec->second == config_["menu"].asString()) {
|
||||
if (rec->second == config_["menu"].asString() && menu_ != nullptr) {
|
||||
// Popup the menu
|
||||
gtk_widget_show_all(GTK_WIDGET(menu_));
|
||||
gtk_menu_popup_at_pointer(GTK_MENU(menu_), reinterpret_cast<GdkEvent*>(e));
|
||||
|
||||
+70
-4
@@ -172,6 +172,10 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config)
|
||||
right_.set_spacing(spacing);
|
||||
}
|
||||
|
||||
if (config.isMember("height") && !config["height"].isUInt()) {
|
||||
spdlog::warn("Invalid type for 'height', expected unsigned integer");
|
||||
}
|
||||
|
||||
height_ = config["height"].isUInt() ? config["height"].asUInt() : 0;
|
||||
width_ = config["width"].isUInt() ? config["width"].asUInt() : 0;
|
||||
|
||||
@@ -263,6 +267,16 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config)
|
||||
|
||||
window.signal_map_event().connect_notify(sigc::mem_fun(*this, &Bar::onMap));
|
||||
|
||||
window.signal_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;
|
||||
@@ -309,6 +323,19 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config)
|
||||
setupWidgets();
|
||||
window.show_all();
|
||||
|
||||
/*
|
||||
* If gtk-layer-shell's synchronous wait for the initial configure timed out, show_all() can
|
||||
* return with a configured but not-yet-presented surface. Kick GTK/layer-shell once control has
|
||||
* returned to the main loop, when any late initial configure has been dispatched and widgets have
|
||||
* had a chance to allocate/draw.
|
||||
*/
|
||||
Glib::signal_idle().connect(sigc::track_obj([this] {
|
||||
window.queue_resize();
|
||||
window.queue_draw();
|
||||
forceLayerCommit();
|
||||
return false;
|
||||
}, *this));
|
||||
|
||||
if (spdlog::should_log(spdlog::level::debug)) {
|
||||
// Unfortunately, this function isn't in the C++ bindings, so we have to call the C version.
|
||||
char* gtk_tree = gtk_style_context_to_string(
|
||||
@@ -374,7 +401,14 @@ void waybar::Bar::setMode(const struct bar_mode& mode) {
|
||||
* gtk-layer-shell schedules a commit on the next frame event in GTK, but this could fail in
|
||||
* certain scenarios, such as fully occluded bar.
|
||||
*/
|
||||
forceLayerCommit();
|
||||
}
|
||||
|
||||
void waybar::Bar::forceLayerCommit() {
|
||||
auto* gtk_window = window.gobj();
|
||||
if (gtk_window != nullptr && gtk_widget_get_realized(GTK_WIDGET(gtk_window))) {
|
||||
gtk_layer_try_force_commit(gtk_window);
|
||||
}
|
||||
wl_display_flush(Client::inst()->wl_display);
|
||||
}
|
||||
|
||||
@@ -541,13 +575,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);
|
||||
}
|
||||
@@ -650,6 +686,17 @@ void waybar::Bar::onConfigure(GdkEventConfigure* ev) {
|
||||
|
||||
configureGlobalOffset(ev->width, ev->height);
|
||||
spdlog::info(BAR_SIZE_MSG, ev->width, ev->height, output->name);
|
||||
|
||||
/*
|
||||
* gtk-layer-shell waits for the compositor's initial configure while realizing the window. On a
|
||||
* busy compositor (common during session startup) that wait can time out even though the initial
|
||||
* configure arrives shortly afterwards. In that case GTK may not schedule the frame commit that
|
||||
* presents the first layer-surface buffer, leaving an otherwise configured bar invisible. Force a
|
||||
* commit after every configure so late initial configures, and later compositor-driven resizes,
|
||||
* always result in a submitted surface state.
|
||||
*/
|
||||
window.queue_draw();
|
||||
forceLayerCommit();
|
||||
}
|
||||
|
||||
void waybar::Bar::configureGlobalOffset(int width, int height) {
|
||||
@@ -694,3 +741,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);
|
||||
});
|
||||
|
||||
|
||||
+29
-1
@@ -63,12 +63,21 @@ Group::Group(const std::string& name, const std::string& id, const Json::Value&
|
||||
? drawer_config["transition-left-to-right"].asBool()
|
||||
: true);
|
||||
click_to_reveal = drawer_config["click-to-reveal"].asBool();
|
||||
reveal_delay = drawer_config["reveal-delay"].asInt();
|
||||
|
||||
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");
|
||||
|
||||
@@ -96,13 +105,27 @@ void Group::hide_group() {
|
||||
|
||||
bool Group::handleMouseEnter(GdkEventCrossing* const& e) {
|
||||
if (!click_to_reveal) {
|
||||
if (reveal_delay > 0) {
|
||||
if (reveal_timeout_.connected()) {
|
||||
reveal_timeout_.disconnect();
|
||||
}
|
||||
|
||||
reveal_timeout_ = Glib::signal_timeout().connect(
|
||||
[this]() { show_group(); return false; },
|
||||
reveal_delay);
|
||||
} else {
|
||||
show_group();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Group::handleMouseLeave(GdkEventCrossing* const& e) {
|
||||
if (!click_to_reveal && e->detail != GDK_NOTIFY_INFERIOR) {
|
||||
if (reveal_delay > 0 && reveal_timeout_.connected()) {
|
||||
reveal_timeout_.disconnect();
|
||||
}
|
||||
|
||||
hide_group();
|
||||
}
|
||||
return false;
|
||||
@@ -124,6 +147,11 @@ auto Group::update() -> void {
|
||||
// noop
|
||||
}
|
||||
|
||||
bool Group::handleScroll(GdkEventScroll* e) {
|
||||
// no scroll.
|
||||
return true;
|
||||
}
|
||||
|
||||
Gtk::Box& Group::getBox() { return is_drawer ? (is_first_widget ? box : revealer_box) : box; }
|
||||
|
||||
void Group::addWidget(Gtk::Widget& widget) {
|
||||
|
||||
+13
-5
@@ -33,7 +33,10 @@ static void writeSignalToPipe(int signum) {
|
||||
// to `signal_handler`.
|
||||
static void catchSignals(waybar::SafeSignal<int>& signal_handler) {
|
||||
int fd[2];
|
||||
pipe(fd);
|
||||
if (pipe(fd) != 0) {
|
||||
spdlog::error("Failed to create signal pipe: {}", strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
int signal_pipe_read_fd = fd[0];
|
||||
signal_pipe_write_fd = fd[1];
|
||||
@@ -52,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;
|
||||
@@ -116,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:
|
||||
@@ -137,15 +144,16 @@ static void handleSignalMainThread(int signum, bool& reload) {
|
||||
break;
|
||||
case SIGCHLD:
|
||||
spdlog::debug("Received SIGCHLD in signalThread");
|
||||
if (!reap.empty()) {
|
||||
reap_mtx.lock();
|
||||
for (auto it = reap.begin(); it != reap.end(); ++it) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(reap_mtx);
|
||||
for (auto it = reap.begin(); it != reap.end();) {
|
||||
if (waitpid(*it, nullptr, WNOHANG) == *it) {
|
||||
spdlog::debug("Reaped child with PID: {}", *it);
|
||||
it = reap.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
reap_mtx.unlock();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
||||
@@ -58,11 +58,11 @@ auto waybar::modules::Backlight::update() -> void {
|
||||
tooltip_format = config_["tooltip-format"].asString();
|
||||
}
|
||||
if (!tooltip_format.empty()) {
|
||||
label_.set_tooltip_text(fmt::format(fmt::runtime(tooltip_format),
|
||||
label_.set_tooltip_markup(fmt::format(fmt::runtime(tooltip_format),
|
||||
fmt::arg("percent", percent),
|
||||
fmt::arg("icon", getIcon(percent))));
|
||||
} else {
|
||||
label_.set_tooltip_text(desc);
|
||||
label_.set_tooltip_markup(desc);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
+112
-58
@@ -2,6 +2,9 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
|
||||
#include "util/command.hpp"
|
||||
#if defined(__FreeBSD__)
|
||||
@@ -33,6 +36,11 @@ waybar::modules::Battery::Battery(const std::string& id, const Bar& bar, const J
|
||||
}
|
||||
udev_monitor_enable_receiving(mon_.get());
|
||||
|
||||
if (config_["smooth-power"].isBool()) {
|
||||
smoothPowerEnable_ = config_["smooth-power"].asBool();
|
||||
if (smoothPowerEnable_ && config_["smooth-power-time-constant"].isNumeric())
|
||||
time_constant_s_ = std::max(1.0, config_["smooth-power-time-constant"].asDouble());
|
||||
}
|
||||
if (config_["weighted-average"].isBool()) weightedAverage_ = config_["weighted-average"].asBool();
|
||||
#endif
|
||||
spdlog::debug("battery: worker interval is {}", interval_.count());
|
||||
@@ -117,17 +125,16 @@ void waybar::modules::Battery::refreshBatteries() {
|
||||
if (((bat_defined && dir_name == config_["bat"].asString()) || !bat_defined) &&
|
||||
(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;
|
||||
}
|
||||
}
|
||||
@@ -139,7 +146,9 @@ void waybar::modules::Battery::refreshBatteries() {
|
||||
auto event_path = (node.path() / "uevent");
|
||||
auto wd = inotify_add_watch(battery_watch_fd_, event_path.c_str(), IN_ACCESS);
|
||||
if (wd < 0) {
|
||||
throw std::runtime_error("Could not watch events for " + node.path().string());
|
||||
spdlog::warn("Could not watch events for {} (device may have been removed)",
|
||||
node.path().string());
|
||||
continue;
|
||||
}
|
||||
batteries_[node.path()] = wd;
|
||||
}
|
||||
@@ -152,7 +161,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()) {
|
||||
@@ -273,11 +282,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.
|
||||
@@ -286,64 +295,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.
|
||||
@@ -351,28 +370,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;
|
||||
@@ -402,9 +424,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) {
|
||||
@@ -562,11 +585,31 @@ waybar::modules::Battery::getInfos() {
|
||||
if (online && current_status != "Discharging") status = "Plugged";
|
||||
}
|
||||
|
||||
if (total_energy_exists && total_power_exists && total_power != 0) {
|
||||
if (!smoothPowerEnable_) {
|
||||
smooth_power_ = total_power;
|
||||
} else {
|
||||
if (status != old_status_raw_) {
|
||||
smooth_power_ = total_power;
|
||||
last_t_ = std::chrono::steady_clock::now();
|
||||
} else {
|
||||
std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
|
||||
double dt_s =
|
||||
std::chrono::duration_cast<std::chrono::duration<double> >(now - last_t_).count();
|
||||
smooth_power_ = smooth_power_ + ((1 - std::exp(-dt_s / time_constant_s_)) *
|
||||
(total_power - smooth_power_));
|
||||
last_t_ = now;
|
||||
}
|
||||
|
||||
old_status_raw_ = status;
|
||||
}
|
||||
}
|
||||
|
||||
float time_remaining{0.0f};
|
||||
if (status == "Discharging" && time_to_empty_now_exists) {
|
||||
if (time_to_empty_now != 0) time_remaining = (float)time_to_empty_now / 3600.0f;
|
||||
} else if (status == "Discharging" && total_power_exists && total_energy_exists) {
|
||||
if (total_power != 0) time_remaining = (float)total_energy / total_power;
|
||||
if (smooth_power_ != 0) time_remaining = (float)total_energy / smooth_power_;
|
||||
} else if (status == "Charging" && time_to_full_now_exists) {
|
||||
if (time_to_full_now_exists && (time_to_full_now != 0))
|
||||
time_remaining = -(float)time_to_full_now / 3600.0f;
|
||||
@@ -584,13 +627,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) {
|
||||
@@ -685,13 +729,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;
|
||||
puts(status.c_str());
|
||||
|
||||
// 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);
|
||||
@@ -790,16 +841,19 @@ void waybar::modules::Battery::processEvents(std::string& state, std::string& st
|
||||
if (!events.isObject() || events.empty()) {
|
||||
return;
|
||||
}
|
||||
std::string event_name = fmt::format("on-{}-{}", status == "discharging" ? status : "charging",
|
||||
state.empty() ? std::to_string(capacity) : state);
|
||||
auto exec = [](Json::Value const& event) {
|
||||
if (!event.isString()) return;
|
||||
if (auto command = event.asString(); !command.empty()) {
|
||||
util::command::exec(command, "");
|
||||
}
|
||||
};
|
||||
std::string status_name = status == "discharging" ? "on-discharging" : "on-charging";
|
||||
std::string event_name = status_name + '-' + (state.empty() ? std::to_string(capacity) : state);
|
||||
if (last_event_ != event_name) {
|
||||
spdlog::debug("battery: triggering event {}", event_name);
|
||||
if (events[event_name].isString()) {
|
||||
std::string exec = events[event_name].asString();
|
||||
// Execute the command if it is not empty
|
||||
if (!exec.empty()) {
|
||||
util::command::exec(exec, "");
|
||||
}
|
||||
exec(events[event_name]);
|
||||
if (!last_event_.empty() && last_event_[3] != event_name[3]) {
|
||||
exec(events[status_name]);
|
||||
}
|
||||
last_event_ = event_name;
|
||||
}
|
||||
|
||||
@@ -111,13 +111,16 @@ waybar::modules::Bluetooth::Bluetooth(const std::string& id, const Json::Value&
|
||||
findConnectedDevices(cur_controller_->path, connected_devices_);
|
||||
}
|
||||
|
||||
if (manager_) {
|
||||
g_signal_connect(manager_.get(), "object-added", G_CALLBACK(onObjectAdded), this);
|
||||
g_signal_connect(manager_.get(), "object-removed", G_CALLBACK(onObjectRemoved), this);
|
||||
g_signal_connect(manager_.get(), "interface-proxy-properties-changed",
|
||||
G_CALLBACK(onInterfaceProxyPropertiesChanged), this);
|
||||
g_signal_connect(manager_.get(), "interface-added", G_CALLBACK(onInterfaceAddedOrRemoved), this);
|
||||
g_signal_connect(manager_.get(), "interface-added", G_CALLBACK(onInterfaceAddedOrRemoved),
|
||||
this);
|
||||
g_signal_connect(manager_.get(), "interface-removed", G_CALLBACK(onInterfaceAddedOrRemoved),
|
||||
this);
|
||||
}
|
||||
|
||||
#ifdef WANT_RFKILL
|
||||
rfkill_.on_update.connect(sigc::hide(sigc::mem_fun(*this, &Bluetooth::update)));
|
||||
@@ -446,6 +449,10 @@ auto waybar::modules::Bluetooth::getControllerProperties(GDBusObject* object,
|
||||
auto waybar::modules::Bluetooth::findCurController() -> std::optional<ControllerInfo> {
|
||||
std::optional<ControllerInfo> controller_info;
|
||||
|
||||
if (!manager_) {
|
||||
return controller_info;
|
||||
}
|
||||
|
||||
GList* objects = g_dbus_object_manager_get_objects(manager_.get());
|
||||
for (GList* l = objects; l != NULL; l = l->next) {
|
||||
GDBusObject* object = G_DBUS_OBJECT(l->data);
|
||||
@@ -465,6 +472,9 @@ auto waybar::modules::Bluetooth::findCurController() -> std::optional<Controller
|
||||
auto waybar::modules::Bluetooth::findConnectedDevices(const std::string& cur_controller_path,
|
||||
std::vector<DeviceInfo>& connected_devices)
|
||||
-> void {
|
||||
if (!manager_) {
|
||||
return;
|
||||
}
|
||||
GList* objects = g_dbus_object_manager_get_objects(manager_.get());
|
||||
for (GList* l = objects; l != NULL; l = l->next) {
|
||||
GDBusObject* object = G_DBUS_OBJECT(l->data);
|
||||
|
||||
@@ -26,6 +26,7 @@ void waybar::modules::cava::Cava::pause_resume() { backend_->doPauseResume(); }
|
||||
auto waybar::modules::cava::Cava::onUpdate(const std::string& input) -> void {
|
||||
Glib::signal_idle().connect_once([this, input]() {
|
||||
if (silence_) {
|
||||
silence_ = false;
|
||||
label_.get_style_context()->remove_class("silent");
|
||||
if (!label_.get_style_context()->has_class("updated"))
|
||||
label_.get_style_context()->add_class("updated");
|
||||
@@ -38,7 +39,6 @@ auto waybar::modules::cava::Cava::onUpdate(const std::string& input) -> void {
|
||||
label_.show();
|
||||
ALabel::update();
|
||||
});
|
||||
silence_ = false;
|
||||
}
|
||||
|
||||
auto waybar::modules::cava::Cava::onSilence() -> void {
|
||||
|
||||
@@ -218,7 +218,7 @@ void waybar::modules::cava::CavaBackend::loadConfig() {
|
||||
prm_.input = ::cava::input_method_by_name(config_["method"].asString().c_str());
|
||||
if (config_["source"].isString()) {
|
||||
if (prm_.audio_source) free(prm_.audio_source);
|
||||
prm_.audio_source = config_["source"].asString().data();
|
||||
prm_.audio_source = strdup(config_["source"].asString().c_str());
|
||||
}
|
||||
if (config_["sample_rate"].isNumeric()) prm_.samplerate = config_["sample_rate"].asLargestInt();
|
||||
if (config_["sample_bits"].isInt()) prm_.samplebits = config_["sample_bits"].asInt();
|
||||
|
||||
@@ -35,7 +35,7 @@ CFFI::CFFI(const std::string& name, const std::string& id, const Json::Value& co
|
||||
throw std::runtime_error{std::string{"Missing wbcffi_init function: "} + dlerror()};
|
||||
}
|
||||
hooks_.deinit = reinterpret_cast<DenitFn*>(dlsym(handle, "wbcffi_deinit"));
|
||||
if (!hooks_.init) {
|
||||
if (!hooks_.deinit) {
|
||||
throw std::runtime_error{std::string{"Missing wbcffi_deinit function: "} + dlerror()};
|
||||
}
|
||||
// Optional functions
|
||||
@@ -71,6 +71,7 @@ CFFI::CFFI(const std::string& name, const std::string& id, const Json::Value& co
|
||||
|
||||
// Prepare config_entries array
|
||||
std::vector<ffi::wbcffi_config_entry> config_entries;
|
||||
config_entries.reserve(keys.size());
|
||||
for (size_t i = 0; i < keys.size(); i++) {
|
||||
config_entries.push_back({keys[i].c_str(), config_entries_stringstor[i].c_str()});
|
||||
}
|
||||
|
||||
@@ -402,8 +402,13 @@ auto waybar::modules::Clock::get_calendar(const year_month_day& today, const yea
|
||||
data = g_utf8_find_next_char(data, end);
|
||||
}
|
||||
}
|
||||
// Note: the stream's default fill character is already a space (L' ' on
|
||||
// libstdc++'s wide FormatStream, ' ' on libc++'s narrow one), so no
|
||||
// std::setfill is needed. Passing std::setfill(' ')/std::setfill(L' ')
|
||||
// here is not portable because the fill char type must match the
|
||||
// FormatStream's char type, which differs between standard libraries.
|
||||
os << Glib::ustring::format(
|
||||
(cldWPos_ != WS::LEFT || line == 0) ? std::left : std::right, std::setfill(L' '),
|
||||
(cldWPos_ != WS::LEFT || line == 0) ? std::left : std::right,
|
||||
std::setw(cldMonColLen_ + ((line < 2) ? cldWnLen_ - wideCharCount : 0)),
|
||||
calendarLine);
|
||||
|
||||
|
||||
+24
-7
@@ -26,9 +26,7 @@ auto waybar::modules::Cpu::update() -> void {
|
||||
auto [load1, load5, load15] = Load::getLoad();
|
||||
auto [cpu_usage, tooltip] = CpuUsage::getCpuUsage(prev_times_);
|
||||
auto [max_frequency, min_frequency, avg_frequency] = CpuFrequency::getCpuFrequency();
|
||||
if (tooltipEnabled()) {
|
||||
label_.set_tooltip_text(tooltip);
|
||||
}
|
||||
|
||||
auto format = format_;
|
||||
auto total_usage = cpu_usage.empty() ? 0 : cpu_usage[0];
|
||||
auto state = getState(total_usage);
|
||||
@@ -43,19 +41,38 @@ auto waybar::modules::Cpu::update() -> void {
|
||||
auto icons = std::vector<std::string>{state};
|
||||
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||
store.push_back(fmt::arg("load", load1));
|
||||
store.push_back(fmt::arg("load1", load1));
|
||||
store.push_back(fmt::arg("load5", load5));
|
||||
store.push_back(fmt::arg("load15", load15));
|
||||
store.push_back(fmt::arg("usage", total_usage));
|
||||
store.push_back(fmt::arg("icon", getIcon(total_usage, icons)));
|
||||
store.push_back(fmt::arg("max_frequency", max_frequency));
|
||||
store.push_back(fmt::arg("min_frequency", min_frequency));
|
||||
store.push_back(fmt::arg("avg_frequency", avg_frequency));
|
||||
std::vector<std::string> arg_names;
|
||||
arg_names.reserve(cpu_usage.size() * 2);
|
||||
for (size_t i = 1; i < cpu_usage.size(); ++i) {
|
||||
auto core_i = i - 1;
|
||||
auto core_format = fmt::format("usage{}", core_i);
|
||||
store.push_back(fmt::arg(core_format.c_str(), cpu_usage[i]));
|
||||
auto icon_format = fmt::format("icon{}", core_i);
|
||||
store.push_back(fmt::arg(icon_format.c_str(), getIcon(cpu_usage[i], icons)));
|
||||
arg_names.push_back(fmt::format("usage{}", core_i));
|
||||
store.push_back(fmt::arg(arg_names.back().c_str(), cpu_usage[i]));
|
||||
arg_names.push_back(fmt::format("icon{}", core_i));
|
||||
store.push_back(fmt::arg(arg_names.back().c_str(), getIcon(cpu_usage[i], icons)));
|
||||
}
|
||||
label_.set_markup(fmt::vformat(format, store));
|
||||
|
||||
if (tooltipEnabled()) {
|
||||
std::string tooltip_format;
|
||||
if (!state.empty() && config_["tooltip-format-" + state].isString()) {
|
||||
tooltip_format = config_["tooltip-format-" + state].asString();
|
||||
} else if (config_["tooltip-format"].isString()) {
|
||||
tooltip_format = config_["tooltip-format"].asString();
|
||||
}
|
||||
if (!tooltip_format.empty()) {
|
||||
label_.set_tooltip_markup(fmt::vformat(tooltip_format, store));
|
||||
} else {
|
||||
label_.set_tooltip_markup(tooltip);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Call parent update
|
||||
|
||||
@@ -20,12 +20,7 @@ waybar::modules::CpuFrequency::CpuFrequency(const std::string& id, const Json::V
|
||||
auto waybar::modules::CpuFrequency::update() -> void {
|
||||
// TODO: as creating dynamic fmt::arg arrays is buggy we have to calc both
|
||||
auto [max_frequency, min_frequency, avg_frequency] = CpuFrequency::getCpuFrequency();
|
||||
if (tooltipEnabled()) {
|
||||
auto tooltip =
|
||||
fmt::format("Minimum frequency: {}\nAverage frequency: {}\nMaximum frequency: {}\n",
|
||||
min_frequency, avg_frequency, max_frequency);
|
||||
label_.set_tooltip_text(tooltip);
|
||||
}
|
||||
|
||||
auto format = format_;
|
||||
auto state = getState(avg_frequency);
|
||||
if (!state.empty() && config_["format-" + state].isString()) {
|
||||
@@ -43,6 +38,18 @@ auto waybar::modules::CpuFrequency::update() -> void {
|
||||
store.push_back(fmt::arg("min_frequency", min_frequency));
|
||||
store.push_back(fmt::arg("avg_frequency", avg_frequency));
|
||||
label_.set_markup(fmt::vformat(format, store));
|
||||
|
||||
if (tooltipEnabled()) {
|
||||
std::string tooltip;
|
||||
if (config_["tooltip-format"].isString()) {
|
||||
tooltip = config_["tooltip-format"].asString();
|
||||
label_.set_tooltip_markup(fmt::vformat(tooltip, store));
|
||||
} else {
|
||||
tooltip = "Minimum frequency: {}\nAverage frequency: {}\nMaximum frequency: {}\n";
|
||||
label_.set_tooltip_markup(
|
||||
fmt::format(fmt::runtime(tooltip), min_frequency, avg_frequency, max_frequency));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Call parent update
|
||||
|
||||
@@ -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 (auto freq_file : frequency_files) {
|
||||
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&) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,9 +20,7 @@ waybar::modules::CpuUsage::CpuUsage(const std::string& id, const Json::Value& co
|
||||
auto waybar::modules::CpuUsage::update() -> void {
|
||||
// TODO: as creating dynamic fmt::arg arrays is buggy we have to calc both
|
||||
auto [cpu_usage, tooltip] = CpuUsage::getCpuUsage(prev_times_);
|
||||
if (tooltipEnabled()) {
|
||||
label_.set_tooltip_text(tooltip);
|
||||
}
|
||||
|
||||
auto format = format_;
|
||||
auto total_usage = cpu_usage.empty() ? 0 : cpu_usage[0];
|
||||
auto state = getState(total_usage);
|
||||
@@ -38,14 +36,25 @@ auto waybar::modules::CpuUsage::update() -> void {
|
||||
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||
store.push_back(fmt::arg("usage", total_usage));
|
||||
store.push_back(fmt::arg("icon", getIcon(total_usage, icons)));
|
||||
std::vector<std::string> arg_names;
|
||||
arg_names.reserve(cpu_usage.size() * 2);
|
||||
for (size_t i = 1; i < cpu_usage.size(); ++i) {
|
||||
auto core_i = i - 1;
|
||||
auto core_format = fmt::format("usage{}", core_i);
|
||||
store.push_back(fmt::arg(core_format.c_str(), cpu_usage[i]));
|
||||
auto icon_format = fmt::format("icon{}", core_i);
|
||||
store.push_back(fmt::arg(icon_format.c_str(), getIcon(cpu_usage[i], icons)));
|
||||
arg_names.push_back(fmt::format("usage{}", core_i));
|
||||
store.push_back(fmt::arg(arg_names.back().c_str(), cpu_usage[i]));
|
||||
arg_names.push_back(fmt::format("icon{}", core_i));
|
||||
store.push_back(fmt::arg(arg_names.back().c_str(), getIcon(cpu_usage[i], icons)));
|
||||
}
|
||||
label_.set_markup(fmt::vformat(format, store));
|
||||
|
||||
if (tooltipEnabled()) {
|
||||
if (config_["tooltip-format"].isString()) {
|
||||
tooltip = config_["tooltip-format"].asString();
|
||||
label_.set_tooltip_markup(fmt::vformat(tooltip, store));
|
||||
} else {
|
||||
label_.set_tooltip_markup(tooltip);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Call parent update
|
||||
@@ -71,7 +80,8 @@ std::tuple<std::vector<uint16_t>, std::string> waybar::modules::CpuUsage::getCpu
|
||||
auto [prev_idle, prev_total] = prev_times[0];
|
||||
const float delta_idle = curr_idle - prev_idle;
|
||||
const float delta_total = curr_total - prev_total;
|
||||
uint16_t tmp = 100 * (1 - delta_idle / delta_total);
|
||||
uint16_t tmp =
|
||||
(delta_total > 0) ? static_cast<uint16_t>(100 * (1 - delta_idle / delta_total)) : 0;
|
||||
tooltip = fmt::format("Total: {}%\nCores: (pending)", tmp);
|
||||
usage.push_back(tmp);
|
||||
} else {
|
||||
@@ -93,7 +103,8 @@ std::tuple<std::vector<uint16_t>, std::string> waybar::modules::CpuUsage::getCpu
|
||||
}
|
||||
const float delta_idle = curr_idle - prev_idle;
|
||||
const float delta_total = curr_total - prev_total;
|
||||
uint16_t tmp = 100 * (1 - delta_idle / delta_total);
|
||||
uint16_t tmp =
|
||||
(delta_total > 0) ? static_cast<uint16_t>(100 * (1 - delta_idle / delta_total)) : 0;
|
||||
if (i == 0) {
|
||||
tooltip = fmt::format("Total: {}%", tmp);
|
||||
} else {
|
||||
|
||||
+21
-6
@@ -2,6 +2,8 @@
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "util/scope_guard.hpp"
|
||||
|
||||
waybar::modules::Custom::Custom(const std::string& name, const std::string& id,
|
||||
@@ -37,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;
|
||||
@@ -136,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() {
|
||||
@@ -180,16 +189,22 @@ auto waybar::modules::Custom::update() -> void {
|
||||
} else {
|
||||
label_.set_markup(str);
|
||||
if (tooltipEnabled()) {
|
||||
std::string tooltip_markup;
|
||||
if (tooltip_format_enabled_) {
|
||||
auto tooltip = config_["tooltip-format"].asString();
|
||||
tooltip = fmt::format(
|
||||
fmt::runtime(tooltip), fmt::arg("text", text_), fmt::arg("alt", alt_),
|
||||
fmt::arg("icon", getIcon(percentage_, alt_)), fmt::arg("percentage", percentage_));
|
||||
label_.set_tooltip_markup(tooltip);
|
||||
tooltip_markup = fmt::format(fmt::runtime(tooltip), fmt::arg("text", text_),
|
||||
fmt::arg("tooltip", tooltip_), fmt::arg("alt", alt_),
|
||||
fmt::arg("icon", getIcon(percentage_, alt_)),
|
||||
fmt::arg("percentage", percentage_));
|
||||
} else if (text_ == tooltip_) {
|
||||
label_.set_tooltip_markup(str);
|
||||
tooltip_markup = str;
|
||||
} else {
|
||||
label_.set_tooltip_markup(tooltip_);
|
||||
tooltip_markup = tooltip_;
|
||||
}
|
||||
|
||||
if (last_tooltip_markup_ != tooltip_markup) {
|
||||
label_.set_tooltip_markup(tooltip_markup);
|
||||
last_tooltip_markup_ = std::move(tooltip_markup);
|
||||
}
|
||||
}
|
||||
auto style = label_.get_style_context();
|
||||
|
||||
@@ -41,7 +41,7 @@ auto waybar::modules::Disk::update() -> void {
|
||||
fs_used - File system used space
|
||||
*/
|
||||
|
||||
if (err != 0) {
|
||||
if (err != 0 || stats.f_blocks == 0) {
|
||||
event_box_.hide();
|
||||
return;
|
||||
}
|
||||
@@ -81,7 +81,7 @@ auto waybar::modules::Disk::update() -> void {
|
||||
if (config_["tooltip-format"].isString()) {
|
||||
tooltip_format = config_["tooltip-format"].asString();
|
||||
}
|
||||
label_.set_tooltip_text(fmt::format(
|
||||
label_.set_tooltip_markup(fmt::format(
|
||||
fmt::runtime(tooltip_format), stats.f_bavail * 100 / stats.f_blocks, fmt::arg("free", free),
|
||||
fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks), fmt::arg("used", used),
|
||||
fmt::arg("percentage_used", percentage_used), fmt::arg("total", total),
|
||||
@@ -92,7 +92,7 @@ auto waybar::modules::Disk::update() -> void {
|
||||
ALabel::update();
|
||||
}
|
||||
|
||||
float waybar::modules::Disk::calc_specific_divisor(std::string divisor) {
|
||||
float waybar::modules::Disk::calc_specific_divisor(const std::string& divisor) {
|
||||
if (divisor == "kB") {
|
||||
return 1000.0;
|
||||
} else if (divisor == "kiB") {
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
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 */
|
||||
}
|
||||
@@ -179,6 +195,7 @@ bool Tags::handle_button_press(GdkEventButton* event_button, uint32_t tag) {
|
||||
}
|
||||
|
||||
void Tags::handle_view_tags(uint32_t tag, uint32_t state, uint32_t clients, uint32_t focused) {
|
||||
if (tag >= buttons_.size()) return;
|
||||
// First clear all occupied state
|
||||
auto& button = buttons_[tag];
|
||||
if (clients) {
|
||||
|
||||
@@ -116,7 +116,7 @@ void Window::handle_frame() {
|
||||
updateAppIconName(appid_, "");
|
||||
updateAppIcon();
|
||||
if (tooltipEnabled()) {
|
||||
label_.set_tooltip_text(title_);
|
||||
label_.set_tooltip_markup(title_);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -143,7 +143,9 @@ void WorkspaceManager::handle_finished() {
|
||||
ext_manager_ = nullptr;
|
||||
}
|
||||
|
||||
void WorkspaceManager::commit() const { ext_workspace_manager_v1_commit(ext_manager_); }
|
||||
void WorkspaceManager::commit() const {
|
||||
if (ext_manager_) ext_workspace_manager_v1_commit(ext_manager_);
|
||||
}
|
||||
|
||||
void WorkspaceManager::update() {
|
||||
spdlog::debug("[ext/workspaces]: Updating state");
|
||||
|
||||
@@ -128,9 +128,9 @@ void Gamemode::getData() {
|
||||
Glib::VariantContainerBase data = gamemode_proxy->call_sync("Get", parameters);
|
||||
if (data && data.is_of_type(Glib::VariantType("(v)"))) {
|
||||
Glib::VariantBase variant;
|
||||
g_variant_get(data.gobj_copy(), "(v)", &variant);
|
||||
g_variant_get(const_cast<GVariant*>(data.gobj()), "(v)", &variant);
|
||||
if (variant && variant.is_of_type(Glib::VARIANT_TYPE_INT32)) {
|
||||
g_variant_get(variant.gobj_copy(), "i", &gameCount);
|
||||
g_variant_get(const_cast<GVariant*>(variant.gobj()), "i", &gameCount);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -158,7 +158,7 @@ void Gamemode::prepareForSleep_cb(const Glib::RefPtr<Gio::DBus::Connection>& con
|
||||
const Glib::VariantContainerBase& parameters) {
|
||||
if (parameters.is_of_type(Glib::VariantType("(b)"))) {
|
||||
gboolean sleeping;
|
||||
g_variant_get(parameters.gobj_copy(), "(b)", &sleeping);
|
||||
g_variant_get(const_cast<GVariant*>(parameters.gobj()), "(b)", &sleeping);
|
||||
if (!sleeping) {
|
||||
getData();
|
||||
dp.emit();
|
||||
@@ -212,7 +212,7 @@ auto Gamemode::update() -> void {
|
||||
// Tooltip
|
||||
if (tooltip) {
|
||||
std::string text = fmt::format(fmt::runtime(tooltip_format), fmt::arg("count", gameCount));
|
||||
box_.set_tooltip_text(text);
|
||||
box_.set_tooltip_markup(text);
|
||||
}
|
||||
|
||||
// Label format
|
||||
|
||||
@@ -9,22 +9,25 @@
|
||||
#include <sys/un.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <array>
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include "util/scoped_fd.hpp"
|
||||
|
||||
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;
|
||||
std::unique_lock lock(folderMutex);
|
||||
|
||||
// socket path, specified by EventManager of Hyprland
|
||||
if (!socketFolder_.empty()) {
|
||||
return socketFolder_;
|
||||
}
|
||||
|
||||
if (socketFolder_.empty()) {
|
||||
const char* xdgRuntimeDirEnv = std::getenv("XDG_RUNTIME_DIR");
|
||||
std::filesystem::path xdgRuntimeDir;
|
||||
// Only set path if env variable is set
|
||||
@@ -38,15 +41,15 @@ std::filesystem::path IPC::getSocketFolder(const char* instanceSig) {
|
||||
spdlog::warn("$XDG_RUNTIME_DIR/hypr does not exist, falling back to /tmp/hypr");
|
||||
socketFolder_ = std::filesystem::path("/tmp") / "hypr";
|
||||
}
|
||||
}
|
||||
|
||||
socketFolder_ = socketFolder_ / instanceSig;
|
||||
return socketFolder_;
|
||||
return socketFolder_ / instanceSig;
|
||||
}
|
||||
|
||||
IPC::IPC() {
|
||||
// will start IPC and relay events to parseIPC
|
||||
ipcThread_ = std::thread([this]() { socketListener(); });
|
||||
socketOwnerPid_ = getpid();
|
||||
ipcThread_ = std::thread([this]() { socketListener(); });
|
||||
}
|
||||
|
||||
IPC::~IPC() {
|
||||
@@ -54,20 +57,21 @@ IPC::~IPC() {
|
||||
// failed exec()) exits.
|
||||
if (getpid() != socketOwnerPid_) return;
|
||||
|
||||
running_ = false;
|
||||
running_.store(false, std::memory_order_relaxed);
|
||||
spdlog::info("Hyprland IPC stopping...");
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(socketMutex_);
|
||||
if (socketfd_ != -1) {
|
||||
spdlog::trace("Shutting down socket");
|
||||
if (shutdown(socketfd_, SHUT_RDWR) == -1) {
|
||||
if (shutdown(socketfd_, SHUT_RDWR) == -1 && errno != ENOTCONN) {
|
||||
spdlog::error("Hyprland IPC: Couldn't shutdown socket");
|
||||
}
|
||||
spdlog::trace("Closing socket");
|
||||
if (close(socketfd_) == -1) {
|
||||
spdlog::error("Hyprland IPC: Couldn't close socket");
|
||||
}
|
||||
}
|
||||
if (ipcThread_.joinable()) {
|
||||
ipcThread_.join();
|
||||
}
|
||||
}
|
||||
|
||||
IPC& IPC::inst() {
|
||||
static IPC ipc;
|
||||
@@ -85,10 +89,10 @@ void IPC::socketListener() {
|
||||
|
||||
spdlog::info("Hyprland IPC starting");
|
||||
|
||||
struct sockaddr_un addr;
|
||||
socketfd_ = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
struct sockaddr_un addr = {};
|
||||
const int socketfd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
|
||||
if (socketfd_ == -1) {
|
||||
if (socketfd == -1) {
|
||||
spdlog::error("Hyprland IPC: socketfd failed");
|
||||
return;
|
||||
}
|
||||
@@ -96,33 +100,57 @@ void IPC::socketListener() {
|
||||
addr.sun_family = AF_UNIX;
|
||||
|
||||
auto socketPath = IPC::getSocketFolder(his) / ".socket2.sock";
|
||||
if (socketPath.native().size() >= sizeof(addr.sun_path)) {
|
||||
spdlog::error("Hyprland IPC: Socket path is too long: {}", socketPath.string());
|
||||
close(socketfd);
|
||||
return;
|
||||
}
|
||||
strncpy(addr.sun_path, socketPath.c_str(), sizeof(addr.sun_path) - 1);
|
||||
|
||||
addr.sun_path[sizeof(addr.sun_path) - 1] = 0;
|
||||
|
||||
int l = sizeof(struct sockaddr_un);
|
||||
|
||||
if (connect(socketfd_, (struct sockaddr*)&addr, l) == -1) {
|
||||
spdlog::error("Hyprland IPC: Unable to connect?");
|
||||
if (connect(socketfd, (struct sockaddr*)&addr, l) == -1) {
|
||||
spdlog::error("Hyprland IPC: Unable to connect? {}", std::strerror(errno));
|
||||
close(socketfd);
|
||||
return;
|
||||
}
|
||||
auto* file = fdopen(socketfd_, "r");
|
||||
if (file == nullptr) {
|
||||
spdlog::error("Hyprland IPC: Couldn't open file descriptor");
|
||||
return;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(socketMutex_);
|
||||
socketfd_ = socketfd;
|
||||
}
|
||||
while (running_) {
|
||||
|
||||
std::string pending;
|
||||
while (running_.load(std::memory_order_relaxed)) {
|
||||
std::array<char, 1024> buffer; // Hyprland socket2 events are max 1024 bytes
|
||||
const ssize_t bytes_read = read(socketfd, buffer.data(), buffer.size());
|
||||
|
||||
auto* receivedCharPtr = fgets(buffer.data(), buffer.size(), file);
|
||||
if (bytes_read == 0) {
|
||||
if (running_.load(std::memory_order_relaxed)) {
|
||||
spdlog::warn("Hyprland IPC: Socket closed by peer");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (receivedCharPtr == nullptr) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
if (bytes_read < 0) {
|
||||
if (errno == EINTR) {
|
||||
continue;
|
||||
}
|
||||
if (!running_.load(std::memory_order_relaxed)) {
|
||||
break;
|
||||
}
|
||||
spdlog::error("Hyprland IPC: read failed: {}", std::strerror(errno));
|
||||
break;
|
||||
}
|
||||
|
||||
std::string messageReceived(buffer.data());
|
||||
messageReceived = messageReceived.substr(0, messageReceived.find_first_of('\n'));
|
||||
pending.append(buffer.data(), static_cast<std::size_t>(bytes_read));
|
||||
for (auto newline_pos = pending.find('\n'); newline_pos != std::string::npos;
|
||||
newline_pos = pending.find('\n')) {
|
||||
std::string messageReceived = pending.substr(0, newline_pos);
|
||||
pending.erase(0, newline_pos + 1);
|
||||
if (messageReceived.empty()) {
|
||||
continue;
|
||||
}
|
||||
spdlog::debug("hyprland IPC received {}", messageReceived);
|
||||
|
||||
try {
|
||||
@@ -132,8 +160,16 @@ void IPC::socketListener() {
|
||||
} catch (...) {
|
||||
throw;
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(socketMutex_);
|
||||
if (socketfd_ != -1) {
|
||||
if (close(socketfd_) == -1) {
|
||||
spdlog::error("Hyprland IPC: Couldn't close socket");
|
||||
}
|
||||
socketfd_ = -1;
|
||||
}
|
||||
}
|
||||
spdlog::debug("Hyprland IPC stopped");
|
||||
}
|
||||
@@ -178,7 +214,7 @@ void IPC::unregisterForIPC(EventHandler* ev_handler) {
|
||||
std::string IPC::getSocket1Reply(const std::string& rq) {
|
||||
// basically hyprctl
|
||||
|
||||
const auto serverSocket = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
util::ScopedFd serverSocket(socket(AF_UNIX, SOCK_STREAM, 0));
|
||||
|
||||
if (serverSocket < 0) {
|
||||
throw std::runtime_error("Hyprland IPC: Couldn't open a socket (1)");
|
||||
@@ -198,8 +234,10 @@ std::string IPC::getSocket1Reply(const std::string& rq) {
|
||||
std::string socketPath = IPC::getSocketFolder(instanceSig) / ".socket.sock";
|
||||
|
||||
// Use snprintf to copy the socketPath string into serverAddress.sun_path
|
||||
if (snprintf(serverAddress.sun_path, sizeof(serverAddress.sun_path), "%s", socketPath.c_str()) <
|
||||
0) {
|
||||
const auto socketPathLength =
|
||||
snprintf(serverAddress.sun_path, sizeof(serverAddress.sun_path), "%s", socketPath.c_str());
|
||||
if (socketPathLength < 0 ||
|
||||
socketPathLength >= static_cast<int>(sizeof(serverAddress.sun_path))) {
|
||||
throw std::runtime_error("Hyprland IPC: Couldn't copy socket path (6)");
|
||||
}
|
||||
|
||||
@@ -208,28 +246,39 @@ std::string IPC::getSocket1Reply(const std::string& rq) {
|
||||
throw std::runtime_error("Hyprland IPC: Couldn't connect to " + socketPath + ". (3)");
|
||||
}
|
||||
|
||||
auto sizeWritten = write(serverSocket, rq.c_str(), rq.length());
|
||||
std::size_t totalWritten = 0;
|
||||
while (totalWritten < rq.length()) {
|
||||
const auto sizeWritten =
|
||||
write(serverSocket, rq.c_str() + totalWritten, rq.length() - totalWritten);
|
||||
|
||||
if (sizeWritten < 0) {
|
||||
if (errno == EINTR) {
|
||||
continue;
|
||||
}
|
||||
spdlog::error("Hyprland IPC: Couldn't write (4)");
|
||||
return "";
|
||||
}
|
||||
if (sizeWritten == 0) {
|
||||
spdlog::error("Hyprland IPC: Socket write made no progress");
|
||||
return "";
|
||||
}
|
||||
totalWritten += static_cast<std::size_t>(sizeWritten);
|
||||
}
|
||||
|
||||
std::array<char, 8192> buffer = {0};
|
||||
std::string response;
|
||||
ssize_t sizeWritten = 0;
|
||||
|
||||
do {
|
||||
sizeWritten = read(serverSocket, buffer.data(), 8192);
|
||||
|
||||
if (sizeWritten < 0) {
|
||||
spdlog::error("Hyprland IPC: Couldn't read (5)");
|
||||
close(serverSocket);
|
||||
return "";
|
||||
}
|
||||
response.append(buffer.data(), sizeWritten);
|
||||
} while (sizeWritten > 0);
|
||||
|
||||
close(serverSocket);
|
||||
return response;
|
||||
}
|
||||
|
||||
@@ -243,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();
|
||||
}
|
||||
@@ -63,22 +90,43 @@ auto Language::update() -> void {
|
||||
|
||||
void Language::onEvent(const std::string& ev) {
|
||||
std::lock_guard<std::mutex> lg(mutex_);
|
||||
std::string kbName(begin(ev) + ev.find_last_of('>') + 1, begin(ev) + ev.find_first_of(','));
|
||||
|
||||
const auto payloadStart = ev.find(">>");
|
||||
if (payloadStart == std::string::npos) {
|
||||
spdlog::warn("hyprland language received malformed event: {}", ev);
|
||||
return;
|
||||
}
|
||||
const auto payload = ev.substr(payloadStart + 2);
|
||||
const auto kbSeparator = payload.find(',');
|
||||
if (kbSeparator == std::string::npos) {
|
||||
spdlog::warn("hyprland language received malformed event payload: {}", ev);
|
||||
return;
|
||||
}
|
||||
// Last comma before variants parenthesis, eg:
|
||||
// activelayout>>micro-star-int'l-co.,-ltd.-msi-gk50-elite-gaming-keyboard,English (US, intl.,
|
||||
// with dead keys)
|
||||
std::string beforeParenthesis;
|
||||
auto parenthesisPos = ev.find_last_of('(');
|
||||
auto parenthesisPos = payload.find_last_of('(');
|
||||
if (parenthesisPos == std::string::npos) {
|
||||
beforeParenthesis = ev;
|
||||
beforeParenthesis = payload;
|
||||
} else {
|
||||
beforeParenthesis = std::string(begin(ev), begin(ev) + parenthesisPos);
|
||||
beforeParenthesis = payload.substr(0, parenthesisPos);
|
||||
}
|
||||
auto layoutName = ev.substr(beforeParenthesis.find_last_of(',') + 1);
|
||||
const auto layoutSeparator = beforeParenthesis.find_last_of(',');
|
||||
if (layoutSeparator == std::string::npos) {
|
||||
spdlog::warn("hyprland language received malformed layout payload: {}", ev);
|
||||
return;
|
||||
}
|
||||
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);
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user