Merge branch 'Alexays:master' into master

pull/1722/head
Samuel Grahn 2022-11-15 15:59:42 +01:00 committed by GitHub
commit 37f8992649
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
95 changed files with 1878 additions and 584 deletions

View File

@ -21,7 +21,8 @@ jobs:
pkg install -y git # subprojects/date
pkg install -y catch evdev-proto gtk-layer-shell gtkmm30 jsoncpp \
libdbusmenu libevdev libfmt libmpdclient libudev-devd meson \
pkgconf pulseaudio scdoc sndio spdlog wayland-protocols upower
pkgconf pulseaudio scdoc sndio spdlog wayland-protocols upower \
libinotify
run: |
meson build -Dman-pages=enabled
ninja -C build

View File

@ -0,0 +1,35 @@
#pragma once
#include <glibmm/markup.h>
#include <gtkmm/button.h>
#include <gtkmm/cssprovider.h>
#include <gtkmm/label.h>
#include <json/json.h>
#include "AModule.hpp"
namespace waybar {
class AButton : public AModule {
public:
AButton(const Json::Value &, const std::string &, const std::string &, const std::string &format,
uint16_t interval = 0, bool ellipsize = false, bool enable_click = false,
bool enable_scroll = false);
virtual ~AButton() = default;
virtual auto update() -> void;
virtual std::string getIcon(uint16_t, const std::string &alt = "", uint16_t max = 0);
virtual std::string getIcon(uint16_t, const std::vector<std::string> &alts, uint16_t max = 0);
protected:
Gtk::Button button_ = Gtk::Button(name_);
Gtk::Label *label_ = (Gtk::Label *)button_.get_child();
std::string format_;
const std::chrono::seconds interval_;
bool alt_ = false;
std::string default_format_;
virtual bool handleToggle(GdkEventButton *const &e);
virtual std::string getState(uint8_t value, bool lesser = false);
};
} // namespace waybar

View File

@ -9,6 +9,7 @@
#ifdef HAVE_SWAY
#include "modules/sway/language.hpp"
#include "modules/sway/mode.hpp"
#include "modules/sway/scratchpad.hpp"
#include "modules/sway/window.hpp"
#include "modules/sway/workspaces.hpp"
#endif
@ -23,10 +24,10 @@
#endif
#ifdef HAVE_HYPRLAND
#include "modules/hyprland/backend.hpp"
#include "modules/hyprland/window.hpp"
#include "modules/hyprland/language.hpp"
#include "modules/hyprland/window.hpp"
#endif
#if defined(__linux__) && !defined(NO_FILESYSTEM)
#if defined(__FreeBSD__) || (defined(__linux__) && !defined(NO_FILESYSTEM))
#include "modules/battery.hpp"
#endif
#if defined(HAVE_CPU_LINUX) || defined(HAVE_CPU_BSD)
@ -75,6 +76,7 @@
#include "modules/custom.hpp"
#include "modules/custom_list.hpp"
#include "modules/temperature.hpp"
#include "modules/user.hpp"
namespace waybar {

View File

@ -5,7 +5,7 @@
#include <string_view>
#include <vector>
#include "ALabel.hpp"
#include "AButton.hpp"
#include "util/json.hpp"
#include "util/sleeper_thread.hpp"
@ -14,16 +14,18 @@ struct udev_device;
namespace waybar::modules {
class Backlight : public ALabel {
class Backlight : public AButton {
class BacklightDev {
public:
BacklightDev() = default;
BacklightDev(std::string name, int actual, int max);
BacklightDev(std::string name, int actual, int max, bool powered);
std::string_view name() const;
int get_actual() const;
void set_actual(int actual);
int get_max() const;
void set_max(int max);
bool get_powered() const;
void set_powered(bool powered);
friend inline bool operator==(const BacklightDev &lhs, const BacklightDev &rhs) {
return lhs.name_ == rhs.name_ && lhs.actual_ == rhs.actual_ && lhs.max_ == rhs.max_;
}
@ -32,6 +34,7 @@ class Backlight : public ALabel {
std::string name_;
int actual_ = 1;
int max_ = 1;
bool powered_ = true;
};
public:

View File

@ -6,14 +6,16 @@
#include <filesystem>
#endif
#include <fmt/format.h>
#if defined(__linux__)
#include <sys/inotify.h>
#endif
#include <algorithm>
#include <fstream>
#include <string>
#include <vector>
#include "ALabel.hpp"
#include "AButton.hpp"
#include "util/sleeper_thread.hpp"
namespace waybar::modules {
@ -24,7 +26,7 @@ namespace fs = std::experimental::filesystem;
namespace fs = std::filesystem;
#endif
class Battery : public ALabel {
class Battery : public AButton {
public:
Battery(const std::string&, const Json::Value&);
~Battery();

View File

@ -1,6 +1,6 @@
#pragma once
#include "ALabel.hpp"
#include "AButton.hpp"
#ifdef WANT_RFKILL
#include "util/rfkill.hpp"
#endif
@ -12,7 +12,7 @@
namespace waybar::modules {
class Bluetooth : public ALabel {
class Bluetooth : public AButton {
struct ControllerInfo {
std::string path;
std::string address;

View File

@ -2,7 +2,7 @@
#include <date/tz.h>
#include "ALabel.hpp"
#include "AButton.hpp"
#include "util/sleeper_thread.hpp"
namespace waybar {
@ -14,7 +14,7 @@ namespace modules {
const std::string kCalendarPlaceholder = "calendar";
const std::string KTimezonedTimeListPlaceholder = "timezoned_time_list";
class Clock : public ALabel {
class Clock : public AButton {
public:
Clock(const std::string&, const Json::Value&);
~Clock() = default;

View File

@ -9,12 +9,12 @@
#include <utility>
#include <vector>
#include "ALabel.hpp"
#include "AButton.hpp"
#include "util/sleeper_thread.hpp"
namespace waybar::modules {
class Cpu : public ALabel {
class Cpu : public AButton {
public:
Cpu(const std::string&, const Json::Value&);
~Cpu() = default;

View File

@ -5,14 +5,14 @@
#include <csignal>
#include <string>
#include "ALabel.hpp"
#include "AButton.hpp"
#include "util/command.hpp"
#include "util/json.hpp"
#include "util/sleeper_thread.hpp"
namespace waybar::modules {
class Custom : public ALabel {
class Custom : public AButton {
public:
Custom(const std::string&, const std::string&, const Json::Value&);
~Custom();

View File

@ -5,13 +5,13 @@
#include <fstream>
#include "ALabel.hpp"
#include "AButton.hpp"
#include "util/format.hpp"
#include "util/sleeper_thread.hpp"
namespace waybar::modules {
class Disk : public ALabel {
class Disk : public AButton {
public:
Disk(const std::string&, const Json::Value&);
~Disk() = default;

View File

@ -1,30 +1,36 @@
#pragma once
#include <string>
#include <list>
#include <functional>
#include <memory>
#include <mutex>
#include <deque>
#include <functional>
#include <string>
#include <thread>
namespace waybar::modules::hyprland {
class EventHandler {
public:
virtual void onEvent(const std::string& ev) = 0;
virtual ~EventHandler() = default;
};
class IPC {
public:
IPC() { startIPC(); }
void registerForIPC(const std::string&, std::function<void(const std::string&)>);
void registerForIPC(const std::string&, EventHandler*);
void unregisterForIPC(EventHandler*);
std::string getSocket1Reply(const std::string& rq);
private:
void startIPC();
void parseIPC(const std::string&);
std::mutex callbackMutex;
std::deque<std::pair<std::string, std::function<void(const std::string&)>>> callbacks;
std::list<std::pair<std::string, EventHandler*>> callbacks;
};
inline std::unique_ptr<IPC> gIPC;
inline bool modulesReady = false;
};
}; // namespace waybar::modules::hyprland

View File

@ -1,16 +1,17 @@
#include <fmt/format.h>
#include "ALabel.hpp"
#include "AButton.hpp"
#include "bar.hpp"
#include "modules/hyprland/backend.hpp"
#include "util/json.hpp"
namespace waybar::modules::hyprland {
class Language : public waybar::ALabel {
class Language : public waybar::AButton,
public EventHandler {
public:
Language(const std::string&, const waybar::Bar&, const Json::Value&);
~Language() = default;
~Language();
auto update() -> void;
@ -26,4 +27,4 @@ private:
std::string layoutName_;
};
}
} // namespace waybar::modules::hyprland

View File

@ -9,20 +9,24 @@
namespace waybar::modules::hyprland {
class Window : public waybar::ALabel {
class Window : public waybar::ALabel,
public EventHandler {
public:
Window(const std::string&, const waybar::Bar&, const Json::Value&);
~Window() = default;
~Window();
auto update() -> void;
private:
uint getActiveWorkspaceID(std::string);
std::string getLastWindowTitle(uint);
void onEvent(const std::string&);
bool separate_outputs;
std::mutex mutex_;
const Bar& bar_;
util::JsonParser parser_;
std::string lastView;
};
}
} // namespace waybar::modules::hyprland

View File

@ -2,13 +2,13 @@
#include <fmt/format.h>
#include "ALabel.hpp"
#include "AButton.hpp"
#include "bar.hpp"
#include "client.hpp"
namespace waybar::modules {
class IdleInhibitor : public ALabel {
class IdleInhibitor : public AButton {
sigc::connection timeout_;
public:
@ -20,6 +20,7 @@ class IdleInhibitor : public ALabel {
private:
bool handleToggle(GdkEventButton* const& e);
void toggleStatus();
const Bar& bar_;
struct zwp_idle_inhibitor_v1* idle_inhibitor_;

View File

@ -4,12 +4,12 @@
#include <memory>
#include "ALabel.hpp"
#include "AButton.hpp"
#include "bar.hpp"
namespace waybar::modules {
class Inhibitor : public ALabel {
class Inhibitor : public AButton {
public:
Inhibitor(const std::string&, const waybar::Bar&, const Json::Value&);
~Inhibitor() override;

View File

@ -1,9 +1,11 @@
#pragma once
#include <fmt/format.h>
#include <fstream>
#include <jack/jack.h>
#include <jack/thread.h>
#include <fstream>
#include "ALabel.hpp"
#include "util/sleeper_thread.hpp"

View File

@ -3,12 +3,15 @@
#include <fmt/chrono.h>
#include <gtkmm/label.h>
#include <unordered_map>
#include "AModule.hpp"
#include "bar.hpp"
#include "util/sleeper_thread.hpp"
extern "C" {
#include <libevdev/libevdev.h>
#include <libinput.h>
}
namespace waybar::modules {
@ -20,6 +23,8 @@ class KeyboardState : public AModule {
auto update() -> void;
private:
auto tryAddDevice(const std::string&) -> void;
Gtk::Box box_;
Gtk::Label numlock_label_;
Gtk::Label capslock_label_;
@ -31,11 +36,12 @@ class KeyboardState : public AModule {
const std::chrono::seconds interval_;
std::string icon_locked_;
std::string icon_unlocked_;
std::string devices_path_;
int fd_;
libevdev* dev_;
struct libinput* libinput_;
std::unordered_map<std::string, struct libinput_device*> libinput_devices_;
util::SleeperThread thread_;
util::SleeperThread libinput_thread_, hotplug_thread_;
};
} // namespace waybar::modules

View File

@ -5,12 +5,12 @@
#include <fstream>
#include <unordered_map>
#include "ALabel.hpp"
#include "AButton.hpp"
#include "util/sleeper_thread.hpp"
namespace waybar::modules {
class Memory : public ALabel {
class Memory : public AButton {
public:
Memory(const std::string&, const Json::Value&);
~Memory() = default;

View File

@ -7,12 +7,12 @@
#include <condition_variable>
#include <thread>
#include "ALabel.hpp"
#include "AButton.hpp"
#include "modules/mpd/state.hpp"
namespace waybar::modules {
class MPD : public ALabel {
class MPD : public AButton {
friend class detail::Context;
// State machine

View File

@ -7,7 +7,7 @@
#include <condition_variable>
#include <thread>
#include "ALabel.hpp"
#include "AButton.hpp"
namespace waybar::modules {
class MPD;

View File

@ -10,7 +10,7 @@
#include <optional>
#include "ALabel.hpp"
#include "AButton.hpp"
#include "util/sleeper_thread.hpp"
#ifdef WANT_RFKILL
#include "util/rfkill.hpp"
@ -18,7 +18,7 @@
namespace waybar::modules {
class Network : public ALabel {
class Network : public AButton {
public:
Network(const std::string&, const Json::Value&);
~Network();

View File

@ -7,11 +7,11 @@
#include <algorithm>
#include <array>
#include "ALabel.hpp"
#include "AButton.hpp"
namespace waybar::modules {
class Pulseaudio : public ALabel {
class Pulseaudio : public AButton {
public:
Pulseaudio(const std::string&, const Json::Value&);
~Pulseaudio();

View File

@ -2,12 +2,12 @@
#include <fmt/chrono.h>
#include "ALabel.hpp"
#include "AButton.hpp"
#include "util/sleeper_thread.hpp"
namespace waybar::modules {
class Clock : public ALabel {
class Clock : public AButton {
public:
Clock(const std::string&, const Json::Value&);
~Clock() = default;

View File

@ -4,12 +4,12 @@
#include <vector>
#include "ALabel.hpp"
#include "AButton.hpp"
#include "util/sleeper_thread.hpp"
namespace waybar::modules {
class Sndio : public ALabel {
class Sndio : public AButton {
public:
Sndio(const std::string &, const Json::Value &);
~Sndio();

View File

@ -6,7 +6,7 @@
#include <map>
#include <string>
#include "ALabel.hpp"
#include "AButton.hpp"
#include "bar.hpp"
#include "client.hpp"
#include "modules/sway/ipc/client.hpp"
@ -14,7 +14,7 @@
namespace waybar::modules::sway {
class Language : public ALabel, public sigc::trackable {
class Language : public AButton, public sigc::trackable {
public:
Language(const std::string& id, const Json::Value& config);
~Language() = default;

View File

@ -2,7 +2,7 @@
#include <fmt/format.h>
#include "ALabel.hpp"
#include "AButton.hpp"
#include "bar.hpp"
#include "client.hpp"
#include "modules/sway/ipc/client.hpp"
@ -10,7 +10,7 @@
namespace waybar::modules::sway {
class Mode : public ALabel, public sigc::trackable {
class Mode : public AButton, public sigc::trackable {
public:
Mode(const std::string&, const Json::Value&);
~Mode() = default;

View File

@ -0,0 +1,35 @@
#pragma once
#include <gtkmm/label.h>
#include <mutex>
#include <string>
#include "ALabel.hpp"
#include "bar.hpp"
#include "client.hpp"
#include "modules/sway/ipc/client.hpp"
#include "util/json.hpp"
namespace waybar::modules::sway {
class Scratchpad : public ALabel {
public:
Scratchpad(const std::string&, const Json::Value&);
~Scratchpad() = default;
auto update() -> void;
private:
auto getTree() -> void;
auto onCmd(const struct Ipc::ipc_response&) -> void;
auto onEvent(const struct Ipc::ipc_response&) -> void;
std::string tooltip_format_;
bool show_empty_;
bool tooltip_enabled_;
std::string tooltip_text_;
int count_;
std::mutex mutex_;
Ipc ipc_;
util::JsonParser parser_;
};
} // namespace waybar::modules::sway

View File

@ -24,7 +24,6 @@ class Window : public AIconLabel, public sigc::trackable {
std::tuple<std::size_t, int, std::string, std::string, std::string, std::string> getFocusedNode(
const Json::Value& nodes, std::string& output);
void getTree();
std::string rewriteTitle(const std::string& title);
void updateAppIconName();
void updateAppIcon();

View File

@ -4,12 +4,12 @@
#include <fstream>
#include "ALabel.hpp"
#include "AButton.hpp"
#include "util/sleeper_thread.hpp"
namespace waybar::modules {
class Temperature : public ALabel {
class Temperature : public AButton {
public:
Temperature(const std::string&, const Json::Value&);
~Temperature() = default;

View File

@ -0,0 +1,36 @@
#pragma once
#include <fmt/chrono.h>
#include <gdkmm/pixbuf.h>
#include <glibmm/refptr.h>
#include "AIconLabel.hpp"
#include "util/sleeper_thread.hpp"
namespace waybar::modules {
class User : public AIconLabel {
public:
User(const std::string&, const Json::Value&);
~User() = default;
auto update() -> void;
private:
util::SleeperThread thread_;
Glib::RefPtr<Gdk::Pixbuf> pixbuf_;
static constexpr inline int defaultUserImageWidth_ = 20;
static constexpr inline int defaultUserImageHeight_ = 20;
bool signal_label(GdkEventButton* button) const;
long uptime_as_seconds();
std::string get_user_login() const;
std::string get_user_home_dir() const;
std::string get_default_user_avatar_path() const;
void init_default_user_avatar(int width, int height);
void init_user_avatar(const std::string& path, int width, int height);
void init_avatar(const Json::Value& config);
void init_update_worker();
};
} // namespace waybar::modules

View File

@ -74,6 +74,10 @@ class Task {
std::string app_id_;
uint32_t state_ = 0;
int32_t drag_start_x;
int32_t drag_start_y;
int32_t drag_start_button = -1;
private:
std::string repr() const;
std::string state_string(bool = false) const;
@ -105,6 +109,12 @@ class Task {
/* Callbacks for Gtk events */
bool handle_clicked(GdkEventButton *);
bool handle_button_release(GdkEventButton *);
bool handle_motion_notify(GdkEventMotion *);
void handle_drag_data_get(const Glib::RefPtr<Gdk::DragContext> &context,
Gtk::SelectionData &selection_data, guint info, guint time);
void handle_drag_data_received(const Glib::RefPtr<Gdk::DragContext> &context, int x, int y,
Gtk::SelectionData selection_data, guint info, guint time);
public:
bool operator==(const Task &) const;

View File

@ -152,6 +152,7 @@ class WorkspaceManager : public AModule {
bool sort_by_name_ = true;
bool sort_by_coordinates_ = true;
bool sort_by_number_ = false;
bool all_outputs_ = false;
bool active_only_ = false;
bool creation_delayed_ = false;

View File

@ -56,8 +56,9 @@ struct formatter<pow_format> {
fraction /= base;
}
auto max_width = 4 // coeff in {:.3g} format
+ 1 // prefix from units array
auto number_width = 5 // coeff in {:.1f} format
+ s.binary_; // potential 4th digit before the decimal point
auto max_width = number_width + 1 // prefix from units array
+ s.binary_ // for the 'i' in GiB.
+ s.unit_.length();
@ -69,15 +70,16 @@ struct formatter<pow_format> {
case '<':
return format_to(ctx.out(), "{:<{}}", fmt::format("{}", s), max_width);
case '=':
format = "{coefficient:<4.3g}{padding}{prefix}{unit}";
format = "{coefficient:<{number_width}.1f}{padding}{prefix}{unit}";
break;
case 0:
default:
format = "{coefficient:.3g}{prefix}{unit}";
format = "{coefficient:.1f}{prefix}{unit}";
break;
}
return format_to(
ctx.out(), format, fmt::arg("coefficient", fraction),
fmt::arg("number_width", number_width),
fmt::arg("prefix", std::string() + units[pow] + ((s.binary_ && pow) ? "i" : "")),
fmt::arg("unit", s.unit_),
fmt::arg("padding", pow ? ""

View File

@ -0,0 +1,8 @@
#pragma once
#include <json/json.h>
#include <string>
namespace waybar::util {
std::string rewriteTitle(const std::string&, const Json::Value&);
}

View File

@ -0,0 +1,6 @@
#pragma once
#include <string>
namespace waybar::util {
std::string sanitize_string(std::string str);
} // namespace waybar::util

View File

@ -37,8 +37,8 @@ The *backlight* module displays the current backlight level.
Positive value to rotate the text label.
*states*: ++
typeof: array ++
A number of backlight states which get activated on certain brightness levels.
typeof: object ++
A number of backlight states which get activated on certain brightness levels. See *waybar-states(5)*.
*on-click*: ++
typeof: string ++

View File

@ -33,7 +33,7 @@ The *battery* module displays the current capacity and state (eg. charging) of y
The interval in which the information gets polled.
*states*: ++
typeof: array ++
typeof: object ++
A number of battery states which get activated on certain capacity levels. See *waybar-states(5)*.
*format*: ++

View File

@ -137,7 +137,7 @@ Addressed by *bluetooth*
*{device_alias}*: Alias of the displayed device.
*{device_enumerate}*: Show a list of all connected devices, each on a seperate line. Define the format of each device with the *tooltip-format-enumerate-connected* ++
*{device_enumerate}*: Show a list of all connected devices, each on a separate line. Define the format of each device with the *tooltip-format-enumerate-connected* ++
and/or *tooltip-format-enumerate-connected-battery* config options. Can only be used in the tooltip related format options.
# EXPERIMENTAL BATTERY PERCENTAGE FEATURE

View File

@ -42,7 +42,7 @@ The *cpu* module displays the current cpu utilization.
Positive value to rotate the text label.
*states*: ++
typeof: array ++
typeof: object ++
A number of cpu usage states which get activated on certain usage levels. See *waybar-states(5)*.
*on-click*: ++

View File

@ -32,7 +32,7 @@ Addressed by *disk*
Positive value to rotate the text label.
*states*: ++
typeof: array ++
typeof: object ++
A number of disk utilization states which get activated on certain percentage thresholds (percentage_used). See *waybar-states(5)*.
*max-length*: ++

View File

@ -23,7 +23,7 @@ Feral Gamemode optimizations.
*tooltip*: ++
typeof: bool ++
defualt: true ++
default: true ++
Option to disable tooltip on hover.
*tooltip-format*: ++

View File

@ -17,12 +17,31 @@ Addressed by *hyprland/window*
default: {} ++
The format, how information should be displayed. On {} the current window title is displayed.
*rewrite*: ++
typeof: object ++
Rules to rewrite window title. See *rewrite rules*.
# REWRITE RULES
*rewrite* is an object where keys are regular expressions and values are
rewrite rules if the expression matches. Rules may contain references to
captures of the expression.
Regular expression and replacement follow ECMA-script rules.
If no expression matches, the title is left unchanged.
Invalid expressions (e.g., mismatched parentheses) are skipped.
# EXAMPLES
```
"hyprland/window": {
"format": "{}"
"format": "{}",
"rewrite": {
"(.*) - Mozilla Firefox": "🌎 $1",
"(.*) - zsh": "> [$1]"
}
}
```

View File

@ -63,6 +63,11 @@ screensaving, also known as "presentation mode".
typeof: double ++
Threshold to be used when scrolling.
*start-activated*: ++
typeof: bool ++
default: *false* ++
Whether the inhibit should be activated when starting waybar.
*timeout*: ++
typeof: double ++
The number of minutes the inhibit should last.

View File

@ -6,7 +6,7 @@ waybar - inhibitor module
# DESCRIPTION
The *inhibitor* module allows to take an inhibitor lock that logind provides.
The *inhibitor* module allows one to take an inhibitor lock that logind provides.
See *systemd-inhibit*(1) for more information.
# CONFIGURATION

View File

@ -13,6 +13,7 @@ You must be a member of the input group to use this module.
# CONFIGURATION
*interval*: ++
Deprecated, this module use event loop now, the interval has no effect.
typeof: integer ++
default: 1 ++
The interval, in seconds, to poll the keyboard state.

View File

@ -32,7 +32,7 @@ Addressed by *memory*
Positive value to rotate the text label.
*states*: ++
typeof: array ++
typeof: object ++
A number of memory utilization states which get activated on certain percentage thresholds. See *waybar-states(5)*.
*max-length*: ++

View File

@ -43,8 +43,8 @@ Additionally you can control the volume by scrolling *up* or *down* while the cu
Positive value to rotate the text label.
*states*: ++
typeof: array ++
A number of volume states which get activated on certain volume levels. See *waybar-states(5)*
typeof: object ++
A number of volume states which get activated on certain volume levels. See *waybar-states(5)*.
*max-length*: ++
typeof: integer ++
@ -101,6 +101,10 @@ Additionally you can control the volume by scrolling *up* or *down* while the cu
default: 100 ++
The maximum volume that can be set, in percentage.
*ignored-sinks*: ++
typeof: array ++
Sinks in this list will not be shown as the active sink by Waybar. Entries should be the sink's description field.
# FORMAT REPLACEMENTS
*{desc}*: Pulseaudio port's description, for bluetooth it'll be the device name.

View File

@ -7,14 +7,13 @@ apply a class when the value matches the declared state value.
# STATES
- Every entry (*state*) consists of a *<name>* (typeof: *string*) and a *<value>* (typeof: *integer*).
Every entry (*state*) consists of a *<name>* (typeof: *string*) and a *<value>* (typeof: *integer*).
- The state can be addressed as a CSS class in the *style.css*. The name of the CSS class is the *<name>* of the state.
Each class gets activated when the current capacity is equal or below the configured *<value>*.
Each class gets activated when the current value is equal to or less than the configured *<value>* for the *battery* module, or equal to or greater than the configured *<value>* for all other modules.
- Also each state can have its own *format*.
Those can be configured via *format-<name>*.
Or if you want to differentiate a bit more even as *format-<status>-<state>*.
- Also, each state can have its own *format*.
Those can be configured via *format-<name>*, or if you want to differentiate a bit more, as *format-<status>-<state>*.
# EXAMPLE

View File

@ -0,0 +1,64 @@
waybar-sway-scratchpad(5)
# NAME
waybar - sway scratchpad module
# DESCRIPTION
The *scratchpad* module displays the scratchpad status in Sway
# CONFIGURATION
Addressed by *sway/scratchpad*
*format*: ++
typeof: string ++
default: {icon} {count} ++
The format, how information should be displayed.
*show-empty*: ++
typeof: bool ++
default: false ++
Option to show module when scratchpad is empty.
*format-icons*: ++
typeof: array/object ++
Based on the current scratchpad window counts, the corresponding icon gets selected.
*tooltip*: ++
typeof: bool ++
default: true ++
Option to disable tooltip on hover.
*tooltip-format*: ++
typeof: string ++
default: {app}: {title} ++
The format, how information in the tooltip should be displayed.
# FORMAT REPLACEMENTS
*{icon}*: Icon, as defined in *format-icons*.
*{count}*: Number of windows in the scratchpad.
*{app}*: Name of the application in the scratchpad.
*{title}*: Title of the application in the scratchpad.
# EXAMPLES
```
"sway/scratchpad": {
"format": "{icon} {count}",
"show-empty": false,
"format-icons": ["", ""],
"tooltip": true,
"tooltip-format": "{app}: {title}"
}
```
# STYLE
- *#scratchpad*
- *#scratchpad.empty*

View File

@ -33,7 +33,7 @@ compatible devices in the tooltip.
*tooltip*: ++
typeof: bool ++
defualt: true ++
default: true ++
Option to disable tooltip on hover.
*tooltip-spacing*: ++

View File

@ -33,6 +33,11 @@ Addressed by *wlr/workspaces*
Note that if both *sort-by-name* and *sort-by-coordinates* are true sort by name will be first.
If both are false - sort by id will be performed.
*sort-by-number*: ++
typeof: bool ++
default: false ++
If set to true, workspace names will be sorted numerically. Takes presedence over any other sort-by option.
*all-outputs*: ++
typeof: bool ++
default: false ++
@ -75,7 +80,8 @@ Additional to workspace name matching, the following *format-icons* can be set.
"5": "",
"focused": "",
"default": ""
}
},
"sort-by-number": true
}
```

View File

@ -273,6 +273,7 @@ A module group is defined by specifying a module named "group/some-group-name".
- *waybar-river-window(5)*
- *waybar-states(5)*
- *waybar-sway-mode(5)*
- *waybar-sway-scratchpad(5)*
- *waybar-sway-window(5)*
- *waybar-sway-workspaces(5)*
- *waybar-wlr-taskbar(5)*

View File

@ -1,6 +1,6 @@
project(
'waybar', 'cpp', 'c',
version: '0.9.13',
version: '0.9.15',
license: 'MIT',
meson_version: '>= 0.49.0',
default_options : [
@ -87,9 +87,11 @@ wayland_protos = dependency('wayland-protocols')
gtkmm = dependency('gtkmm-3.0', version : ['>=3.22.0'])
dbusmenu_gtk = dependency('dbusmenu-gtk3-0.4', required: get_option('dbusmenu-gtk'))
giounix = dependency('gio-unix-2.0', required: (get_option('dbusmenu-gtk').enabled() or get_option('logind').enabled() or get_option('upower_glib').enabled()))
jsoncpp = dependency('jsoncpp')
jsoncpp = dependency('jsoncpp', version : ['>=1.9.2'], fallback : ['jsoncpp', 'jsoncpp_dep'])
sigcpp = dependency('sigc++-2.0')
libinotify = dependency('libinotify', required: false)
libepoll = dependency('epoll-shim', required: false)
libinput = dependency('libinput', required: get_option('libinput'))
libnl = dependency('libnl-3.0', required: get_option('libnl'))
libnlgen = dependency('libnl-genl-3.0', required: get_option('libnl'))
upower_glib = dependency('upower-glib', required: get_option('upower_glib'))
@ -143,6 +145,7 @@ endif
src_files = files(
'src/factory.cpp',
'src/AModule.cpp',
'src/AButton.cpp',
'src/ALabel.cpp',
'src/AIconLabel.cpp',
'src/modules/custom.cpp',
@ -150,12 +153,15 @@ src_files = files(
'src/modules/disk.cpp',
'src/modules/idle_inhibitor.cpp',
'src/modules/temperature.cpp',
'src/modules/user.cpp',
'src/main.cpp',
'src/bar.cpp',
'src/client.cpp',
'src/config.cpp',
'src/group.cpp',
'src/util/ustring_clen.cpp'
'src/util/ustring_clen.cpp',
'src/util/sanitize_str.cpp',
'src/util/rewrite_title.cpp'
)
if is_linux
@ -177,6 +183,11 @@ elif is_dragonfly or is_freebsd or is_netbsd or is_openbsd
'src/modules/memory/bsd.cpp',
'src/modules/memory/common.cpp',
)
if is_freebsd
src_files += files(
'src/modules/battery.cpp',
)
endif
endif
add_project_arguments('-DHAVE_SWAY', language: 'cpp')
@ -186,7 +197,8 @@ src_files += [
'src/modules/sway/mode.cpp',
'src/modules/sway/language.cpp',
'src/modules/sway/window.cpp',
'src/modules/sway/workspaces.cpp'
'src/modules/sway/workspaces.cpp',
'src/modules/sway/scratchpad.cpp'
]
if true
@ -251,8 +263,9 @@ if libudev.found() and (is_linux or libepoll.found())
src_files += 'src/modules/backlight.cpp'
endif
if libevdev.found() and (is_linux or libepoll.found())
if libevdev.found() and (is_linux or libepoll.found()) and libinput.found() and (is_linux or libinotify.found())
add_project_arguments('-DHAVE_LIBEVDEV', language: 'cpp')
add_project_arguments('-DHAVE_LIBINPUT', language: 'cpp')
src_files += 'src/modules/keyboard_state.cpp'
endif
@ -312,12 +325,14 @@ executable(
gtkmm,
dbusmenu_gtk,
giounix,
libinput,
libnl,
libnlgen,
upower_glib,
libpulse,
libjack,
libudev,
libinotify,
libepoll,
libmpdclient,
libevdev,
@ -373,6 +388,7 @@ if scdoc.found()
'waybar-river-window.5.scd',
'waybar-sway-language.5.scd',
'waybar-sway-mode.5.scd',
'waybar-sway-scratchpad.5.scd',
'waybar-sway-window.5.scd',
'waybar-sway-workspaces.5.scd',
'waybar-temperature.5.scd',
@ -413,6 +429,7 @@ endif
catch2 = dependency(
'catch2',
version: '>=3.0.0',
fallback: ['catch2', 'catch2_dep'],
required: get_option('tests'),
)

View File

@ -1,4 +1,5 @@
option('libcxx', type : 'boolean', value : false, description : 'Build with Clang\'s libc++ instead of libstdc++ on Linux.')
option('libinput', type: 'feature', value: 'auto', description: 'Enable libinput support for libinput related features')
option('libnl', type: 'feature', value: 'auto', description: 'Enable libnl support for network related features')
option('libudev', type: 'feature', value: 'auto', description: 'Enable libudev support for udev related features')
option('libevdev', type: 'feature', value: 'auto', description: 'Enable libevdev support for evdev related features')

View File

@ -5,7 +5,7 @@
// "width": 1280, // Waybar width
"spacing": 4, // Gaps between modules (4px)
// Choose the order of the modules
"modules-left": ["sway/workspaces", "sway/mode", "custom/media"],
"modules-left": ["sway/workspaces", "sway/mode", "sway/scratchpad", "custom/media"],
"modules-center": ["sway/window"],
"modules-right": ["mpd", "idle_inhibitor", "pulseaudio", "network", "cpu", "memory", "temperature", "backlight", "keyboard-state", "sway/language", "battery", "battery#bat2", "clock", "tray"],
// Modules configuration
@ -36,6 +36,13 @@
"sway/mode": {
"format": "<span style=\"italic\">{}</span>"
},
"sway/scratchpad": {
"format": "{icon} {count}",
"show-empty": false,
"format-icons": ["", ""],
"tooltip": true,
"tooltip-format": "{app}: {title}"
},
"mpd": {
"format": "{stateIcon} {consumeIcon}{randomIcon}{repeatIcon}{singleIcon}{artist} - {album} - {title} ({elapsedTime:%M:%S}/{totalTime:%M:%S}) ⸨{songPosition}|{queueLength}⸩ {volume}% ",
"format-disconnected": "Disconnected ",

View File

@ -34,21 +34,28 @@ window#waybar.chromium {
border: none;
}
#workspaces button {
padding: 0 5px;
background-color: transparent;
color: #ffffff;
button {
/* Use box-shadow instead of border so the text isn't offset */
box-shadow: inset 0 -3px transparent;
/* Avoid rounded borders under each workspace name */
/* Avoid rounded borders under each button name */
border: none;
border-radius: 0;
}
/* https://github.com/Alexays/Waybar/wiki/FAQ#the-workspace-buttons-have-a-strange-hover-effect */
button:hover {
background: inherit;
box-shadow: inset 0 -3px #ffffff;
}
#workspaces button {
padding: 0 5px;
background-color: transparent;
color: #ffffff;
}
#workspaces button:hover {
background: rgba(0, 0, 0, 0.2);
box-shadow: inset 0 -3px #ffffff;
}
#workspaces button.focused {
@ -78,6 +85,7 @@ window#waybar.chromium {
#tray,
#mode,
#idle_inhibitor,
#scratchpad,
#mpd {
padding: 0 10px;
color: #ffffff;
@ -252,3 +260,11 @@ label:focus {
#keyboard-state > label.locked {
background: rgba(0, 0, 0, 0.2);
}
#scratchpad {
background: rgba(0, 0, 0, 0.2);
}
#scratchpad.empty {
background-color: transparent;
}

162
src/AButton.cpp 100644
View File

@ -0,0 +1,162 @@
#include "AButton.hpp"
#include <fmt/format.h>
#include <util/command.hpp>
namespace waybar {
AButton::AButton(const Json::Value& config, const std::string& name, const std::string& id,
const std::string& format, uint16_t interval, bool ellipsize, bool enable_click,
bool enable_scroll)
: AModule(config, name, id, config["format-alt"].isString() || enable_click, enable_scroll),
format_(config_["format"].isString() ? config_["format"].asString() : format),
interval_(config_["interval"] == "once"
? std::chrono::seconds(100000000)
: std::chrono::seconds(
config_["interval"].isUInt() ? config_["interval"].asUInt() : interval)),
default_format_(format_) {
button_.set_name(name);
button_.set_relief(Gtk::RELIEF_NONE);
/* https://github.com/Alexays/Waybar/issues/1731 */
auto css = Gtk::CssProvider::create();
css->load_from_data(
"button { all: unset; min-width: 0; } label:disabled,button:disabled { all: inherit; } label "
"{ all: unset; }");
button_.get_style_context()->add_provider(css, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
if (!id.empty()) {
button_.get_style_context()->add_class(id);
}
event_box_.add(button_);
if (config_["max-length"].isUInt()) {
label_->set_max_width_chars(config_["max-length"].asInt());
label_->set_ellipsize(Pango::EllipsizeMode::ELLIPSIZE_END);
label_->set_single_line_mode(true);
} else if (ellipsize && label_->get_max_width_chars() == -1) {
label_->set_ellipsize(Pango::EllipsizeMode::ELLIPSIZE_END);
label_->set_single_line_mode(true);
}
if (config_["min-length"].isUInt()) {
label_->set_width_chars(config_["min-length"].asUInt());
}
uint rotate = 0;
if (config_["rotate"].isUInt()) {
rotate = config["rotate"].asUInt();
label_->set_angle(rotate);
}
if (config_["align"].isDouble()) {
auto align = config_["align"].asFloat();
if (rotate == 90 || rotate == 270) {
label_->set_yalign(align);
} else {
label_->set_xalign(align);
}
}
if (!(config_["on-click"].isString() || config_["on-click-middle"].isString() ||
config_["on-click-backward"].isString() || config_["on-click-forward"].isString() ||
config_["on-click-right"].isString() || config_["format-alt"].isString() || enable_click)) {
button_.set_sensitive(false);
} else {
button_.signal_pressed().connect([this] {
GdkEventButton* e = (GdkEventButton*)gdk_event_new(GDK_BUTTON_PRESS);
e->button = 1;
handleToggle(e);
});
}
}
auto AButton::update() -> void { AModule::update(); }
std::string AButton::getIcon(uint16_t percentage, const std::string& alt, uint16_t max) {
auto format_icons = config_["format-icons"];
if (format_icons.isObject()) {
if (!alt.empty() && (format_icons[alt].isString() || format_icons[alt].isArray())) {
format_icons = format_icons[alt];
} else {
format_icons = format_icons["default"];
}
}
if (format_icons.isArray()) {
auto size = format_icons.size();
auto idx = std::clamp(percentage / ((max == 0 ? 100 : max) / size), 0U, size - 1);
format_icons = format_icons[idx];
}
if (format_icons.isString()) {
return format_icons.asString();
}
return "";
}
std::string AButton::getIcon(uint16_t percentage, const std::vector<std::string>& alts,
uint16_t max) {
auto format_icons = config_["format-icons"];
if (format_icons.isObject()) {
std::string _alt = "default";
for (const auto& alt : alts) {
if (!alt.empty() && (format_icons[alt].isString() || format_icons[alt].isArray())) {
_alt = alt;
break;
}
}
format_icons = format_icons[_alt];
}
if (format_icons.isArray()) {
auto size = format_icons.size();
auto idx = std::clamp(percentage / ((max == 0 ? 100 : max) / size), 0U, size - 1);
format_icons = format_icons[idx];
}
if (format_icons.isString()) {
return format_icons.asString();
}
return "";
}
bool waybar::AButton::handleToggle(GdkEventButton* const& e) {
if (config_["format-alt-click"].isUInt() && e->button == config_["format-alt-click"].asUInt()) {
alt_ = !alt_;
if (alt_ && config_["format-alt"].isString()) {
format_ = config_["format-alt"].asString();
} else {
format_ = default_format_;
}
}
return AModule::handleToggle(e);
}
std::string AButton::getState(uint8_t value, bool lesser) {
if (!config_["states"].isObject()) {
return "";
}
// Get current state
std::vector<std::pair<std::string, uint8_t>> states;
if (config_["states"].isObject()) {
for (auto it = config_["states"].begin(); it != config_["states"].end(); ++it) {
if (it->isUInt() && it.key().isString()) {
states.emplace_back(it.key().asString(), it->asUInt());
}
}
}
// Sort states
std::sort(states.begin(), states.end(), [&lesser](auto& a, auto& b) {
return lesser ? a.second < b.second : a.second > b.second;
});
std::string valid_state;
for (auto const& state : states) {
if ((lesser ? value <= state.second : value >= state.second) && valid_state.empty()) {
button_.get_style_context()->add_class(state.first);
valid_state = state.first;
} else {
button_.get_style_context()->remove_class(state.first);
}
}
return valid_state;
}
} // namespace waybar

View File

@ -7,7 +7,7 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
auto hash_pos = name.find('#');
auto ref = name.substr(0, hash_pos);
auto id = hash_pos != std::string::npos ? name.substr(hash_pos + 1) : "";
#if defined(__linux__) && !defined(NO_FILESYSTEM)
#if defined(__FreeBSD__) || (defined(__linux__) && !defined(NO_FILESYSTEM))
if (ref == "battery") {
return new waybar::modules::Battery(id, config_[name]);
}
@ -35,6 +35,9 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
if (ref == "sway/language") {
return new waybar::modules::sway::Language(id, config_[name]);
}
if (ref == "sway/scratchpad") {
return new waybar::modules::sway::Scratchpad(id, config_[name]);
}
#endif
#ifdef HAVE_WLR
if (ref == "wlr/taskbar") {
@ -81,6 +84,9 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
if (ref == "clock") {
return new waybar::modules::Clock(id, config_[name]);
}
if (ref == "user") {
return new waybar::modules::User(id, config_[name]);
}
if (ref == "disk") {
return new waybar::modules::Disk(id, config_[name]);
}

View File

@ -73,8 +73,9 @@ void check_nn(const void *ptr, const char *message = "ptr was null") {
}
} // namespace
waybar::modules::Backlight::BacklightDev::BacklightDev(std::string name, int actual, int max)
: name_(std::move(name)), actual_(actual), max_(max) {}
waybar::modules::Backlight::BacklightDev::BacklightDev(std::string name, int actual, int max,
bool powered)
: name_(std::move(name)), actual_(actual), max_(max), powered_(powered) {}
std::string_view waybar::modules::Backlight::BacklightDev::name() const { return name_; }
@ -86,8 +87,12 @@ int waybar::modules::Backlight::BacklightDev::get_max() const { return max_; }
void waybar::modules::Backlight::BacklightDev::set_max(int max) { max_ = max; }
bool waybar::modules::Backlight::BacklightDev::get_powered() const { return powered_; }
void waybar::modules::Backlight::BacklightDev::set_powered(bool powered) { powered_ = powered; }
waybar::modules::Backlight::Backlight(const std::string &id, const Json::Value &config)
: ALabel(config, "backlight", id, "{percent}%", 2),
: AButton(config, "backlight", id, "{percent}%", 2),
preferred_device_(config["device"].isString() ? config["device"].asString() : "") {
// Get initial state
{
@ -172,21 +177,26 @@ auto waybar::modules::Backlight::update() -> void {
return;
}
if (best->get_powered()) {
event_box_.show();
const uint8_t percent =
best->get_max() == 0 ? 100 : round(best->get_actual() * 100.0f / best->get_max());
label_.set_markup(fmt::format(format_, fmt::arg("percent", std::to_string(percent)),
label_->set_markup(fmt::format(format_, fmt::arg("percent", std::to_string(percent)),
fmt::arg("icon", getIcon(percent))));
getState(percent);
} else {
event_box_.hide();
}
} else {
if (!previous_best_.has_value()) {
return;
}
label_.set_markup("");
label_->set_markup("");
}
previous_best_ = best == nullptr ? std::nullopt : std::optional{*best};
previous_format_ = format_;
// Call parent update
ALabel::update();
AButton::update();
}
template <class ForwardIt>
@ -215,6 +225,7 @@ void waybar::modules::Backlight::upsert_device(ForwardIt first, ForwardIt last,
const char *actual = udev_device_get_sysattr_value(dev, actual_brightness_attr);
const char *max = udev_device_get_sysattr_value(dev, "max_brightness");
const char *power = udev_device_get_sysattr_value(dev, "bl_power");
auto found =
std::find_if(first, last, [name](const auto &device) { return device.name() == name; });
@ -225,10 +236,14 @@ void waybar::modules::Backlight::upsert_device(ForwardIt first, ForwardIt last,
if (max != nullptr) {
found->set_max(std::stoi(max));
}
if (power != nullptr) {
found->set_powered(std::stoi(power) == 0);
}
} else {
const int actual_int = actual == nullptr ? 0 : std::stoi(actual);
const int max_int = max == nullptr ? 0 : std::stoi(max);
*inserter = BacklightDev{name, actual_int, max_int};
const bool power_bool = power == nullptr ? true : std::stoi(power) == 0;
*inserter = BacklightDev{name, actual_int, max_int, power_bool};
++inserter;
}
}

View File

@ -1,9 +1,13 @@
#include "modules/battery.hpp"
#if defined(__FreeBSD__)
#include <sys/sysctl.h>
#endif
#include <spdlog/spdlog.h>
#include <iostream>
waybar::modules::Battery::Battery(const std::string& id, const Json::Value& config)
: ALabel(config, "battery", id, "{capacity}%", 60) {
: AButton(config, "battery", id, "{capacity}%", 60) {
#if defined(__linux__)
battery_watch_fd_ = inotify_init1(IN_CLOEXEC);
if (battery_watch_fd_ == -1) {
throw std::runtime_error("Unable to listen batteries.");
@ -19,11 +23,12 @@ waybar::modules::Battery::Battery(const std::string& id, const Json::Value& conf
if (global_watch < 0) {
throw std::runtime_error("Could not watch for battery plug/unplug");
}
#endif
worker();
}
waybar::modules::Battery::~Battery() {
#if defined(__linux__)
std::lock_guard<std::mutex> guard(battery_list_mutex_);
if (global_watch >= 0) {
@ -39,9 +44,16 @@ waybar::modules::Battery::~Battery() {
batteries_.erase(it);
}
close(battery_watch_fd_);
#endif
}
void waybar::modules::Battery::worker() {
#if defined(__FreeBSD__)
thread_timer_ = [this] {
dp.emit();
thread_timer_.sleep_for(interval_);
};
#else
thread_timer_ = [this] {
// Make sure we eventually update the list of batteries even if we miss an
// inotify event for some reason
@ -68,9 +80,11 @@ void waybar::modules::Battery::worker() {
refreshBatteries();
dp.emit();
};
#endif
}
void waybar::modules::Battery::refreshBatteries() {
#if defined(__linux__)
std::lock_guard<std::mutex> guard(battery_list_mutex_);
// Mark existing list of batteries as not necessarily found
std::map<fs::path, bool> check_map;
@ -108,7 +122,7 @@ void waybar::modules::Battery::refreshBatteries() {
}
auto adap_defined = config_["adapter"].isString();
if (((adap_defined && dir_name == config_["adapter"].asString()) || !adap_defined) &&
fs::exists(node.path() / "online")) {
(fs::exists(node.path() / "online") || fs::exists(node.path() / "status"))) {
adapter_ = node.path();
}
}
@ -135,6 +149,7 @@ void waybar::modules::Battery::refreshBatteries() {
batteries_.erase(check.first);
}
}
#endif
}
// Unknown > Full > Not charging > Discharging > Charging
@ -156,105 +171,35 @@ const std::tuple<uint8_t, float, std::string, float> waybar::modules::Battery::g
std::lock_guard<std::mutex> guard(battery_list_mutex_);
try {
uint32_t total_power = 0; // μW
uint32_t total_energy = 0; // μWh
uint32_t total_energy_full = 0;
uint32_t total_energy_full_design = 0;
uint32_t total_capacity{0};
std::string status = "Unknown";
for (auto const& item : batteries_) {
auto bat = item.first;
uint32_t power_now;
uint32_t energy_full;
uint32_t energy_now;
uint32_t energy_full_design;
uint32_t capacity{0};
std::string _status;
std::getline(std::ifstream(bat / "status"), _status);
// Some battery will report current and charge in μA/μAh.
// Scale these by the voltage to get μW/μWh.
if (fs::exists(bat / "current_now") || fs::exists(bat / "current_avg")) {
uint32_t voltage_now;
uint32_t current_now;
uint32_t charge_now;
uint32_t charge_full;
uint32_t charge_full_design;
// Some batteries have only *_avg, not *_now
if (fs::exists(bat / "voltage_now"))
std::ifstream(bat / "voltage_now") >> voltage_now;
else
std::ifstream(bat / "voltage_avg") >> voltage_now;
if (fs::exists(bat / "current_now"))
std::ifstream(bat / "current_now") >> current_now;
else
std::ifstream(bat / "current_avg") >> current_now;
std::ifstream(bat / "charge_full") >> charge_full;
std::ifstream(bat / "charge_full_design") >> charge_full_design;
if (fs::exists(bat / "charge_now"))
std::ifstream(bat / "charge_now") >> charge_now;
else {
// charge_now is missing on some systems, estimate using capacity.
uint32_t capacity;
std::ifstream(bat / "capacity") >> capacity;
charge_now = (capacity * charge_full) / 100;
}
power_now = ((uint64_t)current_now * (uint64_t)voltage_now) / 1000000;
energy_now = ((uint64_t)charge_now * (uint64_t)voltage_now) / 1000000;
energy_full = ((uint64_t)charge_full * (uint64_t)voltage_now) / 1000000;
energy_full_design = ((uint64_t)charge_full_design * (uint64_t)voltage_now) / 1000000;
} // Gamepads such as PS Dualshock provide the only capacity
else if (fs::exists(bat / "energy_now") && fs::exists(bat / "energy_full")) {
std::ifstream(bat / "power_now") >> power_now;
std::ifstream(bat / "energy_now") >> energy_now;
std::ifstream(bat / "energy_full") >> energy_full;
std::ifstream(bat / "energy_full_design") >> energy_full_design;
} else {
std::ifstream(bat / "capacity") >> capacity;
power_now = 0;
energy_now = 0;
energy_full = 0;
energy_full_design = 0;
#if defined(__FreeBSD__)
/* Allocate state of battery units reported via ACPI. */
int battery_units = 0;
size_t battery_units_size = sizeof battery_units;
if (sysctlbyname("hw.acpi.battery.units", &battery_units, &battery_units_size, NULL, 0) != 0) {
throw std::runtime_error("sysctl hw.acpi.battery.units failed");
}
// Show the "smallest" status among all batteries
if (status_gt(status, _status)) {
status = _status;
if (battery_units < 0) {
throw std::runtime_error("No battery units");
}
total_power += power_now;
total_energy += energy_now;
total_energy_full += energy_full;
total_energy_full_design += energy_full_design;
total_capacity += capacity;
int capacity;
size_t size_capacity = sizeof capacity;
if (sysctlbyname("hw.acpi.battery.life", &capacity, &size_capacity, NULL, 0) != 0) {
throw std::runtime_error("sysctl hw.acpi.battery.life failed");
}
if (!adapter_.empty() && status == "Discharging") {
bool online;
std::ifstream(adapter_ / "online") >> online;
if (online) {
status = "Plugged";
int time;
size_t size_time = sizeof time;
if (sysctlbyname("hw.acpi.battery.time", &time, &size_time, NULL, 0) != 0) {
throw std::runtime_error("sysctl hw.acpi.battery.time failed");
}
int rate;
size_t size_rate = sizeof rate;
if (sysctlbyname("hw.acpi.battery.rate", &rate, &size_rate, NULL, 0) != 0) {
throw std::runtime_error("sysctl hw.acpi.battery.rate failed");
}
float time_remaining = 0;
if (status == "Discharging" && total_power != 0) {
time_remaining = (float)total_energy / total_power;
} else if (status == "Charging" && total_power != 0) {
time_remaining = -(float)(total_energy_full - total_energy) / total_power;
if (time_remaining > 0.0f) {
// If we've turned positive it means the battery is past 100% and so
// just report that as no time remaining
time_remaining = 0.0f;
}
}
float capacity{0.0f};
if (total_energy_full > 0.0f) {
capacity = ((float)total_energy * 100.0f / (float)total_energy_full);
} else {
capacity = (float)total_capacity;
}
// Handle design-capacity
if (config_["design-capacity"].isBool() ? config_["design-capacity"].asBool() : false) {
capacity = ((float)total_energy * 100.0f / (float)total_energy_full_design);
}
auto status = getAdapterStatus(capacity);
// Handle full-at
if (config_["full-at"].isUInt()) {
auto full_at = config_["full-at"].asUInt();
@ -268,13 +213,319 @@ const std::tuple<uint8_t, float, std::string, float> waybar::modules::Battery::g
capacity = 100.f;
}
uint8_t cap = round(capacity);
if (cap == 100 && status == "Charging") {
if (cap == 100 && status == "Plugged") {
// If we've reached 100% just mark as full as some batteries can stay
// stuck reporting they're still charging but not yet done
status = "Full";
}
// spdlog::info("{} {} {} {}", capacity,time,status,rate);
return {capacity, time / 60.0, status, rate};
#elif defined(__linux__)
uint32_t total_power = 0; // μW
bool total_power_exists = false;
uint32_t total_energy = 0; // μWh
bool total_energy_exists = false;
uint32_t total_energy_full = 0;
bool total_energy_full_exists = false;
uint32_t total_energy_full_design = 0;
bool total_energy_full_design_exists = false;
uint32_t total_capacity = 0;
bool total_capacity_exists = false;
std::string status = "Unknown";
for (auto const& item : batteries_) {
auto bat = item.first;
std::string _status;
std::getline(std::ifstream(bat / "status"), _status);
// Some battery will report current and charge in μA/μAh.
// Scale these by the voltage to get μW/μWh.
uint32_t capacity = 0;
bool capacity_exists = false;
if (fs::exists(bat / "capacity")) {
capacity_exists = true;
std::ifstream(bat / "capacity") >> capacity;
}
uint32_t current_now = 0;
bool current_now_exists = false;
if (fs::exists(bat / "current_now")) {
current_now_exists = true;
std::ifstream(bat / "current_now") >> current_now;
} else if (fs::exists(bat / "current_avg")) {
current_now_exists = true;
std::ifstream(bat / "current_avg") >> current_now;
}
uint32_t voltage_now = 0;
bool voltage_now_exists = false;
if (fs::exists(bat / "voltage_now")) {
voltage_now_exists = true;
std::ifstream(bat / "voltage_now") >> voltage_now;
} else if (fs::exists(bat / "voltage_avg")) {
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")) {
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")) {
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")) {
charge_now_exists = true;
std::ifstream(bat / "charge_now") >> charge_now;
}
uint32_t power_now = 0;
bool power_now_exists = false;
if (fs::exists(bat / "power_now")) {
power_now_exists = true;
std::ifstream(bat / "power_now") >> power_now;
}
uint32_t energy_now = 0;
bool energy_now_exists = false;
if (fs::exists(bat / "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")) {
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")) {
energy_full_design_exists = true;
std::ifstream(bat / "energy_full_design") >> energy_full_design;
}
if (!voltage_now_exists) {
if (power_now_exists && current_now_exists && current_now != 0) {
voltage_now_exists = true;
voltage_now = 1000000 * (uint64_t)power_now / (uint64_t)current_now;
} else if (energy_full_design_exists && charge_full_design_exists &&
charge_full_design != 0) {
voltage_now_exists = true;
voltage_now = 1000000 * (uint64_t)energy_full_design / (uint64_t)charge_full_design;
} else if (energy_now_exists) {
if (charge_now_exists && charge_now != 0) {
voltage_now_exists = true;
voltage_now = 1000000 * (uint64_t)energy_now / (uint64_t)charge_now;
} else if (capacity_exists && charge_full_exists) {
charge_now_exists = true;
charge_now = (uint64_t)charge_full * (uint64_t)capacity / 100;
if (charge_full != 0 && capacity != 0) {
voltage_now_exists = true;
voltage_now =
1000000 * (uint64_t)energy_now * 100 / (uint64_t)charge_full / (uint64_t)capacity;
}
}
} else if (energy_full_exists) {
if (charge_full_exists && charge_full != 0) {
voltage_now_exists = true;
voltage_now = 1000000 * (uint64_t)energy_full / (uint64_t)charge_full;
} else if (charge_now_exists && capacity_exists) {
if (capacity != 0) {
charge_full_exists = true;
charge_full = 100 * (uint64_t)charge_now / (uint64_t)capacity;
}
if (charge_now != 0) {
voltage_now_exists = true;
voltage_now =
10000 * (uint64_t)energy_full * (uint64_t)capacity / (uint64_t)charge_now;
}
}
}
}
if (!capacity_exists) {
if (charge_now_exists && charge_full_exists && charge_full != 0) {
capacity_exists = true;
capacity = 100 * (uint64_t)charge_now / (uint64_t)charge_full;
} 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 (charge_now_exists && energy_full_exists && voltage_now_exists) {
if (!charge_full_exists && voltage_now != 0) {
charge_full_exists = true;
charge_full = 1000000 * (uint64_t)energy_full / (uint64_t)voltage_now;
}
if (energy_full != 0) {
capacity_exists = true;
capacity = (uint64_t)charge_now * (uint64_t)voltage_now / 10000 / (uint64_t)energy_full;
}
} else if (charge_full_exists && energy_now_exists && voltage_now_exists) {
if (!charge_now_exists && voltage_now != 0) {
charge_now_exists = true;
charge_now = 1000000 * (uint64_t)energy_now / (uint64_t)voltage_now;
}
if (voltage_now != 0 && charge_full != 0) {
capacity_exists = true;
capacity = 100 * 1000000 * (uint64_t)energy_now / (uint64_t)voltage_now /
(uint64_t)charge_full;
}
}
}
if (!energy_now_exists && voltage_now_exists) {
if (charge_now_exists) {
energy_now_exists = true;
energy_now = (uint64_t)charge_now * (uint64_t)voltage_now / 1000000;
} else if (capacity_exists && charge_full_exists) {
charge_now_exists = true;
charge_now = (uint64_t)capacity * (uint64_t)charge_full / 100;
energy_now_exists = true;
energy_now =
(uint64_t)voltage_now * (uint64_t)capacity * (uint64_t)charge_full / 1000000 / 100;
} else if (capacity_exists && energy_full) {
if (voltage_now != 0) {
charge_full_exists = true;
charge_full = 1000000 * (uint64_t)energy_full / (uint64_t)voltage_now;
charge_now_exists = true;
charge_now = (uint64_t)capacity * 10000 * (uint64_t)energy_full / (uint64_t)voltage_now;
}
energy_now_exists = true;
energy_now = (uint64_t)capacity * (uint64_t)energy_full / 100;
}
}
if (!energy_full_exists && voltage_now_exists) {
if (charge_full_exists) {
energy_full_exists = true;
energy_full = (uint64_t)charge_full * (uint64_t)voltage_now / 1000000;
} else if (charge_now_exists && capacity_exists && capacity != 0) {
charge_full_exists = true;
charge_full = 100 * (uint64_t)charge_now / (uint64_t)capacity;
energy_full_exists = true;
energy_full = (uint64_t)charge_now * (uint64_t)voltage_now / (uint64_t)capacity / 10000;
} else if (capacity_exists && energy_now) {
if (voltage_now != 0) {
charge_now_exists = true;
charge_now = 1000000 * (uint64_t)energy_now / (uint64_t)voltage_now;
}
if (capacity != 0) {
energy_full_exists = true;
energy_full = 100 * (uint64_t)energy_now / (uint64_t)capacity;
if (voltage_now != 0) {
charge_full_exists = true;
charge_full =
100 * 1000000 * (uint64_t)energy_now / (uint64_t)voltage_now / (uint64_t)capacity;
}
}
}
}
if (!power_now_exists && voltage_now_exists && current_now_exists) {
power_now_exists = true;
power_now = (uint64_t)voltage_now * (uint64_t)current_now / 1000000;
}
if (!energy_full_design_exists && voltage_now_exists && charge_full_design_exists) {
energy_full_design_exists = true;
energy_full_design = (uint64_t)voltage_now * (uint64_t)charge_full_design / 1000000;
}
// Show the "smallest" status among all batteries
if (status_gt(status, _status)) status = _status;
if (power_now_exists) {
total_power_exists = true;
total_power += power_now;
}
if (energy_now_exists) {
total_energy_exists = true;
total_energy += energy_now;
}
if (energy_full_exists) {
total_energy_full_exists = true;
total_energy_full += energy_full;
}
if (energy_full_design_exists) {
total_energy_full_design_exists = true;
total_energy_full_design += energy_full_design;
}
if (capacity_exists) {
total_capacity_exists = true;
total_capacity += capacity;
}
}
// Give `Plugged` higher priority over `Not charging`.
// So in a setting where TLP is used, `Plugged` is shown when the threshold is reached
if (!adapter_.empty() && (status == "Discharging" || status == "Not charging")) {
bool online;
std::string current_status;
std::ifstream(adapter_ / "online") >> online;
std::getline(std::ifstream(adapter_ / "status"), current_status);
if (online && current_status != "Discharging") status = "Plugged";
}
float time_remaining{0.0f};
if (status == "Discharging" && total_power_exists && total_energy_exists) {
if (total_power != 0) time_remaining = (float)total_energy / total_power;
} else if (status == "Charging" && total_energy_exists && total_energy_full_exists &&
total_power_exists) {
if (total_power != 0)
time_remaining = -(float)(total_energy_full - total_energy) / total_power;
// If we've turned positive it means the battery is past 100% and so just report that as no
// time remaining
if (time_remaining > 0.0f) time_remaining = 0.0f;
}
float calculated_capacity{0.0f};
if (total_capacity_exists) {
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)
calculated_capacity = ((float)total_energy * 100.0f / (float)total_energy_full);
}
}
// Handle design-capacity
if ((config_["design-capacity"].isBool() ? config_["design-capacity"].asBool() : false) &&
total_energy_exists && total_energy_full_design_exists) {
if (total_energy_full_design > 0.0f)
calculated_capacity = ((float)total_energy * 100.0f / (float)total_energy_full_design);
}
// Handle full-at
if (config_["full-at"].isUInt()) {
auto full_at = config_["full-at"].asUInt();
if (full_at < 100) calculated_capacity = 100.f * calculated_capacity / full_at;
}
// Handle it gracefully by clamping at 100%
// This can happen when the battery is calibrating and goes above 100%
if (calculated_capacity > 100.f) calculated_capacity = 100.f;
uint8_t cap = round(calculated_capacity);
// If we've reached 100% just mark as full as some batteries can stay stuck reporting they're
// still charging but not yet done
if (cap == 100 && status == "Charging") status = "Full";
return {cap, time_remaining, status, total_power / 1e6};
#endif
} catch (const std::exception& e) {
spdlog::error("Battery: {}", e.what());
return {0, 0, "Unknown", 0};
@ -282,13 +533,26 @@ const std::tuple<uint8_t, float, std::string, float> waybar::modules::Battery::g
}
const std::string waybar::modules::Battery::getAdapterStatus(uint8_t capacity) const {
#if defined(__FreeBSD__)
int state;
size_t size_state = sizeof state;
if (sysctlbyname("hw.acpi.battery.state", &state, &size_state, NULL, 0) != 0) {
throw std::runtime_error("sysctl hw.acpi.battery.state failed");
}
bool online = state == 2;
std::string status{"Unknown"}; // TODO: add status in FreeBSD
{
#else
if (!adapter_.empty()) {
bool online;
std::string status;
std::ifstream(adapter_ / "online") >> online;
std::getline(std::ifstream(adapter_ / "status"), status);
#endif
if (capacity == 100) {
return "Full";
}
if (online) {
if (online && status != "Discharging") {
return "Plugged";
}
return "Discharging";
@ -309,14 +573,17 @@ const std::string waybar::modules::Battery::formatTimeRemaining(float hoursRemai
format = config_["format-time"].asString();
}
std::string zero_pad_minutes = fmt::format("{:02d}", minutes);
return fmt::format(format, fmt::arg("H", full_hours), fmt::arg("M", minutes), fmt::arg("m", zero_pad_minutes));
return fmt::format(format, fmt::arg("H", full_hours), fmt::arg("M", minutes),
fmt::arg("m", zero_pad_minutes));
}
auto waybar::modules::Battery::update() -> void {
#if defined(__linux__)
if (batteries_.empty()) {
event_box_.hide();
return;
}
#endif
auto [capacity, time_remaining, status, power] = getInfos();
if (status == "Unknown") {
status = getAdapterStatus(capacity);
@ -346,14 +613,14 @@ auto waybar::modules::Battery::update() -> void {
} else if (config_["tooltip-format"].isString()) {
tooltip_format = config_["tooltip-format"].asString();
}
label_.set_tooltip_text(fmt::format(tooltip_format, fmt::arg("timeTo", tooltip_text_default),
fmt::arg("capacity", capacity),
button_.set_tooltip_text(fmt::format(tooltip_format, fmt::arg("timeTo", tooltip_text_default),
fmt::arg("power", power), fmt::arg("capacity", capacity),
fmt::arg("time", time_remaining_formatted)));
}
if (!old_status_.empty()) {
label_.get_style_context()->remove_class(old_status_);
button_.get_style_context()->remove_class(old_status_);
}
label_.get_style_context()->add_class(status);
button_.get_style_context()->add_class(status);
old_status_ = status;
if (!state.empty() && config_["format-" + status + "-" + state].isString()) {
format = config_["format-" + status + "-" + state].asString();
@ -367,10 +634,10 @@ auto waybar::modules::Battery::update() -> void {
} else {
event_box_.show();
auto icons = std::vector<std::string>{status + "-" + state, status, state};
label_.set_markup(fmt::format(format, fmt::arg("capacity", capacity), fmt::arg("power", power),
label_->set_markup(fmt::format(format, fmt::arg("capacity", capacity), fmt::arg("power", power),
fmt::arg("icon", getIcon(capacity, icons)),
fmt::arg("time", time_remaining_formatted)));
}
// Call parent update
ALabel::update();
AButton::update();
}

View File

@ -80,7 +80,7 @@ auto getUcharProperty(GDBusProxy* proxy, const char* property_name) -> unsigned
} // namespace
waybar::modules::Bluetooth::Bluetooth(const std::string& id, const Json::Value& config)
: ALabel(config, "bluetooth", id, " {status}", 10),
: AButton(config, "bluetooth", id, " {status}", 10),
#ifdef WANT_RFKILL
rfkill_{RFKILL_TYPE_BLUETOOTH},
#endif
@ -101,6 +101,7 @@ waybar::modules::Bluetooth::Bluetooth(const std::string& id, const Json::Value&
} else {
spdlog::error("findCurController() failed: no bluetooth controller found");
}
event_box_.hide();
return;
}
findConnectedDevices(cur_controller_.path, connected_devices_);
@ -189,10 +190,10 @@ auto waybar::modules::Bluetooth::update() -> void {
format_.empty() ? event_box_.hide() : event_box_.show();
auto update_style_context = [this](const std::string& style_class, bool in_next_state) {
if (in_next_state && !label_.get_style_context()->has_class(style_class)) {
label_.get_style_context()->add_class(style_class);
} else if (!in_next_state && label_.get_style_context()->has_class(style_class)) {
label_.get_style_context()->remove_class(style_class);
if (in_next_state && !button_.get_style_context()->has_class(style_class)) {
button_.get_style_context()->add_class(style_class);
} else if (!in_next_state && button_.get_style_context()->has_class(style_class)) {
button_.get_style_context()->remove_class(style_class);
}
};
update_style_context("discoverable", cur_controller_.discoverable);
@ -204,7 +205,7 @@ auto waybar::modules::Bluetooth::update() -> void {
update_style_context(state, true);
state_ = state;
label_.set_markup(fmt::format(
label_->set_markup(fmt::format(
format_, fmt::arg("status", state_), fmt::arg("num_connections", connected_devices_.size()),
fmt::arg("controller_address", cur_controller_.address),
fmt::arg("controller_address_type", cur_controller_.address_type),
@ -245,7 +246,7 @@ auto waybar::modules::Bluetooth::update() -> void {
device_enumerate_.erase(0, 1);
}
}
label_.set_tooltip_text(fmt::format(
button_.set_tooltip_text(fmt::format(
tooltip_format, fmt::arg("status", state_),
fmt::arg("num_connections", connected_devices_.size()),
fmt::arg("controller_address", cur_controller_.address),
@ -259,7 +260,7 @@ auto waybar::modules::Bluetooth::update() -> void {
}
// Call parent update
ALabel::update();
AButton::update();
}
// NOTE: only for when the org.bluez.Battery1 interface is added/removed after/before a device is

View File

@ -18,7 +18,7 @@
using waybar::waybar_time;
waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
: ALabel(config, "clock", id, "{:%H:%M}", 60, false, false, true),
: AButton(config, "clock", id, "{:%H:%M}", 60, false, false, true),
current_time_zone_idx_(0),
is_calendar_in_tooltip_(false),
is_timezoned_list_in_tooltip_(false) {
@ -107,7 +107,7 @@ auto waybar::modules::Clock::update() -> void {
} else {
text = fmt::format(format_, wtime);
}
label_.set_markup(text);
label_->set_markup(text);
if (tooltipEnabled()) {
if (config_["tooltip-format"].isString()) {
@ -119,12 +119,12 @@ auto waybar::modules::Clock::update() -> void {
text =
fmt::format(tooltip_format, wtime, fmt::arg(kCalendarPlaceholder.c_str(), calendar_lines),
fmt::arg(KTimezonedTimeListPlaceholder.c_str(), timezoned_time_lines));
label_.set_tooltip_markup(text);
button_.set_tooltip_markup(text);
}
}
// Call parent update
ALabel::update();
AButton::update();
}
bool waybar::modules::Clock::handleScroll(GdkEventScroll* e) {

View File

@ -10,7 +10,7 @@
#endif
waybar::modules::Cpu::Cpu(const std::string& id, const Json::Value& config)
: ALabel(config, "cpu", id, "{usage}%", 10) {
: AButton(config, "cpu", id, "{usage}%", 10) {
thread_ = [this] {
dp.emit();
thread_.sleep_for(interval_);
@ -23,7 +23,7 @@ auto waybar::modules::Cpu::update() -> void {
auto [cpu_usage, tooltip] = getCpuUsage();
auto [max_frequency, min_frequency, avg_frequency] = getCpuFrequency();
if (tooltipEnabled()) {
label_.set_tooltip_text(tooltip);
button_.set_tooltip_text(tooltip);
}
auto format = format_;
auto total_usage = cpu_usage.empty() ? 0 : cpu_usage[0];
@ -52,11 +52,11 @@ auto waybar::modules::Cpu::update() -> void {
auto icon_format = fmt::format("icon{}", core_i);
store.push_back(fmt::arg(icon_format.c_str(), getIcon(cpu_usage[i], icons)));
}
label_.set_markup(fmt::vformat(format, store));
label_->set_markup(fmt::vformat(format, store));
}
// Call parent update
ALabel::update();
AButton::update();
}
double waybar::modules::Cpu::getCpuLoad() {

View File

@ -4,7 +4,7 @@
waybar::modules::Custom::Custom(const std::string& name, const std::string& id,
const Json::Value& config)
: ALabel(config, "custom-" + name, id, "{}"),
: AButton(config, "custom-" + name, id, "{}"),
name_(name),
id_(id),
percentage_(0),
@ -103,13 +103,13 @@ void waybar::modules::Custom::handleEvent() {
}
bool waybar::modules::Custom::handleScroll(GdkEventScroll* e) {
auto ret = ALabel::handleScroll(e);
auto ret = AButton::handleScroll(e);
handleEvent();
return ret;
}
bool waybar::modules::Custom::handleToggle(GdkEventButton* const& e) {
auto ret = ALabel::handleToggle(e);
auto ret = AButton::handleToggle(e);
handleEvent();
return ret;
}
@ -131,31 +131,33 @@ auto waybar::modules::Custom::update() -> void {
if (str.empty()) {
event_box_.hide();
} else {
label_.set_markup(str);
label_->set_markup(str);
if (tooltipEnabled()) {
if (text_ == tooltip_) {
if (label_.get_tooltip_markup() != str) {
label_.set_tooltip_markup(str);
if (button_.get_tooltip_markup() != str) {
button_.set_tooltip_markup(str);
}
} else {
if (label_.get_tooltip_markup() != tooltip_) {
label_.set_tooltip_markup(tooltip_);
if (button_.get_tooltip_markup() != tooltip_) {
button_.set_tooltip_markup(tooltip_);
}
}
}
auto classes = label_.get_style_context()->list_classes();
auto classes = button_.get_style_context()->list_classes();
for (auto const& c : classes) {
if (c == id_) continue;
label_.get_style_context()->remove_class(c);
button_.get_style_context()->remove_class(c);
}
for (auto const& c : class_) {
label_.get_style_context()->add_class(c);
button_.get_style_context()->add_class(c);
}
button_.get_style_context()->add_class("flat");
button_.get_style_context()->add_class("text-button");
event_box_.show();
}
}
// Call parent update
ALabel::update();
AButton::update();
}
void waybar::modules::Custom::parseOutputRaw() {

View File

@ -3,7 +3,7 @@
using namespace waybar::util;
waybar::modules::Disk::Disk(const std::string& id, const Json::Value& config)
: ALabel(config, "disk", id, "{}%", 30), path_("/") {
: AButton(config, "disk", id, "{}%", 30), path_("/") {
thread_ = [this] {
dp.emit();
thread_.sleep_for(interval_);
@ -58,7 +58,7 @@ auto waybar::modules::Disk::update() -> void {
event_box_.hide();
} else {
event_box_.show();
label_.set_markup(
label_->set_markup(
fmt::format(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),
@ -70,12 +70,12 @@ auto waybar::modules::Disk::update() -> void {
if (config_["tooltip-format"].isString()) {
tooltip_format = config_["tooltip-format"].asString();
}
label_.set_tooltip_text(
button_.set_tooltip_text(
fmt::format(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), fmt::arg("path", path_)));
}
// Call parent update
ALabel::update();
AButton::update();
}

View File

@ -109,7 +109,7 @@ Gamemode::Gamemode(const std::string& id, const Json::Value& config)
}
Gamemode::~Gamemode() {
if (gamemode_proxy) gamemode_proxy->unreference();
if (gamemode_proxy) gamemode_proxy.reset();
if (gamemodeWatcher_id > 0) {
Gio::DBus::unwatch_name(gamemodeWatcher_id);
gamemodeWatcher_id = 0;

View File

@ -95,15 +95,37 @@ void IPC::parseIPC(const std::string& ev) {
for (auto& [eventname, handler] : callbacks) {
if (eventname == request) {
handler(ev);
handler->onEvent(ev);
}
}
}
void IPC::registerForIPC(const std::string& ev, std::function<void(const std::string&)> fn) {
void IPC::registerForIPC(const std::string& ev, EventHandler* ev_handler) {
if (!ev_handler) {
return;
}
callbackMutex.lock();
callbacks.emplace_back(std::make_pair(ev, fn));
callbacks.emplace_back(std::make_pair(ev, ev_handler));
callbackMutex.unlock();
}
void IPC::unregisterForIPC(EventHandler* ev_handler) {
if (!ev_handler) {
return;
}
callbackMutex.lock();
for (auto it = callbacks.begin(); it != callbacks.end();) {
auto it_current = it;
it++;
auto& [eventname, handler] = *it_current;
if (handler == ev_handler) {
callbacks.erase(it_current);
}
}
callbackMutex.unlock();
}

View File

@ -4,12 +4,14 @@
#include <xkbcommon/xkbcommon.h>
#include <xkbcommon/xkbregistry.h>
#include <util/sanitize_str.hpp>
#include "modules/hyprland/backend.hpp"
namespace waybar::modules::hyprland {
Language::Language(const std::string& id, const Bar& bar, const Json::Value& config)
: ALabel(config, "language", id, "{}", 0, true), bar_(bar) {
: AButton(config, "language", id, "{}", 0, true), bar_(bar) {
modulesReady = true;
if (!gIPC.get()) {
@ -19,24 +21,30 @@ Language::Language(const std::string& id, const Bar& bar, const Json::Value& con
// get the active layout when open
initLanguage();
label_.hide();
ALabel::update();
button_.hide();
AButton::update();
// register for hyprland ipc
gIPC->registerForIPC("activelayout", [&](const std::string& ev) { this->onEvent(ev); });
gIPC->registerForIPC("activelayout", this);
}
Language::~Language() {
gIPC->unregisterForIPC(this);
// wait for possible event handler to finish
std::lock_guard<std::mutex> lg(mutex_);
}
auto Language::update() -> void {
std::lock_guard<std::mutex> lg(mutex_);
if (!format_.empty()) {
label_.show();
label_.set_markup(layoutName_);
button_.show();
label_->set_markup(layoutName_);
} else {
label_.hide();
button_.hide();
}
ALabel::update();
AButton::update();
}
void Language::onEvent(const std::string& ev) {
@ -48,16 +56,6 @@ void Language::onEvent(const std::string& ev) {
if (config_.isMember("keyboard-name") && keebName != config_["keyboard-name"].asString())
return; // ignore
auto replaceAll = [](std::string str, const std::string& from,
const std::string& to) -> std::string {
size_t start_pos = 0;
while ((start_pos = str.find(from, start_pos)) != std::string::npos) {
str.replace(start_pos, from.length(), to);
start_pos += to.length();
}
return str;
};
const auto BRIEFNAME = getShortFrom(layoutName);
if (config_.isMember("format-" + BRIEFNAME)) {
@ -67,7 +65,7 @@ void Language::onEvent(const std::string& ev) {
layoutName = fmt::format(format_, layoutName);
}
layoutName = replaceAll(layoutName, "&", "&amp;");
layoutName = waybar::util::sanitize_string(layoutName);
if (layoutName == layoutName_) return;

View File

@ -2,13 +2,20 @@
#include <spdlog/spdlog.h>
#include <regex>
#include <util/sanitize_str.hpp>
#include "modules/hyprland/backend.hpp"
#include "util/command.hpp"
#include "util/json.hpp"
#include "util/rewrite_title.hpp"
namespace waybar::modules::hyprland {
Window::Window(const std::string& id, const Bar& bar, const Json::Value& config)
: ALabel(config, "window", id, "{}", 0, true), bar_(bar) {
modulesReady = true;
separate_outputs = config["separate-outputs"].as<bool>();
if (!gIPC.get()) {
gIPC = std::make_unique<IPC>();
@ -18,7 +25,13 @@ Window::Window(const std::string& id, const Bar& bar, const Json::Value& config)
ALabel::update();
// register for hyprland ipc
gIPC->registerForIPC("activewindow", [&](const std::string& ev) { this->onEvent(ev); });
gIPC->registerForIPC("activewindow", this);
}
Window::~Window() {
gIPC->unregisterForIPC(this);
// wait for possible event handler to finish
std::lock_guard<std::mutex> lg(mutex_);
}
auto Window::update() -> void {
@ -27,7 +40,8 @@ auto Window::update() -> void {
if (!format_.empty()) {
label_.show();
label_.set_markup(fmt::format(format_, lastView));
label_.set_markup(
fmt::format(format_, waybar::util::rewriteTitle(lastView, config_["rewrite"])));
} else {
label_.hide();
}
@ -35,21 +49,45 @@ auto Window::update() -> void {
ALabel::update();
}
uint Window::getActiveWorkspaceID(std::string monitorName) {
auto cmd = waybar::util::command::exec("hyprctl monitors -j");
assert(cmd.exit_code == 0);
Json::Value json = parser_.parse(cmd.out);
assert(json.isArray());
auto monitor = std::find_if(json.begin(), json.end(),
[&](Json::Value monitor) { return monitor["name"] == monitorName; });
if (monitor == std::end(json)) {
return 0;
}
return (*monitor)["activeWorkspace"]["id"].as<uint>();
}
std::string Window::getLastWindowTitle(uint workspaceID) {
auto cmd = waybar::util::command::exec("hyprctl workspaces -j");
assert(cmd.exit_code == 0);
Json::Value json = parser_.parse(cmd.out);
assert(json.isArray());
auto workspace = std::find_if(json.begin(), json.end(), [&](Json::Value workspace) {
return workspace["id"].as<uint>() == workspaceID;
});
if (workspace == std::end(json)) {
return "";
}
return (*workspace)["lastwindowtitle"].as<std::string>();
}
void Window::onEvent(const std::string& ev) {
std::lock_guard<std::mutex> lg(mutex_);
auto windowName = ev.substr(ev.find_first_of(',') + 1).substr(0, 256);
auto replaceAll = [](std::string str, const std::string& from,
const std::string& to) -> std::string {
size_t start_pos = 0;
while ((start_pos = str.find(from, start_pos)) != std::string::npos) {
str.replace(start_pos, from.length(), to);
start_pos += to.length();
std::string windowName;
if (separate_outputs) {
windowName = getLastWindowTitle(getActiveWorkspaceID(this->bar_.output->name));
} else {
windowName = ev.substr(ev.find_first_of(',') + 1).substr(0, 256);
}
return str;
};
windowName = replaceAll(windowName, "&", "&amp;");
windowName = waybar::util::sanitize_string(windowName);
if (windowName == lastView) return;
@ -59,5 +97,4 @@ void Window::onEvent(const std::string& ev) {
dp.emit();
}
} // namespace waybar::modules::hyprland

View File

@ -8,7 +8,7 @@ bool waybar::modules::IdleInhibitor::status = false;
waybar::modules::IdleInhibitor::IdleInhibitor(const std::string& id, const Bar& bar,
const Json::Value& config)
: ALabel(config, "idle_inhibitor", id, "{status}"),
: AButton(config, "idle_inhibitor", id, "{status}", 0, false, true),
bar_(bar),
idle_inhibitor_(nullptr),
pid_(-1) {
@ -16,6 +16,11 @@ waybar::modules::IdleInhibitor::IdleInhibitor(const std::string& id, const Bar&
throw std::runtime_error("idle-inhibit not available");
}
if (waybar::modules::IdleInhibitor::modules.empty() && config_["start-activated"].isBool() &&
config_["start-activated"].asBool() != status) {
toggleStatus();
}
event_box_.add_events(Gdk::BUTTON_PRESS_MASK);
event_box_.signal_button_press_event().connect(
sigc::mem_fun(*this, &IdleInhibitor::handleToggle));
@ -44,13 +49,13 @@ waybar::modules::IdleInhibitor::~IdleInhibitor() {
auto waybar::modules::IdleInhibitor::update() -> void {
// Check status
if (status) {
label_.get_style_context()->remove_class("deactivated");
button_.get_style_context()->remove_class("deactivated");
if (idle_inhibitor_ == nullptr) {
idle_inhibitor_ = zwp_idle_inhibit_manager_v1_create_inhibitor(
waybar::Client::inst()->idle_inhibit_manager, bar_.surface);
}
} else {
label_.get_style_context()->remove_class("activated");
button_.get_style_context()->remove_class("activated");
if (idle_inhibitor_ != nullptr) {
zwp_idle_inhibitor_v1_destroy(idle_inhibitor_);
idle_inhibitor_ = nullptr;
@ -58,11 +63,11 @@ auto waybar::modules::IdleInhibitor::update() -> void {
}
std::string status_text = status ? "activated" : "deactivated";
label_.set_markup(fmt::format(format_, fmt::arg("status", status_text),
label_->set_markup(fmt::format(format_, fmt::arg("status", status_text),
fmt::arg("icon", getIcon(0, status_text))));
label_.get_style_context()->add_class(status_text);
button_.get_style_context()->add_class(status_text);
if (tooltipEnabled()) {
label_.set_tooltip_markup(
button_.set_tooltip_markup(
status ? fmt::format(config_["tooltip-format-activated"].isString()
? config_["tooltip-format-activated"].asString()
: "{status}",
@ -75,11 +80,10 @@ auto waybar::modules::IdleInhibitor::update() -> void {
fmt::arg("icon", getIcon(0, status_text))));
}
// Call parent update
ALabel::update();
AButton::update();
}
bool waybar::modules::IdleInhibitor::handleToggle(GdkEventButton* const& e) {
if (e->button == 1) {
void waybar::modules::IdleInhibitor::toggleStatus() {
status = !status;
if (timeout_.connected()) {
@ -106,6 +110,11 @@ bool waybar::modules::IdleInhibitor::handleToggle(GdkEventButton* const& e) {
},
timeoutSecs);
}
}
bool waybar::modules::IdleInhibitor::handleToggle(GdkEventButton* const& e) {
if (e->button == 1) {
toggleStatus();
// Make all other idle inhibitor modules update
for (auto const& module : waybar::modules::IdleInhibitor::modules) {
@ -115,6 +124,6 @@ bool waybar::modules::IdleInhibitor::handleToggle(GdkEventButton* const& e) {
}
}
ALabel::handleToggle(e);
AButton::handleToggle(e);
return true;
}

View File

@ -98,7 +98,7 @@ auto getInhibitors(const Json::Value& config) -> std::string {
namespace waybar::modules {
Inhibitor::Inhibitor(const std::string& id, const Bar& bar, const Json::Value& config)
: ALabel(config, "inhibitor", id, "{status}", true),
: AButton(config, "inhibitor", id, "{status}", true),
dbus_(::dbus()),
inhibitors_(::getInhibitors(config)) {
event_box_.add_events(Gdk::BUTTON_PRESS_MASK);
@ -117,16 +117,16 @@ auto Inhibitor::activated() -> bool { return handle_ != -1; }
auto Inhibitor::update() -> void {
std::string status_text = activated() ? "activated" : "deactivated";
label_.get_style_context()->remove_class(activated() ? "deactivated" : "activated");
label_.set_markup(fmt::format(format_, fmt::arg("status", status_text),
button_.get_style_context()->remove_class(activated() ? "deactivated" : "activated");
label_->set_markup(fmt::format(format_, fmt::arg("status", status_text),
fmt::arg("icon", getIcon(0, status_text))));
label_.get_style_context()->add_class(status_text);
button_.get_style_context()->add_class(status_text);
if (tooltipEnabled()) {
label_.set_tooltip_text(status_text);
button_.set_tooltip_text(status_text);
}
return ALabel::update();
return AButton::update();
}
auto Inhibitor::handleToggle(GdkEventButton* const& e) -> bool {
@ -142,7 +142,7 @@ auto Inhibitor::handleToggle(GdkEventButton* const& e) -> bool {
}
}
return ALabel::handleToggle(e);
return AButton::handleToggle(e);
}
} // namespace waybar::modules

View File

@ -8,8 +8,13 @@
extern "C" {
#include <fcntl.h>
#include <libinput.h>
#include <linux/input-event-codes.h>
#include <poll.h>
#include <sys/inotify.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
}
class errno_error : public std::runtime_error {
@ -99,8 +104,18 @@ waybar::modules::KeyboardState::KeyboardState(const std::string& id, const Bar&
icon_unlocked_(config_["format-icons"]["unlocked"].isString()
? config_["format-icons"]["unlocked"].asString()
: "unlocked"),
fd_(0),
dev_(nullptr) {
devices_path_("/dev/input/"),
libinput_(nullptr),
libinput_devices_({}) {
static struct libinput_interface interface = {
[](const char* path, int flags, void* user_data) { return open(path, flags); },
[](int fd, void* user_data) { close(fd); }};
if (config_["interval"].isUInt()) {
spdlog::warn("keyboard-state: interval is deprecated");
}
libinput_ = libinput_path_create_context(&interface, NULL);
box_.set_name("keyboard-state");
if (config_["numlock"].asBool()) {
numlock_label_.get_style_context()->add_class("numlock");
@ -121,70 +136,135 @@ waybar::modules::KeyboardState::KeyboardState(const std::string& id, const Bar&
if (config_["device-path"].isString()) {
std::string dev_path = config_["device-path"].asString();
fd_ = openFile(dev_path, O_NONBLOCK | O_CLOEXEC | O_RDONLY);
dev_ = openDevice(fd_);
} else {
DIR* dev_dir = opendir("/dev/input");
tryAddDevice(dev_path);
if (libinput_devices_.empty()) {
spdlog::error("keyboard-state: Cannot find device {}", dev_path);
}
}
DIR* dev_dir = opendir(devices_path_.c_str());
if (dev_dir == nullptr) {
throw errno_error(errno, "Failed to open /dev/input");
throw errno_error(errno, "Failed to open " + devices_path_);
}
dirent* ep;
while ((ep = readdir(dev_dir))) {
if (ep->d_type != DT_CHR) continue;
std::string dev_path = std::string("/dev/input/") + ep->d_name;
int fd = openFile(dev_path.c_str(), O_NONBLOCK | O_CLOEXEC | O_RDONLY);
try {
auto dev = openDevice(fd);
if (supportsLockStates(dev)) {
spdlog::info("Found device {} at '{}'", libevdev_get_name(dev), dev_path);
fd_ = fd;
dev_ = dev;
if (ep->d_type == DT_DIR) continue;
std::string dev_path = devices_path_ + ep->d_name;
tryAddDevice(dev_path);
}
if (libinput_devices_.empty()) {
throw errno_error(errno, "Failed to find keyboard device");
}
libinput_thread_ = [this] {
dp.emit();
while (1) {
struct pollfd fd = {libinput_get_fd(libinput_), POLLIN, 0};
poll(&fd, 1, -1);
libinput_dispatch(libinput_);
struct libinput_event* event;
while ((event = libinput_get_event(libinput_))) {
auto type = libinput_event_get_type(event);
if (type == LIBINPUT_EVENT_KEYBOARD_KEY) {
auto keyboard_event = libinput_event_get_keyboard_event(event);
auto state = libinput_event_keyboard_get_key_state(keyboard_event);
if (state == LIBINPUT_KEY_STATE_RELEASED) {
uint32_t key = libinput_event_keyboard_get_key(keyboard_event);
switch (key) {
case KEY_CAPSLOCK:
case KEY_NUMLOCK:
case KEY_SCROLLLOCK:
dp.emit();
break;
default:
break;
}
}
}
libinput_event_destroy(event);
}
}
};
hotplug_thread_ = [this] {
int fd;
fd = inotify_init();
if (fd < 0) {
spdlog::error("Failed to initialize inotify: {}", strerror(errno));
return;
}
inotify_add_watch(fd, devices_path_.c_str(), IN_CREATE | IN_DELETE);
while (1) {
int BUF_LEN = 1024 * (sizeof(struct inotify_event) + 16);
char buf[BUF_LEN];
int length = read(fd, buf, 1024);
if (length < 0) {
spdlog::error("Failed to read inotify: {}", strerror(errno));
return;
}
for (int i = 0; i < length;) {
struct inotify_event* event = (struct inotify_event*)&buf[i];
std::string dev_path = devices_path_ + event->name;
if (event->mask & IN_CREATE) {
// Wait for device setup
int timeout = 10;
while (timeout--) {
try {
int fd = openFile(dev_path, O_NONBLOCK | O_CLOEXEC | O_RDONLY);
closeFile(fd);
break;
} catch (const errno_error& e) {
if (e.code == EACCES) {
sleep(1);
}
}
}
tryAddDevice(dev_path);
} else if (event->mask & IN_DELETE) {
auto it = libinput_devices_.find(dev_path);
if (it != libinput_devices_.end()) {
spdlog::info("Keyboard {} has been removed.", dev_path);
libinput_devices_.erase(it);
}
}
i += sizeof(struct inotify_event) + event->len;
}
}
};
}
waybar::modules::KeyboardState::~KeyboardState() {
for (const auto& [_, dev_ptr] : libinput_devices_) {
libinput_path_remove_device(dev_ptr);
}
}
auto waybar::modules::KeyboardState::update() -> void {
sleep(0); // Wait for keyboard status change
int numl = 0, capsl = 0, scrolll = 0;
try {
std::string dev_path;
if (config_["device-path"].isString() &&
libinput_devices_.find(config_["device-path"].asString()) != libinput_devices_.end()) {
dev_path = config_["device-path"].asString();
} else {
dev_path = libinput_devices_.begin()->first;
}
int fd = openFile(dev_path, O_NONBLOCK | O_CLOEXEC | O_RDONLY);
auto dev = openDevice(fd);
numl = libevdev_get_event_value(dev, EV_LED, LED_NUML);
capsl = libevdev_get_event_value(dev, EV_LED, LED_CAPSL);
scrolll = libevdev_get_event_value(dev, EV_LED, LED_SCROLLL);
libevdev_free(dev);
closeFile(fd);
} catch (const errno_error& e) {
// ENOTTY just means the device isn't an evdev device, skip it
if (e.code != ENOTTY) {
spdlog::warn(e.what());
}
}
closeFile(fd);
}
if (dev_ == nullptr) {
throw errno_error(errno, "Failed to find keyboard device");
}
}
thread_ = [this] {
dp.emit();
thread_.sleep_for(interval_);
};
}
waybar::modules::KeyboardState::~KeyboardState() {
libevdev_free(dev_);
try {
closeFile(fd_);
} catch (const std::runtime_error& e) {
spdlog::warn(e.what());
}
}
auto waybar::modules::KeyboardState::update() -> void {
int err = LIBEVDEV_READ_STATUS_SUCCESS;
while (err == LIBEVDEV_READ_STATUS_SUCCESS) {
input_event ev;
err = libevdev_next_event(dev_, LIBEVDEV_READ_FLAG_NORMAL, &ev);
while (err == LIBEVDEV_READ_STATUS_SYNC) {
err = libevdev_next_event(dev_, LIBEVDEV_READ_FLAG_SYNC, &ev);
}
}
if (-err != EAGAIN) {
throw errno_error(-err, "Failed to sync evdev device");
}
int numl = libevdev_get_event_value(dev_, EV_LED, LED_NUML);
int capsl = libevdev_get_event_value(dev_, EV_LED, LED_CAPSL);
int scrolll = libevdev_get_event_value(dev_, EV_LED, LED_SCROLLL);
struct {
bool state;
@ -211,3 +291,25 @@ auto waybar::modules::KeyboardState::update() -> void {
AModule::update();
}
auto waybar::modules ::KeyboardState::tryAddDevice(const std::string& dev_path) -> void {
try {
int fd = openFile(dev_path, O_NONBLOCK | O_CLOEXEC | O_RDONLY);
auto dev = openDevice(fd);
if (supportsLockStates(dev)) {
spdlog::info("Found device {} at '{}'", libevdev_get_name(dev), dev_path);
if (libinput_devices_.find(dev_path) == libinput_devices_.end()) {
auto device = libinput_path_add_device(libinput_, dev_path.c_str());
libinput_device_ref(device);
libinput_devices_[dev_path] = device;
}
}
libevdev_free(dev);
closeFile(fd);
} catch (const errno_error& e) {
// ENOTTY just means the device isn't an evdev device, skip it
if (e.code != ENOTTY) {
spdlog::warn(e.what());
}
}
}

View File

@ -1,7 +1,7 @@
#include "modules/memory.hpp"
waybar::modules::Memory::Memory(const std::string& id, const Json::Value& config)
: ALabel(config, "memory", id, "{}%", 30) {
: AButton(config, "memory", id, "{}%", 30) {
thread_ = [this] {
dp.emit();
thread_.sleep_for(interval_);
@ -31,17 +31,18 @@ auto waybar::modules::Memory::update() -> void {
}
if (memtotal > 0 && memfree >= 0) {
auto total_ram_gigabytes = memtotal / std::pow(1024, 2);
auto total_swap_gigabytes = swaptotal / std::pow(1024, 2);
float total_ram_gigabytes =
0.01 * round(memtotal / 10485.76); // 100*10485.76 = 2^20 = 1024^2 = GiB/KiB
float total_swap_gigabytes = 0.01 * round(swaptotal / 10485.76);
int used_ram_percentage = 100 * (memtotal - memfree) / memtotal;
int used_swap_percentage = 0;
if (swaptotal && swapfree) {
used_swap_percentage = 100 * (swaptotal - swapfree) / swaptotal;
}
auto used_ram_gigabytes = (memtotal - memfree) / std::pow(1024, 2);
auto used_swap_gigabytes = (swaptotal - swapfree) / std::pow(1024, 2);
auto available_ram_gigabytes = memfree / std::pow(1024, 2);
auto available_swap_gigabytes = swapfree / std::pow(1024, 2);
float used_ram_gigabytes = 0.01 * round((memtotal - memfree) / 10485.76);
float used_swap_gigabytes = 0.01 * round((swaptotal - swapfree) / 10485.76);
float available_ram_gigabytes = 0.01 * round(memfree / 10485.76);
float available_swap_gigabytes = 0.01 * round(swapfree / 10485.76);
auto format = format_;
auto state = getState(used_ram_percentage);
@ -54,7 +55,7 @@ auto waybar::modules::Memory::update() -> void {
} else {
event_box_.show();
auto icons = std::vector<std::string>{state};
label_.set_markup(fmt::format(
label_->set_markup(fmt::format(
format, used_ram_percentage, fmt::arg("icon", getIcon(used_ram_percentage, icons)),
fmt::arg("total", total_ram_gigabytes), fmt::arg("swapTotal", total_swap_gigabytes),
fmt::arg("percentage", used_ram_percentage),
@ -66,7 +67,7 @@ auto waybar::modules::Memory::update() -> void {
if (tooltipEnabled()) {
if (config_["tooltip-format"].isString()) {
auto tooltip_format = config_["tooltip-format"].asString();
label_.set_tooltip_text(fmt::format(
button_.set_tooltip_text(fmt::format(
tooltip_format, used_ram_percentage, fmt::arg("total", total_ram_gigabytes),
fmt::arg("swapTotal", total_swap_gigabytes),
fmt::arg("percentage", used_ram_percentage),
@ -74,12 +75,12 @@ auto waybar::modules::Memory::update() -> void {
fmt::arg("swapUsed", used_swap_gigabytes), fmt::arg("avail", available_ram_gigabytes),
fmt::arg("swapAvail", available_swap_gigabytes)));
} else {
label_.set_tooltip_text(fmt::format("{:.{}f}GiB used", used_ram_gigabytes, 1));
button_.set_tooltip_text(fmt::format("{:.{}f}GiB used", used_ram_gigabytes, 1));
}
}
} else {
event_box_.hide();
}
// Call parent update
ALabel::update();
AButton::update();
}

View File

@ -4,6 +4,9 @@
#include <glibmm/ustring.h>
#include <spdlog/spdlog.h>
#include <util/sanitize_str.hpp>
using namespace waybar::util;
#include "modules/mpd/state.hpp"
#if defined(MPD_NOINLINE)
namespace waybar::modules {
@ -12,7 +15,7 @@ namespace waybar::modules {
#endif
waybar::modules::MPD::MPD(const std::string& id, const Json::Value& config)
: ALabel(config, "mpd", id, "{album} - {artist} - {title}", 5),
: AButton(config, "mpd", id, "{album} - {artist} - {title}", 5, false, true),
module_name_(id.empty() ? "mpd" : "mpd#" + id),
server_(nullptr),
port_(config_["port"].isUInt() ? config["port"].asUInt() : 0),
@ -44,7 +47,7 @@ auto waybar::modules::MPD::update() -> void {
context_.update();
// Call parent update
ALabel::update();
AButton::update();
}
void waybar::modules::MPD::queryMPD() {
@ -85,15 +88,15 @@ std::string waybar::modules::MPD::getFilename() const {
void waybar::modules::MPD::setLabel() {
if (connection_ == nullptr) {
label_.get_style_context()->add_class("disconnected");
label_.get_style_context()->remove_class("stopped");
label_.get_style_context()->remove_class("playing");
label_.get_style_context()->remove_class("paused");
button_.get_style_context()->add_class("disconnected");
button_.get_style_context()->remove_class("stopped");
button_.get_style_context()->remove_class("playing");
button_.get_style_context()->remove_class("paused");
auto format = config_["format-disconnected"].isString()
? config_["format-disconnected"].asString()
: "disconnected";
label_.set_markup(format);
label_->set_markup(format);
if (tooltipEnabled()) {
std::string tooltip_format;
@ -101,11 +104,11 @@ void waybar::modules::MPD::setLabel() {
? config_["tooltip-format-disconnected"].asString()
: "MPD (disconnected)";
// Nothing to format
label_.set_tooltip_text(tooltip_format);
button_.set_tooltip_text(tooltip_format);
}
return;
} else {
label_.get_style_context()->remove_class("disconnected");
button_.get_style_context()->remove_class("disconnected");
}
auto format = format_;
@ -118,29 +121,29 @@ void waybar::modules::MPD::setLabel() {
if (stopped()) {
format =
config_["format-stopped"].isString() ? config_["format-stopped"].asString() : "stopped";
label_.get_style_context()->add_class("stopped");
label_.get_style_context()->remove_class("playing");
label_.get_style_context()->remove_class("paused");
button_.get_style_context()->add_class("stopped");
button_.get_style_context()->remove_class("playing");
button_.get_style_context()->remove_class("paused");
} else {
label_.get_style_context()->remove_class("stopped");
button_.get_style_context()->remove_class("stopped");
if (playing()) {
label_.get_style_context()->add_class("playing");
label_.get_style_context()->remove_class("paused");
button_.get_style_context()->add_class("playing");
button_.get_style_context()->remove_class("paused");
} else if (paused()) {
format = config_["format-paused"].isString() ? config_["format-paused"].asString()
: config_["format"].asString();
label_.get_style_context()->add_class("paused");
label_.get_style_context()->remove_class("playing");
button_.get_style_context()->add_class("paused");
button_.get_style_context()->remove_class("playing");
}
stateIcon = getStateIcon();
artist = getTag(MPD_TAG_ARTIST);
album_artist = getTag(MPD_TAG_ALBUM_ARTIST);
album = getTag(MPD_TAG_ALBUM);
title = getTag(MPD_TAG_TITLE);
date = getTag(MPD_TAG_DATE);
filename = getFilename();
artist = sanitize_string(getTag(MPD_TAG_ARTIST));
album_artist = sanitize_string(getTag(MPD_TAG_ALBUM_ARTIST));
album = sanitize_string(getTag(MPD_TAG_ALBUM));
title = sanitize_string(getTag(MPD_TAG_TITLE));
date = sanitize_string(getTag(MPD_TAG_DATE));
filename = sanitize_string(getFilename());
song_pos = mpd_status_get_song_pos(status_.get()) + 1;
volume = mpd_status_get_volume(status_.get());
if (volume < 0) {
@ -166,17 +169,15 @@ void waybar::modules::MPD::setLabel() {
if (config_["title-len"].isInt()) title = title.substr(0, config_["title-len"].asInt());
try {
label_.set_markup(fmt::format(
format, fmt::arg("artist", Glib::Markup::escape_text(artist).raw()),
fmt::arg("albumArtist", Glib::Markup::escape_text(album_artist).raw()),
fmt::arg("album", Glib::Markup::escape_text(album).raw()),
fmt::arg("title", Glib::Markup::escape_text(title).raw()),
fmt::arg("date", Glib::Markup::escape_text(date).raw()), fmt::arg("volume", volume),
fmt::arg("elapsedTime", elapsedTime), fmt::arg("totalTime", totalTime),
fmt::arg("songPosition", song_pos), fmt::arg("queueLength", queue_length),
fmt::arg("stateIcon", stateIcon), fmt::arg("consumeIcon", consumeIcon),
fmt::arg("randomIcon", randomIcon), fmt::arg("repeatIcon", repeatIcon),
fmt::arg("singleIcon", singleIcon), fmt::arg("filename", filename)));
label_->set_markup(fmt::format(
format, fmt::arg("artist", artist.raw()), fmt::arg("albumArtist", album_artist.raw()),
fmt::arg("album", album.raw()), fmt::arg("title", title.raw()), fmt::arg("date", date),
fmt::arg("volume", volume), fmt::arg("elapsedTime", elapsedTime),
fmt::arg("totalTime", totalTime), fmt::arg("songPosition", song_pos),
fmt::arg("queueLength", queue_length), fmt::arg("stateIcon", stateIcon),
fmt::arg("consumeIcon", consumeIcon), fmt::arg("randomIcon", randomIcon),
fmt::arg("repeatIcon", repeatIcon), fmt::arg("singleIcon", singleIcon),
fmt::arg("filename", filename)));
} catch (fmt::format_error const& e) {
spdlog::warn("mpd: format error: {}", e.what());
}
@ -195,7 +196,7 @@ void waybar::modules::MPD::setLabel() {
fmt::arg("queueLength", queue_length), fmt::arg("stateIcon", stateIcon),
fmt::arg("consumeIcon", consumeIcon), fmt::arg("randomIcon", randomIcon),
fmt::arg("repeatIcon", repeatIcon), fmt::arg("singleIcon", singleIcon));
label_.set_tooltip_text(tooltip_text);
button_.set_tooltip_text(tooltip_text);
} catch (fmt::format_error const& e) {
spdlog::warn("mpd: format error (tooltip): {}", e.what());
}

View File

@ -78,7 +78,7 @@ waybar::modules::Network::readBandwidthUsage() {
}
waybar::modules::Network::Network(const std::string &id, const Json::Value &config)
: ALabel(config, "network", id, DEFAULT_FORMAT, 60),
: AButton(config, "network", id, DEFAULT_FORMAT, 60),
ifid_(-1),
family_(config["family"] == "ipv6" ? AF_INET6 : AF_INET),
efd_(-1),
@ -95,11 +95,11 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf
#endif
frequency_(0.0) {
// Start with some "text" in the module's label_, update() will then
// Start with some "text" in the module's label_-> update() will then
// update it. Since the text should be different, update() will be able
// to show or hide the event_box_. This is to work around the case where
// the module start with no text, but the the event_box_ is shown.
label_.set_markup("<s></s>");
label_->set_markup("<s></s>");
auto bandwidth = readBandwidthUsage();
if (bandwidth.has_value()) {
@ -309,8 +309,8 @@ auto waybar::modules::Network::update() -> void {
if (!alt_) {
auto state = getNetworkState();
if (!state_.empty() && label_.get_style_context()->has_class(state_)) {
label_.get_style_context()->remove_class(state_);
if (!state_.empty() && button_.get_style_context()->has_class(state_)) {
button_.get_style_context()->remove_class(state_);
}
if (config_["format-" + state].isString()) {
default_format_ = config_["format-" + state].asString();
@ -322,8 +322,8 @@ auto waybar::modules::Network::update() -> void {
if (config_["tooltip-format-" + state].isString()) {
tooltip_format = config_["tooltip-format-" + state].asString();
}
if (!label_.get_style_context()->has_class(state)) {
label_.get_style_context()->add_class(state);
if (!button_.get_style_context()->has_class(state)) {
button_.get_style_context()->add_class(state);
}
format_ = default_format_;
state_ = state;
@ -349,8 +349,8 @@ auto waybar::modules::Network::update() -> void {
fmt::arg("bandwidthUpBytes", pow_format(bandwidth_up / interval_.count(), "B/s")),
fmt::arg("bandwidthTotalBytes",
pow_format((bandwidth_up + bandwidth_down) / interval_.count(), "B/s")));
if (text.compare(label_.get_label()) != 0) {
label_.set_markup(text);
if (text.compare(label_->get_label()) != 0) {
label_->set_markup(text);
if (text.empty()) {
event_box_.hide();
} else {
@ -382,16 +382,16 @@ auto waybar::modules::Network::update() -> void {
fmt::arg("bandwidthUpBytes", pow_format(bandwidth_up / interval_.count(), "B/s")),
fmt::arg("bandwidthTotalBytes",
pow_format((bandwidth_up + bandwidth_down) / interval_.count(), "B/s")));
if (label_.get_tooltip_text() != tooltip_text) {
label_.set_tooltip_markup(tooltip_text);
if (button_.get_tooltip_text() != tooltip_text) {
button_.set_tooltip_markup(tooltip_text);
}
} else if (label_.get_tooltip_text() != text) {
label_.set_tooltip_markup(text);
} else if (button_.get_tooltip_text() != text) {
button_.set_tooltip_markup(text);
}
}
// Call parent update
ALabel::update();
AButton::update();
}
bool waybar::modules::Network::checkInterface(std::string name) {
@ -646,7 +646,12 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
if (has_gateway && !has_destination && temp_idx != -1) {
// Check if this is the first default route we see, or if this new
// route have a higher priority.
if (!is_del_event && ((net->ifid_ == -1) || (priority < net->route_priority))) {
/** Module doesn`t update state, because RTA_GATEWAY call before enable new router and set
higher priority. Disable router -> RTA_GATEWAY -> up new router -> set higher priority added
checking route id
**/
if (!is_del_event &&
((net->ifid_ == -1) || (priority < net->route_priority) || (net->ifid_ != temp_idx))) {
// Clear if's state for the case were there is a higher priority
// route on a different interface.
net->clearIface();

View File

@ -1,7 +1,7 @@
#include "modules/pulseaudio.hpp"
waybar::modules::Pulseaudio::Pulseaudio(const std::string &id, const Json::Value &config)
: ALabel(config, "pulseaudio", id, "{volume}%"),
: AButton(config, "pulseaudio", id, "{volume}%"),
mainloop_(nullptr),
mainloop_api_(nullptr),
context_(nullptr),
@ -36,6 +36,7 @@ waybar::modules::Pulseaudio::Pulseaudio(const std::string &id, const Json::Value
}
waybar::modules::Pulseaudio::~Pulseaudio() {
pa_context_disconnect(context_);
mainloop_api_->quit(mainloop_api_, 0);
pa_threaded_mainloop_stop(mainloop_);
pa_threaded_mainloop_free(mainloop_);
@ -91,19 +92,31 @@ bool waybar::modules::Pulseaudio::handleScroll(GdkEventScroll *e) {
pa_volume_t change = volume_tick;
pa_cvolume pa_volume = pa_volume_;
int max_volume = 100;
double step = 1;
// isDouble returns true for integers as well, just in case
if (config_["scroll-step"].isDouble()) {
change = round(config_["scroll-step"].asDouble() * volume_tick);
step = config_["scroll-step"].asDouble();
}
if (config_["max-volume"].isInt()) {
max_volume = std::min(0, config_["max-volume"].asInt());
max_volume = std::min(config_["max-volume"].asInt(), static_cast<int>(PA_VOLUME_UI_MAX));
}
if (dir == SCROLL_DIR::UP) {
if (volume_ + 1 <= max_volume) {
if (volume_ < max_volume) {
if (volume_ + step > max_volume) {
change = round((max_volume - volume_) * volume_tick);
} else {
change = round(step * volume_tick);
}
pa_cvolume_inc(&pa_volume, change);
}
} else if (dir == SCROLL_DIR::DOWN) {
if (volume_ - 1 >= 0) {
if (volume_ > 0) {
if (volume_ - step < 0) {
change = round(volume_ * volume_tick);
} else {
change = round(step * volume_tick);
}
pa_cvolume_dec(&pa_volume, change);
}
}
@ -170,6 +183,15 @@ void waybar::modules::Pulseaudio::sinkInfoCb(pa_context * /*context*/, const pa_
if (i == nullptr) return;
auto pa = static_cast<waybar::modules::Pulseaudio *>(data);
if (pa->config_["ignored-sinks"].isArray()) {
for (const auto &ignored_sink : pa->config_["ignored-sinks"]) {
if (ignored_sink.asString() == i->description) {
return;
}
}
}
if (pa->current_sink_name_ == i->name) {
if (i->state != PA_SINK_RUNNING) {
pa->current_sink_running_ = false;
@ -240,9 +262,9 @@ auto waybar::modules::Pulseaudio::update() -> void {
if (monitor_.find("a2dp_sink") != std::string::npos || // PulseAudio
monitor_.find("a2dp-sink") != std::string::npos) { // PipeWire
format_name = format_name + "-bluetooth";
label_.get_style_context()->add_class("bluetooth");
button_.get_style_context()->add_class("bluetooth");
} else {
label_.get_style_context()->remove_class("bluetooth");
button_.get_style_context()->remove_class("bluetooth");
}
if (muted_) {
// Check muted bluetooth format exist, otherwise fallback to default muted format
@ -250,29 +272,29 @@ auto waybar::modules::Pulseaudio::update() -> void {
format_name = "format";
}
format_name = format_name + "-muted";
label_.get_style_context()->add_class("muted");
label_.get_style_context()->add_class("sink-muted");
button_.get_style_context()->add_class("muted");
button_.get_style_context()->add_class("sink-muted");
} else {
label_.get_style_context()->remove_class("muted");
label_.get_style_context()->remove_class("sink-muted");
button_.get_style_context()->remove_class("muted");
button_.get_style_context()->remove_class("sink-muted");
}
format = config_[format_name].isString() ? config_[format_name].asString() : format;
}
// TODO: find a better way to split source/sink
std::string format_source = "{volume}%";
if (source_muted_) {
label_.get_style_context()->add_class("source-muted");
button_.get_style_context()->add_class("source-muted");
if (config_["format-source-muted"].isString()) {
format_source = config_["format-source-muted"].asString();
}
} else {
label_.get_style_context()->remove_class("source-muted");
button_.get_style_context()->remove_class("source-muted");
if (config_["format-source-muted"].isString()) {
format_source = config_["format-source"].asString();
}
}
format_source = fmt::format(format_source, fmt::arg("volume", source_volume_));
label_.set_markup(fmt::format(
label_->set_markup(fmt::format(
format, fmt::arg("desc", desc_), fmt::arg("volume", volume_),
fmt::arg("format_source", format_source), fmt::arg("source_volume", source_volume_),
fmt::arg("source_desc", source_desc_), fmt::arg("icon", getIcon(volume_, getPulseIcon()))));
@ -283,16 +305,16 @@ auto waybar::modules::Pulseaudio::update() -> void {
tooltip_format = config_["tooltip-format"].asString();
}
if (!tooltip_format.empty()) {
label_.set_tooltip_text(fmt::format(
button_.set_tooltip_text(fmt::format(
tooltip_format, fmt::arg("desc", desc_), fmt::arg("volume", volume_),
fmt::arg("format_source", format_source), fmt::arg("source_volume", source_volume_),
fmt::arg("source_desc", source_desc_),
fmt::arg("icon", getIcon(volume_, getPulseIcon()))));
} else {
label_.set_tooltip_text(desc_);
button_.set_tooltip_text(desc_);
}
}
// Call parent update
ALabel::update();
AButton::update();
}

View File

@ -3,7 +3,7 @@
#include <time.h>
waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
: ALabel(config, "clock", id, "{:%H:%M}", 60) {
: AButton(config, "clock", id, "{:%H:%M}", 60) {
thread_ = [this] {
dp.emit();
auto now = std::chrono::system_clock::now();
@ -19,17 +19,17 @@ auto waybar::modules::Clock::update() -> void {
auto now = std::chrono::system_clock::now();
auto localtime = fmt::localtime(std::chrono::system_clock::to_time_t(now));
auto text = fmt::format(format_, localtime);
label_.set_markup(text);
label_->set_markup(text);
if (tooltipEnabled()) {
if (config_["tooltip-format"].isString()) {
auto tooltip_format = config_["tooltip-format"].asString();
auto tooltip_text = fmt::format(tooltip_format, localtime);
label_.set_tooltip_text(tooltip_text);
button_.set_tooltip_text(tooltip_text);
} else {
label_.set_tooltip_text(text);
button_.set_tooltip_text(text);
}
}
// Call parent update
ALabel::update();
AButton::update();
}

View File

@ -41,7 +41,7 @@ auto Sndio::connect_to_sndio() -> void {
}
Sndio::Sndio(const std::string &id, const Json::Value &config)
: ALabel(config, "sndio", id, "{volume}%", 1),
: AButton(config, "sndio", id, "{volume}%", 1, false, true),
hdl_(nullptr),
pfds_(0),
addr_(0),
@ -105,14 +105,14 @@ auto Sndio::update() -> void {
unsigned int vol = 100. * static_cast<double>(volume_) / static_cast<double>(maxval_);
if (volume_ == 0) {
label_.get_style_context()->add_class("muted");
button_.get_style_context()->add_class("muted");
} else {
label_.get_style_context()->remove_class("muted");
button_.get_style_context()->remove_class("muted");
}
label_.set_markup(fmt::format(format, fmt::arg("volume", vol), fmt::arg("raw_value", volume_)));
label_->set_markup(fmt::format(format, fmt::arg("volume", vol), fmt::arg("raw_value", volume_)));
ALabel::update();
AButton::update();
}
auto Sndio::set_desc(struct sioctl_desc *d, unsigned int val) -> void {

View File

@ -18,7 +18,7 @@ const std::string Language::XKB_LAYOUT_NAMES_KEY = "xkb_layout_names";
const std::string Language::XKB_ACTIVE_LAYOUT_NAME_KEY = "xkb_active_layout_name";
Language::Language(const std::string& id, const Json::Value& config)
: ALabel(config, "language", id, "{}", 0, true) {
: AButton(config, "language", id, "{}", 0, true) {
is_variant_displayed = format_.find("{variant}") != std::string::npos;
if (format_.find("{}") != std::string::npos || format_.find("{short}") != std::string::npos) {
displayed_short_flag |= static_cast<std::byte>(DispayedShortFlag::ShortName);
@ -99,7 +99,7 @@ auto Language::update() -> void {
format_, fmt::arg("short", layout_.short_name),
fmt::arg("shortDescription", layout_.short_description), fmt::arg("long", layout_.full_name),
fmt::arg("variant", layout_.variant), fmt::arg("flag", layout_.country_flag())));
label_.set_markup(display_layout);
label_->set_markup(display_layout);
if (tooltipEnabled()) {
if (tooltip_format_ != "") {
auto tooltip_display_layout = trim(
@ -107,22 +107,22 @@ auto Language::update() -> void {
fmt::arg("shortDescription", layout_.short_description),
fmt::arg("long", layout_.full_name), fmt::arg("variant", layout_.variant),
fmt::arg("flag", layout_.country_flag())));
label_.set_tooltip_markup(tooltip_display_layout);
button_.set_tooltip_markup(tooltip_display_layout);
} else {
label_.set_tooltip_markup(display_layout);
button_.set_tooltip_markup(display_layout);
}
}
event_box_.show();
// Call parent update
ALabel::update();
AButton::update();
}
auto Language::set_current_layout(std::string current_layout) -> void {
label_.get_style_context()->remove_class(layout_.short_name);
button_.get_style_context()->remove_class(layout_.short_name);
layout_ = layouts_map_[current_layout];
label_.get_style_context()->add_class(layout_.short_name);
button_.get_style_context()->add_class(layout_.short_name);
}
auto Language::init_layouts_map(const std::vector<std::string>& used_layouts) -> void {

View File

@ -5,7 +5,7 @@
namespace waybar::modules::sway {
Mode::Mode(const std::string& id, const Json::Value& config)
: ALabel(config, "mode", id, "{}", 0, true) {
: AButton(config, "mode", id, "{}", 0, true) {
ipc_.subscribe(R"(["mode"])");
ipc_.signal_event.connect(sigc::mem_fun(*this, &Mode::onEvent));
// Launch worker
@ -42,14 +42,14 @@ auto Mode::update() -> void {
if (mode_.empty()) {
event_box_.hide();
} else {
label_.set_markup(fmt::format(format_, mode_));
label_->set_markup(fmt::format(format_, mode_));
if (tooltipEnabled()) {
label_.set_tooltip_text(mode_);
button_.set_tooltip_text(mode_);
}
event_box_.show();
}
// Call parent update
ALabel::update();
AButton::update();
}
} // namespace waybar::modules::sway

View File

@ -0,0 +1,82 @@
#include "modules/sway/scratchpad.hpp"
#include <spdlog/spdlog.h>
#include <string>
namespace waybar::modules::sway {
Scratchpad::Scratchpad(const std::string& id, const Json::Value& config)
: ALabel(config, "scratchpad", id,
config["format"].isString() ? config["format"].asString() : "{icon} {count}"),
tooltip_format_(config_["tooltip-format"].isString() ? config_["tooltip-format"].asString()
: "{app}: {title}"),
show_empty_(config_["show-empty"].isBool() ? config_["show-empty"].asBool() : false),
tooltip_enabled_(config_["tooltip"].isBool() ? config_["tooltip"].asBool() : true),
tooltip_text_(""),
count_(0) {
ipc_.subscribe(R"(["window"])");
ipc_.signal_event.connect(sigc::mem_fun(*this, &Scratchpad::onEvent));
ipc_.signal_cmd.connect(sigc::mem_fun(*this, &Scratchpad::onCmd));
getTree();
ipc_.setWorker([this] {
try {
ipc_.handleEvent();
} catch (const std::exception& e) {
spdlog::error("Scratchpad: {}", e.what());
}
});
}
auto Scratchpad::update() -> void {
if (count_ || show_empty_) {
event_box_.show();
label_.set_markup(
fmt::format(format_, fmt::arg("icon", getIcon(count_, "", config_["format-icons"].size())),
fmt::arg("count", count_)));
if (tooltip_enabled_) {
label_.set_tooltip_markup(tooltip_text_);
}
} else {
event_box_.hide();
}
if (count_) {
label_.get_style_context()->remove_class("empty");
} else {
label_.get_style_context()->add_class("empty");
}
ALabel::update();
}
auto Scratchpad::getTree() -> void {
try {
ipc_.sendCmd(IPC_GET_TREE);
} catch (const std::exception& e) {
spdlog::error("Scratchpad: {}", e.what());
}
}
auto Scratchpad::onCmd(const struct Ipc::ipc_response& res) -> void {
try {
std::lock_guard<std::mutex> lock(mutex_);
auto tree = parser_.parse(res.payload);
count_ = tree["nodes"][0]["nodes"][0]["floating_nodes"].size();
if (tooltip_enabled_) {
tooltip_text_.clear();
for (const auto& window : tree["nodes"][0]["nodes"][0]["floating_nodes"]) {
tooltip_text_.append(fmt::format(tooltip_format_ + '\n',
fmt::arg("app", window["app_id"].asString()),
fmt::arg("title", window["name"].asString())));
}
if (!tooltip_text_.empty()) {
tooltip_text_.pop_back();
}
}
dp.emit();
} catch (const std::exception& e) {
spdlog::error("Scratchpad: {}", e.what());
}
}
auto Scratchpad::onEvent(const struct Ipc::ipc_response& res) -> void { getTree(); }
} // namespace waybar::modules::sway

View File

@ -12,6 +12,8 @@
#include <regex>
#include <string>
#include "util/rewrite_title.hpp"
namespace waybar::modules::sway {
Window::Window(const std::string& id, const Bar& bar, const Json::Value& config)
@ -175,7 +177,8 @@ auto Window::update() -> void {
bar_.window.get_style_context()->remove_class("solo");
bar_.window.get_style_context()->remove_class("empty");
}
label_.set_markup(fmt::format(format_, fmt::arg("title", rewriteTitle(window_)),
label_.set_markup(fmt::format(
format_, fmt::arg("title", waybar::util::rewriteTitle(window_, config_["rewrite"])),
fmt::arg("app_id", app_id_), fmt::arg("shell", shell_)));
if (tooltipEnabled()) {
label_.set_tooltip_text(window_);
@ -262,30 +265,4 @@ void Window::getTree() {
}
}
std::string Window::rewriteTitle(const std::string& title) {
const auto& rules = config_["rewrite"];
if (!rules.isObject()) {
return title;
}
std::string res = title;
for (auto it = rules.begin(); it != rules.end(); ++it) {
if (it.key().isString() && it->isString()) {
try {
// malformated regexes will cause an exception.
// in this case, log error and try the next rule.
const std::regex rule{it.key().asString()};
if (std::regex_match(title, rule)) {
res = std::regex_replace(res, rule, it->asString());
}
} catch (const std::regex_error& e) {
spdlog::error("Invalid rule {}: {}", it.key().asString(), e.what());
}
}
}
return res;
}
} // namespace waybar::modules::sway

View File

@ -3,15 +3,11 @@
#include <filesystem>
#if defined(__FreeBSD__)
// clang-format off
#include <sys/types.h>
#include <sys/sysctl.h>
// clang-format on
#endif
waybar::modules::Temperature::Temperature(const std::string& id, const Json::Value& config)
: ALabel(config, "temperature", id, "{temperatureC}°C", 10) {
: AButton(config, "temperature", id, "{temperatureC}°C", 10) {
#if defined(__FreeBSD__)
// try to read sysctl?
#else
@ -46,9 +42,9 @@ auto waybar::modules::Temperature::update() -> void {
auto format = format_;
if (critical) {
format = config_["format-critical"].isString() ? config_["format-critical"].asString() : format;
label_.get_style_context()->add_class("critical");
button_.get_style_context()->add_class("critical");
} else {
label_.get_style_context()->remove_class("critical");
button_.get_style_context()->remove_class("critical");
}
if (format.empty()) {
@ -59,7 +55,7 @@ auto waybar::modules::Temperature::update() -> void {
}
auto max_temp = config_["critical-threshold"].isInt() ? config_["critical-threshold"].asInt() : 0;
label_.set_markup(fmt::format(format, fmt::arg("temperatureC", temperature_c),
label_->set_markup(fmt::format(format, fmt::arg("temperatureC", temperature_c),
fmt::arg("temperatureF", temperature_f),
fmt::arg("temperatureK", temperature_k),
fmt::arg("icon", getIcon(temperature_c, "", max_temp))));
@ -68,12 +64,12 @@ auto waybar::modules::Temperature::update() -> void {
if (config_["tooltip-format"].isString()) {
tooltip_format = config_["tooltip-format"].asString();
}
label_.set_tooltip_text(fmt::format(tooltip_format, fmt::arg("temperatureC", temperature_c),
button_.set_tooltip_text(fmt::format(tooltip_format, fmt::arg("temperatureC", temperature_c),
fmt::arg("temperatureF", temperature_f),
fmt::arg("temperatureK", temperature_k)));
}
// Call parent update
ALabel::update();
AButton::update();
}
float waybar::modules::Temperature::getTemperature() {
@ -81,8 +77,12 @@ float waybar::modules::Temperature::getTemperature() {
int temp;
size_t size = sizeof temp;
auto zone = config_["thermal-zone"].isInt() ? config_["thermal-zone"].asInt() : 0;
auto sysctl_thermal = fmt::format("hw.acpi.thermal.tz{}.temperature", zone);
if (sysctlbyname("hw.acpi.thermal.tz0.temperature", &temp, &size, NULL, 0) != 0) {
throw std::runtime_error("sysctl hw.acpi.thermal.tz0.temperature or dev.cpu.0.temperature failed");
throw std::runtime_error(
"sysctl hw.acpi.thermal.tz0.temperature or dev.cpu.0.temperature failed");
}
auto temperature_c = ((float)temp - 2732) / 10;
return temperature_c;

View File

@ -0,0 +1,130 @@
#include "modules/user.hpp"
#include <fmt/chrono.h>
#include <glibmm/miscutils.h>
#include <unistd.h>
#include <algorithm>
#include <chrono>
#include "gdkmm/event.h"
#include "gdkmm/types.h"
#include "sigc++/functors/mem_fun.h"
#include "sigc++/functors/ptr_fun.h"
#if HAVE_CPU_LINUX
#include <sys/sysinfo.h>
#endif
#if HAVE_CPU_BSD
#include <time.h>
#endif
#define LEFT_MOUSE_BUTTON 1
namespace waybar::modules {
User::User(const std::string& id, const Json::Value& config)
: AIconLabel(config, "user", id, "{user} {work_H}:{work_M}", 60, false, false, true) {
if (AIconLabel::iconEnabled()) {
this->init_avatar(AIconLabel::config_);
}
this->init_update_worker();
AModule::event_box_.signal_button_press_event().connect(sigc::mem_fun(this, &User::signal_label));
}
bool User::signal_label(GdkEventButton* button) const {
if (button->type != GDK_BUTTON_PRESS) return true;
if (button->button == LEFT_MOUSE_BUTTON) {
Gio::AppInfo::launch_default_for_uri("file:///" + this->get_user_home_dir());
}
return false;
}
long User::uptime_as_seconds() {
long uptime = 0;
#if HAVE_CPU_LINUX
struct sysinfo s_info;
if (0 == sysinfo(&s_info)) {
uptime = s_info.uptime;
}
#endif
#if HAVE_CPU_BSD
struct timespec s_info;
if (0 == clock_gettime(CLOCK_UPTIME_PRECISE, &s_info)) {
uptime = s_info.tv_sec;
}
#endif
return uptime;
}
std::string User::get_user_login() const { return Glib::get_user_name(); }
std::string User::get_user_home_dir() const { return Glib::get_home_dir(); }
void User::init_update_worker() {
this->thread_ = [this] {
ALabel::dp.emit();
auto now = std::chrono::system_clock::now();
auto diff = now.time_since_epoch() % ALabel::interval_;
this->thread_.sleep_for(ALabel::interval_ - diff);
};
}
void User::init_avatar(const Json::Value& config) {
int height =
config["height"].isUInt() ? config["height"].asUInt() : this->defaultUserImageHeight_;
int width = config["width"].isUInt() ? config["width"].asUInt() : this->defaultUserImageWidth_;
if (config["avatar"].isString()) {
std::string userAvatar = config["avatar"].asString();
if (!userAvatar.empty()) {
this->init_user_avatar(userAvatar, width, height);
return;
}
}
this->init_default_user_avatar(width, width);
}
std::string User::get_default_user_avatar_path() const {
return this->get_user_home_dir() + "/" + ".face";
}
void User::init_default_user_avatar(int width, int height) {
this->init_user_avatar(this->get_default_user_avatar_path(), width, height);
}
void User::init_user_avatar(const std::string& path, int width, int height) {
this->pixbuf_ = Gdk::Pixbuf::create_from_file(path, width, height);
AIconLabel::image_.set(this->pixbuf_);
}
auto User::update() -> void {
std::string systemUser = this->get_user_login();
std::transform(systemUser.cbegin(), systemUser.cend(), systemUser.begin(),
[](unsigned char c) { return std::toupper(c); });
long uptimeSeconds = this->uptime_as_seconds();
auto workSystemTimeSeconds = std::chrono::seconds(uptimeSeconds);
auto currentSystemTime = std::chrono::system_clock::now();
auto startSystemTime = currentSystemTime - workSystemTimeSeconds;
long workSystemDays = uptimeSeconds / 86400;
auto label = fmt::format(ALabel::format_, fmt::arg("up_H", fmt::format("{:%H}", startSystemTime)),
fmt::arg("up_M", fmt::format("{:%M}", startSystemTime)),
fmt::arg("up_d", fmt::format("{:%d}", startSystemTime)),
fmt::arg("up_m", fmt::format("{:%m}", startSystemTime)),
fmt::arg("up_Y", fmt::format("{:%Y}", startSystemTime)),
fmt::arg("work_d", workSystemDays),
fmt::arg("work_H", fmt::format("{:%H}", workSystemTimeSeconds)),
fmt::arg("work_M", fmt::format("{:%M}", workSystemTimeSeconds)),
fmt::arg("work_S", fmt::format("{:%S}", workSystemTimeSeconds)),
fmt::arg("user", systemUser));
ALabel::label_.set_markup(label);
ALabel::update();
}
}; // namespace waybar::modules

View File

@ -251,6 +251,9 @@ static const struct zwlr_foreign_toplevel_handle_v1_listener toplevel_handle_imp
.parent = tl_handle_parent,
};
static const std::vector<Gtk::TargetEntry> target_entries = {
Gtk::TargetEntry("WAYBAR_TOPLEVEL", Gtk::TARGET_SAME_APP, 0)};
Task::Task(const waybar::Bar &bar, const Json::Value &config, Taskbar *tbar,
struct zwlr_foreign_toplevel_handle_v1 *tl_handle, struct wl_seat *seat)
: bar_{bar},
@ -309,9 +312,22 @@ Task::Task(const waybar::Bar &bar, const Json::Value &config, Taskbar *tbar,
/* Handle click events if configured */
if (config_["on-click"].isString() || config_["on-click-middle"].isString() ||
config_["on-click-right"].isString()) {
}
button_.add_events(Gdk::BUTTON_PRESS_MASK);
button_.signal_button_press_event().connect(sigc::mem_fun(*this, &Task::handle_clicked), false);
}
button_.signal_button_release_event().connect(sigc::mem_fun(*this, &Task::handle_button_release),
false);
button_.signal_motion_notify_event().connect(sigc::mem_fun(*this, &Task::handle_motion_notify),
false);
button_.drag_source_set(target_entries, Gdk::BUTTON1_MASK, Gdk::ACTION_MOVE);
button_.drag_dest_set(target_entries, Gtk::DEST_DEFAULT_ALL, Gdk::ACTION_MOVE);
button_.signal_drag_data_get().connect(sigc::mem_fun(*this, &Task::handle_drag_data_get), false);
button_.signal_drag_data_received().connect(
sigc::mem_fun(*this, &Task::handle_drag_data_received), false);
}
Task::~Task() {
@ -495,6 +511,14 @@ void Task::handle_closed() {
}
bool Task::handle_clicked(GdkEventButton *bt) {
/* filter out additional events for double/triple clicks */
if (bt->type == GDK_BUTTON_PRESS) {
/* save where the button press ocurred in case it becomes a drag */
drag_start_button = bt->button;
drag_start_x = bt->x;
drag_start_y = bt->y;
}
std::string action;
if (config_["on-click"].isString() && bt->button == 1)
action = config_["on-click"].asString();
@ -528,6 +552,54 @@ bool Task::handle_clicked(GdkEventButton *bt) {
return true;
}
bool Task::handle_button_release(GdkEventButton *bt) {
drag_start_button = -1;
return false;
}
bool Task::handle_motion_notify(GdkEventMotion *mn) {
if (drag_start_button == -1) return false;
if (button_.drag_check_threshold(drag_start_x, drag_start_y, mn->x, mn->y)) {
/* start drag in addition to other assigned action */
auto target_list = Gtk::TargetList::create(target_entries);
auto refptr = Glib::RefPtr<Gtk::TargetList>(target_list);
auto drag_context =
button_.drag_begin(refptr, Gdk::DragAction::ACTION_MOVE, drag_start_button, (GdkEvent *)mn);
}
return false;
}
void Task::handle_drag_data_get(const Glib::RefPtr<Gdk::DragContext> &context,
Gtk::SelectionData &selection_data, guint info, guint time) {
spdlog::debug("drag_data_get");
void *button_addr = (void *)&this->button_;
selection_data.set("WAYBAR_TOPLEVEL", 32, (const guchar *)&button_addr, sizeof(gpointer));
}
void Task::handle_drag_data_received(const Glib::RefPtr<Gdk::DragContext> &context, int x, int y,
Gtk::SelectionData selection_data, guint info, guint time) {
spdlog::debug("drag_data_received");
gpointer handle = *(gpointer *)selection_data.get_data();
auto dragged_button = (Gtk::Button *)handle;
if (dragged_button == &this->button_) return;
auto parent_of_dragged = dragged_button->get_parent();
auto parent_of_dest = this->button_.get_parent();
if (parent_of_dragged != parent_of_dest) return;
auto box = (Gtk::Box *)parent_of_dragged;
auto position_prop = box->child_property_position(this->button_);
auto position = position_prop.get_value();
box->reorder_child(*dragged_button, position);
}
bool Task::operator==(const Task &o) const { return o.id_ == id_; }
bool Task::operator!=(const Task &o) const { return o.id_ != id_; }

View File

@ -32,6 +32,11 @@ WorkspaceManager::WorkspaceManager(const std::string &id, const waybar::Bar &bar
sort_by_coordinates_ = config_sort_by_coordinates.asBool();
}
auto config_sort_by_number = config_["sort-by-number"];
if (config_sort_by_number.isBool()) {
sort_by_number_ = config_sort_by_number.asBool();
}
auto config_all_outputs = config_["all-outputs"];
if (config_all_outputs.isBool()) {
all_outputs_ = config_all_outputs.asBool();
@ -61,6 +66,12 @@ auto WorkspaceManager::workspace_comparator() const
auto is_name_less = lhs->get_name() < rhs->get_name();
auto is_name_eq = lhs->get_name() == rhs->get_name();
auto is_coords_less = lhs->get_coords() < rhs->get_coords();
auto is_number_less = std::stoi(lhs->get_name()) < std::stoi(rhs->get_name());
if (sort_by_number_) {
return is_number_less;
}
if (sort_by_name_) {
if (sort_by_coordinates_) {
return is_name_eq ? is_coords_less : is_name_less;

View File

@ -0,0 +1,32 @@
#include "util/rewrite_title.hpp"
#include <spdlog/spdlog.h>
#include <regex>
namespace waybar::util {
std::string rewriteTitle(const std::string& title, const Json::Value& rules) {
if (!rules.isObject()) {
return title;
}
std::string res = title;
for (auto it = rules.begin(); it != rules.end(); ++it) {
if (it.key().isString() && it->isString()) {
try {
// malformated regexes will cause an exception.
// in this case, log error and try the next rule.
const std::regex rule{it.key().asString()};
if (std::regex_match(title, rule)) {
res = std::regex_replace(res, rule, it->asString());
}
} catch (const std::regex_error& e) {
spdlog::error("Invalid rule {}: {}", it.key().asString(), e.what());
}
}
}
return res;
}
} // namespace waybar::util

View File

@ -0,0 +1,24 @@
#include <array>
#include <string>
#include <util/sanitize_str.hpp>
#include <utility>
namespace waybar::util {
// replaces ``<>&"'`` with their encoded counterparts
std::string sanitize_string(std::string str) {
// note: it's important that '&' is replaced first; therefor we *can't* use std::map
const std::pair<char, std::string> replacement_table[] = {
{'&', "&amp;"}, {'<', "&lt;"}, {'>', "&gt;"}, {'"', "&quot;"}, {'\'', "&apos;"}};
size_t startpoint;
for (size_t i = 0; i < (sizeof(replacement_table) / sizeof(replacement_table[0])); ++i) {
startpoint = 0;
std::pair pair = replacement_table[i];
while ((startpoint = str.find(pair.first, startpoint)) != std::string::npos) {
str.replace(startpoint, 1, pair.second);
startpoint += pair.second.length();
}
}
return str;
}
} // namespace waybar::util

View File

@ -1,12 +1,13 @@
[wrap-file]
directory = Catch2-2.13.7
source_url = https://github.com/catchorg/Catch2/archive/v2.13.7.zip
source_filename = Catch2-2.13.7.zip
source_hash = 3f3ccd90ad3a8fbb1beeb15e6db440ccdcbebe378dfd125d07a1f9a587a927e9
patch_filename = catch2_2.13.7-1_patch.zip
patch_url = https://wrapdb.mesonbuild.com/v2/catch2_2.13.7-1/get_patch
patch_hash = 2f7369645d747e5bd866317ac1dd4c3d04dc97d3aad4fc6b864bdf75d3b57158
directory = Catch2-3.1.0
source_url = https://github.com/catchorg/Catch2/archive/v3.1.0.tar.gz
source_filename = Catch2-3.1.0.tar.gz
source_hash = c252b2d9537e18046d8b82535069d2567f77043f8e644acf9a9fffc22ea6e6f7
patch_filename = catch2_3.1.0-1_patch.zip
patch_url = https://wrapdb.mesonbuild.com/v2/catch2_3.1.0-1/get_patch
patch_hash = 4ebf4277aed574a9912a79f4817a310d837798e099bbafa6097be23a7f5e3ae4
wrapdb_version = 3.1.0-1
[provide]
catch2 = catch2_dep
catch2-with-main = catch2_with_main_dep

View File

@ -1,9 +1,14 @@
[wrap-file]
source_url=https://github.com/HowardHinnant/date/archive/v3.0.0.tar.gz
source_filename=date-3.0.0.tar.gz
source_hash=87bba2eaf0ebc7ec539e5e62fc317cb80671a337c1fb1b84cb9e4d42c6dbebe3
directory=date-3.0.0
source_url = https://github.com/HowardHinnant/date/archive/v3.0.1.tar.gz
source_filename = date-3.0.1.tar.gz
source_hash = 7a390f200f0ccd207e8cff6757e04817c1a0aec3e327b006b7eb451c57ee3538
directory = date-3.0.1
patch_filename = hinnant-date_3.0.1-2_patch.zip
patch_url = https://wrapdb.mesonbuild.com/v2/hinnant-date_3.0.1-2/get_patch
patch_hash = 11b715b792609117a63310eeefc2939cc2ca26ecd4e996108335e504db58a41d
wrapdb_version = 3.0.1-2
[provide]
tz = tz_dep
date = date_dep
patch_url = https://github.com/mesonbuild/hinnant-date/releases/download/3.0.0-1/hinnant-date.zip
patch_filename = hinnant-date-3.0.0-1-wrap.zip
patch_hash = 6ccaf70732d8bdbd1b6d5fdf3e1b935c23bf269bda12fdfd0e561276f63432fe

View File

@ -0,0 +1,9 @@
[wrap-file]
directory = jsoncpp-1.9.5
source_url = https://github.com/open-source-parsers/jsoncpp/archive/1.9.5.tar.gz
source_filename = jsoncpp-1.9.5.tar.gz
source_hash = f409856e5920c18d0c2fb85276e24ee607d2a09b5e7d5f0a371368903c275da2
[provide]
jsoncpp = jsoncpp_dep

View File

@ -2,7 +2,7 @@
#include <glibmm.h>
#include <catch2/catch.hpp>
#include <catch2/catch_all.hpp>
#include <thread>
#include <type_traits>

View File

@ -1,6 +1,6 @@
#include "config.hpp"
#include <catch2/catch.hpp>
#include <catch2/catch_all.hpp>
TEST_CASE("Load simple config", "[config]") {
waybar::Config conf;

View File

@ -3,8 +3,8 @@
#include <spdlog/sinks/stdout_sinks.h>
#include <spdlog/spdlog.h>
#include <catch2/catch.hpp>
#include <catch2/catch_reporter_tap.hpp>
#include <catch2/catch_all.hpp>
#include <catch2/reporters/catch_reporter_tap.hpp>
#include <memory>
int main(int argc, char* argv[]) {
@ -13,14 +13,15 @@ int main(int argc, char* argv[]) {
session.applyCommandLine(argc, argv);
const auto logger = spdlog::default_logger();
const auto& reporter_name = session.config().getReporterName();
if (reporter_name == "tap") {
for (const auto& spec : session.config().getReporterSpecs()) {
if (spec.name() == "tap") {
spdlog::set_pattern("# [%l] %v");
} else if (reporter_name == "compact") {
} else if (spec.name() == "compact") {
logger->sinks().clear();
} else {
logger->sinks().assign({std::make_shared<spdlog::sinks::stderr_sink_st>()});
}
}
return session.run();
}

View File

@ -3,7 +3,7 @@
#include <date/date.h>
#include <date/tz.h>
#include <catch2/catch.hpp>
#include <catch2/catch_all.hpp>
#include <chrono>
#include <stdexcept>