This commit is contained in:
2026-02-23 15:42:08 +03:00
140 changed files with 2731 additions and 1654 deletions

View File

@@ -10,13 +10,9 @@ jobs:
lint: lint:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v6
# TODO: bump to clang 19 release - uses: RafikFarhad/clang-format-github-action@v6
# - uses: DoozyX/clang-format-lint-action@v0.18.2
- uses: DoozyX/clang-format-lint-action@558090054b3f39e3d6af24f0cd73b319535da809
name: clang-format name: clang-format
with: with:
source: "." sources: "src/**/*.hpp,src/**/*.cpp"
extensions: "hpp,h,cpp,c" style: "file"
style: "file:.clang-format"
clangFormatVersion: 19

View File

@@ -12,7 +12,7 @@ jobs:
container: container:
image: alexays/waybar:debian image: alexays/waybar:debian
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v6
- name: configure - name: configure
run: | run: |
meson -Dcpp_std=c++20 build # necessary to generate compile_commands.json meson -Dcpp_std=c++20 build # necessary to generate compile_commands.json

View File

@@ -17,7 +17,7 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Login to Docker Hub - name: Login to Docker Hub
uses: docker/login-action@v3 uses: docker/login-action@v3

View File

@@ -12,7 +12,7 @@ jobs:
# https://github.com/actions/runner/issues/385 - for FreeBSD runner support # https://github.com/actions/runner/issues/385 - for FreeBSD runner support
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v6
- name: Test in FreeBSD VM - name: Test in FreeBSD VM
uses: cross-platform-actions/action@v0.28.0 uses: cross-platform-actions/action@v0.28.0
timeout-minutes: 180 timeout-minutes: 180

View File

@@ -25,7 +25,7 @@ jobs:
image: alexays/waybar:${{ matrix.distro }} image: alexays/waybar:${{ matrix.distro }}
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v6
- name: configure - name: configure
run: meson setup -Dman-pages=enabled -Dcpp_std=${{matrix.cpp_std}} build run: meson setup -Dman-pages=enabled -Dcpp_std=${{matrix.cpp_std}} build
- name: build - name: build

View File

@@ -2,12 +2,15 @@ name: "Nix-Tests"
on: on:
pull_request: pull_request:
push: push:
concurrency:
group: ${{ github.workflow }}-nix-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs: jobs:
nix-flake-check: nix-flake-check:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- uses: cachix/install-nix-action@v27 - uses: cachix/install-nix-action@v31
with: with:
extra_nix_config: | extra_nix_config: |
experimental-features = nix-command flakes experimental-features = nix-command flakes

View File

@@ -12,11 +12,11 @@ jobs:
if: github.event_name != 'schedule' || github.repository == 'Alexays/Waybar' if: github.event_name != 'schedule' || github.repository == 'Alexays/Waybar'
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Install Nix - name: Install Nix
uses: cachix/install-nix-action@v27 uses: cachix/install-nix-action@v31
with: with:
extra_nix_config: | extra_nix_config: |
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
- name: Update flake.lock - name: Update flake.lock
uses: DeterminateSystems/update-flake-lock@v21 uses: DeterminateSystems/update-flake-lock@v28

2
.gitignore vendored
View File

@@ -7,6 +7,7 @@ vgcore.*
*.swp *.swp
packagecache packagecache
/subprojects/**/ /subprojects/**/
/subprojects/.wraplock
/build* /build*
/dist /dist
/meson.egg-info /meson.egg-info
@@ -50,3 +51,4 @@ result
result-* result-*
.ccls-cache .ccls-cache
_codeql_detected_source_root

View File

@@ -119,7 +119,7 @@ sudo apt install \
On Arch, you can use this command: On Arch, you can use this command:
``` ```
pacman -S \ pacman -S --asdeps \
gtkmm3 \ gtkmm3 \
jsoncpp \ jsoncpp \
libsigc++ \ libsigc++ \
@@ -151,6 +151,10 @@ Contributions welcome!<br>
Have fun :)<br> Have fun :)<br>
The style guidelines are [Google's](https://google.github.io/styleguide/cppguide.html) The style guidelines are [Google's](https://google.github.io/styleguide/cppguide.html)
> [!CAUTION]
> Distributions of Waybar are only released on the [official GitHub page](https://github.com/Alexays/Waybar).<br/>
> Waybar does **not** have an official website. Do not trust any sites that claim to be official.
## License ## License
Waybar is licensed under the MIT license. [See LICENSE for more information](https://github.com/Alexays/Waybar/blob/master/LICENSE). Waybar is licensed under the MIT license. [See LICENSE for more information](https://github.com/Alexays/Waybar/blob/master/LICENSE).

View File

@@ -0,0 +1 @@
.

12
flake.lock generated
View File

@@ -3,11 +3,11 @@
"flake-compat": { "flake-compat": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1747046372, "lastModified": 1767039857,
"narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=", "narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=",
"owner": "edolstra", "owner": "edolstra",
"repo": "flake-compat", "repo": "flake-compat",
"rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885", "rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -18,11 +18,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1760878510, "lastModified": 1769461804,
"narHash": "sha256-K5Osef2qexezUfs0alLvZ7nQFTGS9DL2oTVsIXsqLgs=", "narHash": "sha256-msG8SU5WsBUfVVa/9RPLaymvi5bI8edTavbIq3vRlhI=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "5e2a59a5b1a82f89f2c7e598302a9cacebb72a67", "rev": "bfc1b8a4574108ceef22f02bafcf6611380c100d",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@@ -56,6 +56,8 @@ class Client {
std::list<struct waybar_output> outputs_; std::list<struct waybar_output> outputs_;
std::unique_ptr<CssReloadHelper> m_cssReloadHelper; std::unique_ptr<CssReloadHelper> m_cssReloadHelper;
std::string m_cssFile; std::string m_cssFile;
sigc::connection monitor_added_connection_;
sigc::connection monitor_removed_connection_;
}; };
} // namespace waybar } // namespace waybar

View File

@@ -6,6 +6,7 @@
#if defined(__linux__) #if defined(__linux__)
#include <sys/inotify.h> #include <sys/inotify.h>
#endif #endif
#include <poll.h>
#include <algorithm> #include <algorithm>
#include <fstream> #include <fstream>
@@ -15,6 +16,7 @@
#include "ALabel.hpp" #include "ALabel.hpp"
#include "bar.hpp" #include "bar.hpp"
#include "util/sleeper_thread.hpp" #include "util/sleeper_thread.hpp"
#include "util/udev_deleter.hpp"
namespace waybar::modules { namespace waybar::modules {
@@ -37,15 +39,17 @@ class Battery : public ALabel {
void setBarClass(std::string&); void setBarClass(std::string&);
void processEvents(std::string& state, std::string& status, uint8_t capacity); void processEvents(std::string& state, std::string& status, uint8_t capacity);
int global_watch;
std::map<fs::path, int> batteries_; std::map<fs::path, int> batteries_;
std::unique_ptr<udev, util::UdevDeleter> udev_;
std::array<pollfd, 1> poll_fds_;
std::unique_ptr<udev_monitor, util::UdevMonitorDeleter> mon_;
fs::path adapter_; fs::path adapter_;
int battery_watch_fd_; int battery_watch_fd_;
int global_watch_fd_;
std::mutex battery_list_mutex_; std::mutex battery_list_mutex_;
std::string old_status_; std::string old_status_;
std::string last_event_; std::string last_event_;
bool warnFirstTime_{true}; bool warnFirstTime_{true};
bool weightedAverage_{true};
const Bar& bar_; const Bar& bar_;
util::SleeperThread thread_; util::SleeperThread thread_;

View File

@@ -0,0 +1,43 @@
#pragma once
#include <epoxy/gl.h>
#include "AModule.hpp"
#include "cava_backend.hpp"
namespace waybar::modules::cava {
class CavaGLSL final : public AModule, public Gtk::GLArea {
public:
CavaGLSL(const std::string&, const Json::Value&);
~CavaGLSL() = default;
private:
std::shared_ptr<CavaBackend> backend_;
struct ::cava::config_params prm_;
int frame_counter{0};
bool silence_{false};
bool hide_on_silence_{false};
// Cava method
auto onUpdate(const ::cava::audio_raw& input) -> void;
auto onSilence() -> void;
// Member variable to store the shared pointer
std::shared_ptr<::cava::audio_raw> m_data_;
GLuint shaderProgram_;
// OpenGL variables
GLuint fbo_;
GLuint texture_;
GLint uniform_bars_;
GLint uniform_previous_bars_;
GLint uniform_bars_count_;
GLint uniform_time_;
// Methods
void onRealize();
bool onRender(const Glib::RefPtr<Gdk::GLContext>& context);
void initShaders();
void initSurface();
void initGLSL();
GLuint loadShader(const std::string& fileName, GLenum type);
};
} // namespace waybar::modules::cava

View File

@@ -9,20 +9,20 @@ class Cava final : public ALabel, public sigc::trackable {
public: public:
Cava(const std::string&, const Json::Value&); Cava(const std::string&, const Json::Value&);
~Cava() = default; ~Cava() = default;
auto onUpdate(const std::string& input) -> void;
auto onSilence() -> void;
auto doAction(const std::string& name) -> void override; auto doAction(const std::string& name) -> void override;
private: private:
std::shared_ptr<CavaBackend> backend_; std::shared_ptr<CavaBackend> backend_;
// Text to display // Text to display
std::string label_text_{""}; Glib::ustring label_text_{""};
bool silence_{false};
bool hide_on_silence_{false}; bool hide_on_silence_{false};
std::string format_silent_{""}; std::string format_silent_{""};
int ascii_range_{0}; int ascii_range_{0};
bool silence_{false};
// Cava method // Cava method
void pause_resume(); void pause_resume();
auto onUpdate(const std::string& input) -> void;
auto onSilence() -> void;
// ModuleActionMap // ModuleActionMap
static inline std::map<const std::string, void (waybar::modules::cava::Cava::* const)()> static inline std::map<const std::string, void (waybar::modules::cava::Cava::* const)()>
actionMap_{{"mode", &waybar::modules::cava::Cava::pause_resume}}; actionMap_{{"mode", &waybar::modules::cava::Cava::pause_resume}};

View File

@@ -32,16 +32,22 @@ class CavaBackend final {
int getAsciiRange(); int getAsciiRange();
void doPauseResume(); void doPauseResume();
void Update(); void Update();
const struct ::cava::config_params* getPrm();
std::chrono::milliseconds getFrameTimeMilsec();
// Signal accessor // Signal accessor
using type_signal_update = sigc::signal<void(const std::string&)>; using type_signal_update = sigc::signal<void(const std::string&)>;
type_signal_update signal_update(); type_signal_update signal_update();
using type_signal_audio_raw_update = sigc::signal<void(const ::cava::audio_raw&)>;
type_signal_audio_raw_update signal_audio_raw_update();
using type_signal_silence = sigc::signal<void()>; using type_signal_silence = sigc::signal<void()>;
type_signal_silence signal_silence(); type_signal_silence signal_silence();
private: private:
CavaBackend(const Json::Value& config); CavaBackend(const Json::Value& config);
util::SleeperThread thread_;
util::SleeperThread read_thread_; util::SleeperThread read_thread_;
util::SleeperThread out_thread_;
// Cava API to read audio source // Cava API to read audio source
::cava::ptr input_source_{NULL}; ::cava::ptr input_source_{NULL};
@@ -55,6 +61,7 @@ class CavaBackend final {
// Delay to handle audio source // Delay to handle audio source
std::chrono::milliseconds frame_time_milsec_{1s}; std::chrono::milliseconds frame_time_milsec_{1s};
const Json::Value& config_;
int re_paint_{0}; int re_paint_{0};
bool silence_{false}; bool silence_{false};
bool silence_prev_{false}; bool silence_prev_{false};
@@ -66,9 +73,12 @@ class CavaBackend final {
void execute(); void execute();
bool isSilence(); bool isSilence();
void doUpdate(bool force = false); void doUpdate(bool force = false);
void loadConfig();
void freeBackend();
// Signal // Signal
type_signal_update m_signal_update_; type_signal_update m_signal_update_;
type_signal_audio_raw_update m_signal_audio_raw_;
type_signal_silence m_signal_silence_; type_signal_silence m_signal_silence_;
}; };
} // namespace waybar::modules::cava } // namespace waybar::modules::cava

View File

@@ -0,0 +1,27 @@
#pragma once
#ifdef HAVE_LIBCAVA
#include "cavaRaw.hpp"
#include "cava_backend.hpp"
#ifdef HAVE_LIBCAVAGLSL
#include "cavaGLSL.hpp"
#endif
#endif
namespace waybar::modules::cava {
AModule* getModule(const std::string& id, const Json::Value& config) {
#ifdef HAVE_LIBCAVA
const std::shared_ptr<CavaBackend> backend_{waybar::modules::cava::CavaBackend::inst(config)};
switch (backend_->getPrm()->output) {
#ifdef HAVE_LIBCAVAGLSL
case ::cava::output_method::OUTPUT_SDL_GLSL:
return new waybar::modules::cava::CavaGLSL(id, config);
#endif
default:
return new waybar::modules::cava::Cava(id, config);
}
#else
throw std::runtime_error("Unknown module");
#endif
};
} // namespace waybar::modules::cava

View File

@@ -26,6 +26,7 @@ class Submap : public waybar::ALabel, public EventHandler {
const Bar& bar_; const Bar& bar_;
util::JsonParser parser_; util::JsonParser parser_;
std::string submap_; std::string submap_;
std::string prev_submap_;
bool always_on_ = false; bool always_on_ = false;
std::string default_submap_ = "Default"; std::string default_submap_ = "Default";

View File

@@ -43,6 +43,7 @@ class Workspaces : public AModule, public EventHandler {
auto moveToMonitor() const -> bool { return m_moveToMonitor; } auto moveToMonitor() const -> bool { return m_moveToMonitor; }
auto enableTaskbar() const -> bool { return m_enableTaskbar; } auto enableTaskbar() const -> bool { return m_enableTaskbar; }
auto taskbarWithIcon() const -> bool { return m_taskbarWithIcon; } auto taskbarWithIcon() const -> bool { return m_taskbarWithIcon; }
auto barScroll() const -> bool { return m_barScroll; }
auto getBarOutput() const -> std::string { return m_bar.output->name; } auto getBarOutput() const -> std::string { return m_bar.output->name; }
auto formatBefore() const -> std::string { return m_formatBefore; } auto formatBefore() const -> std::string { return m_formatBefore; }
@@ -122,6 +123,8 @@ class Workspaces : public AModule, public EventHandler {
static std::pair<std::string, std::string> splitDoublePayload(std::string const& payload); static std::pair<std::string, std::string> splitDoublePayload(std::string const& payload);
static std::tuple<std::string, std::string, std::string> splitTriplePayload( static std::tuple<std::string, std::string, std::string> splitTriplePayload(
std::string const& payload); std::string const& payload);
// scroll events
bool handleScroll(GdkEventScroll* e) override;
// Update methods // Update methods
void doUpdate(); void doUpdate();
@@ -145,6 +148,7 @@ class Workspaces : public AModule, public EventHandler {
bool m_specialVisibleOnly = false; bool m_specialVisibleOnly = false;
bool m_persistentOnly = false; bool m_persistentOnly = false;
bool m_moveToMonitor = false; bool m_moveToMonitor = false;
bool m_barScroll = false;
Json::Value m_persistentWorkspaceConfig; Json::Value m_persistentWorkspaceConfig;
// Map for windows stored in workspaces not present in the current bar. // Map for windows stored in workspaces not present in the current bar.

View File

@@ -3,6 +3,7 @@
#include <fmt/chrono.h> #include <fmt/chrono.h>
#include <gtkmm/label.h> #include <gtkmm/label.h>
#include <mutex>
#include <set> #include <set>
#include <unordered_map> #include <unordered_map>
@@ -41,6 +42,7 @@ class KeyboardState : public AModule {
struct libinput* libinput_; struct libinput* libinput_;
std::unordered_map<std::string, struct libinput_device*> libinput_devices_; std::unordered_map<std::string, struct libinput_device*> libinput_devices_;
std::mutex devices_mutex_; // protects libinput_devices_
std::set<int> binding_keys; std::set<int> binding_keys;
util::SleeperThread libinput_thread_, hotplug_thread_; util::SleeperThread libinput_thread_, hotplug_thread_;

View File

@@ -13,6 +13,7 @@
#include "ipc.hpp" #include "ipc.hpp"
#include "util/sleeper_thread.hpp" #include "util/sleeper_thread.hpp"
#include "util/SafeSignal.hpp"
namespace waybar::modules::sway { namespace waybar::modules::sway {
@@ -27,8 +28,8 @@ class Ipc {
std::string payload; std::string payload;
}; };
sigc::signal<void, const struct ipc_response &> signal_event; ::waybar::SafeSignal<const struct ipc_response&> signal_event;
sigc::signal<void, const struct ipc_response &> signal_cmd; ::waybar::SafeSignal<const struct ipc_response&> signal_cmd;
void sendCmd(uint32_t type, const std::string& payload = ""); void sendCmd(uint32_t type, const std::string& payload = "");
void subscribe(const std::string& payload); void subscribe(const std::string& payload);

View File

@@ -33,8 +33,8 @@ class Taskbar;
class Task { class Task {
public: public:
Task(const waybar::Bar &, const Json::Value &, Taskbar *, Task(const waybar::Bar&, const Json::Value&, Taskbar*, struct zwlr_foreign_toplevel_handle_v1*,
struct zwlr_foreign_toplevel_handle_v1 *, struct wl_seat *); struct wl_seat*);
~Task(); ~Task();
public: public:

View File

@@ -109,7 +109,7 @@ inline FILE* open(const std::string& cmd, int& pid, const std::string& output_na
::close(fd[0]); ::close(fd[0]);
dup2(fd[1], 1); dup2(fd[1], 1);
setpgid(child_pid, child_pid); setpgid(child_pid, child_pid);
if (output_name != "") { if (!output_name.empty()) {
setenv("WAYBAR_OUTPUT_NAME", output_name.c_str(), 1); setenv("WAYBAR_OUTPUT_NAME", output_name.c_str(), 1);
} }
execlp("/bin/sh", "sh", "-c", cmd.c_str(), (char*)0); execlp("/bin/sh", "sh", "-c", cmd.c_str(), (char*)0);
@@ -138,7 +138,7 @@ inline struct res execNoRead(const std::string& cmd) {
return {WEXITSTATUS(stat), ""}; return {WEXITSTATUS(stat), ""};
} }
inline int32_t forkExec(const std::string& cmd) { inline int32_t forkExec(const std::string& cmd, const std::string& output_name) {
if (cmd == "") return -1; if (cmd == "") return -1;
pid_t pid = fork(); pid_t pid = fork();
@@ -157,6 +157,9 @@ inline int32_t forkExec(const std::string& cmd) {
err = pthread_sigmask(SIG_UNBLOCK, &mask, nullptr); err = pthread_sigmask(SIG_UNBLOCK, &mask, nullptr);
if (err != 0) spdlog::error("pthread_sigmask in forkExec failed: {}", strerror(err)); if (err != 0) spdlog::error("pthread_sigmask in forkExec failed: {}", strerror(err));
setpgid(pid, pid); setpgid(pid, pid);
if (!output_name.empty()) {
setenv("WAYBAR_OUTPUT_NAME", output_name.c_str(), 1);
}
execl("/bin/sh", "sh", "-c", cmd.c_str(), (char*)0); execl("/bin/sh", "sh", "-c", cmd.c_str(), (char*)0);
exit(0); exit(0);
} else { } else {
@@ -169,4 +172,6 @@ inline int32_t forkExec(const std::string& cmd) {
return pid; return pid;
} }
inline int32_t forkExec(const std::string& cmd) { return forkExec(cmd, ""); }
} // namespace waybar::util::command } // namespace waybar::util::command

View File

@@ -30,15 +30,16 @@ class JsonParser {
std::istringstream jsonStream(modifiedJsonStr); std::istringstream jsonStream(modifiedJsonStr);
std::string errs; std::string errs;
if (!Json::parseFromStream(m_readerBuilder, jsonStream, &root, &errs)) { // Use local CharReaderBuilder for thread safety - the IPC singleton's
// parser can be called concurrently from multiple module threads
Json::CharReaderBuilder readerBuilder;
if (!Json::parseFromStream(readerBuilder, jsonStream, &root, &errs)) {
throw std::runtime_error("Error parsing JSON: " + errs); throw std::runtime_error("Error parsing JSON: " + errs);
} }
return root; return root;
} }
private: private:
Json::CharReaderBuilder m_readerBuilder;
static std::string replaceHexadecimalEscape(const std::string& str) { static std::string replaceHexadecimalEscape(const std::string& str) {
static std::regex re("\\\\x"); static std::regex re("\\\\x");
return std::regex_replace(str, re, "\\u00"); return std::regex_replace(str, re, "\\u00");

View File

@@ -0,0 +1,21 @@
#pragma once
#include <libudev.h>
namespace waybar::util {
struct UdevDeleter {
void operator()(udev* ptr) const { udev_unref(ptr); }
};
struct UdevDeviceDeleter {
void operator()(udev_device* ptr) const { udev_device_unref(ptr); }
};
struct UdevEnumerateDeleter {
void operator()(udev_enumerate* ptr) const { udev_enumerate_unref(ptr); }
};
struct UdevMonitorDeleter {
void operator()(udev_monitor* ptr) const { udev_monitor_unref(ptr); }
};
} // namespace waybar::util

View File

@@ -91,6 +91,11 @@ The *battery* module displays the current capacity and state (eg. charging) of y
typeof: string ++ typeof: string ++
Command to execute when scrolling up on the module. Command to execute when scrolling up on the module.
*weighted-average*: ++
typeof: bool ++
default: true ++
Option to combine multiple batteries with different capacities.
*on-scroll-down*: ++ *on-scroll-down*: ++
typeof: string ++ typeof: string ++
Command to execute when scrolling down on the module. Command to execute when scrolling down on the module.
@@ -177,6 +182,7 @@ Every entry in the *events* object consists of a *<event-name>* (typeof: *string
- *on-<status>-<state>* - *on-<status>-<state>*
- *on-<status>-<capacity>* - *on-<status>-<capacity>*
- *on-<status>*
Where: Where:
@@ -198,7 +204,9 @@ Where:
"events": { "events": {
"on-discharging-warning": "notify-send -u normal 'Low Battery'", "on-discharging-warning": "notify-send -u normal 'Low Battery'",
"on-discharging-critical": "notify-send -u critical 'Very Low Battery'", "on-discharging-critical": "notify-send -u critical 'Very Low Battery'",
"on-charging-100": "notify-send -u normal 'Battery Full!'" "on-charging-100": "notify-send -u normal 'Battery Full!'",
"on-discharging": "notify-send -u normal 'Power Switch' Discharging",
"on-charging": "notify-send -u normal 'Power Switch' Charging'"
}, },
"format": "{capacity}% {icon}", "format": "{capacity}% {icon}",
"format-icons": ["", "", "", "", ""], "format-icons": ["", "", "", "", ""],

View File

@@ -8,6 +8,8 @@ waybar - cava module
*cava* module for karlstav/cava project. See it on github: https://github.com/karlstav/cava. *cava* module for karlstav/cava project. See it on github: https://github.com/karlstav/cava.
Module supports two different frontends starting from the 0.15.0 release. The frontend that
will be used is managed by the method parameter in the [output] section of the cava configuration file.
# FILES # FILES
@@ -32,6 +34,10 @@ libcava lives in:
:[ string :[ string
:[ :[
:< Path where cava configuration file is placed to :< Path where cava configuration file is placed to
|[ *method* \[output\]
:[ string
:[
:< Manages which frontend Waybar cava module should use. Values: raw, sdl_glsl
|[ *framerate* |[ *framerate*
:[ integer :[ integer
:[ 30 :[ 30
@@ -43,7 +49,7 @@ libcava lives in:
|[ *sensitivity* |[ *sensitivity*
:[ integer :[ integer
:[ 100 :[ 100
:[ Manual sensitivity in %. It's recommended to be omitted when *autosens* = 1 :[ Manual sensitivity in %. If autosens is enabled, this will only be the initial value. 200 means double height. Accepts only non-negative values
|[ *bars* |[ *bars*
:[ integer :[ integer
:[ 12 :[ 12
@@ -68,7 +74,7 @@ libcava lives in:
:[ string :[ string
:[ :[
:[ Widget's text after sleep_timer elapsed (hide_on_silence has to be false) :[ Widget's text after sleep_timer elapsed (hide_on_silence has to be false)
|[ *method* |[ *method* \[input\]
:[ string :[ string
:[ pulse :[ pulse
:[ Audio capturing method. Possible methods are: pipewire, pulse, alsa, fifo, sndio or shmem :[ Audio capturing method. Possible methods are: pipewire, pulse, alsa, fifo, sndio or shmem
@@ -105,9 +111,9 @@ libcava lives in:
:[ false :[ false
:[ Disables or enables the so-called "Monstercat smoothing" with or without "waves" :[ Disables or enables the so-called "Monstercat smoothing" with or without "waves"
|[ *noise_reduction* |[ *noise_reduction*
:[ double :[ integer
:[ 0.77 :[ 77
:[ Range between 0 - 1. The raw visualization is very noisy, this factor adjusts the integral and gravity filters to keep the signal smooth. 1 - will be very slow and smooth, 0 - will be fast but noisy :[ Range between 0 - 100. The raw visualization is very noisy, this factor adjusts the integral and gravity filters to keep the signal smooth. 100 will be very slow and smooth, 0 will be fast but noisy
|[ *input_delay* |[ *input_delay*
:[ integer :[ integer
:[ 2 :[ 2
@@ -119,11 +125,11 @@ libcava lives in:
|[ *data_format* |[ *data_format*
:[ string :[ string
:[ asci :[ asci
:[ It's impossible to set it. Waybar sets it to = asci for internal needs :[ Raw data format. Can be 'binary' or 'ascii'
|[ *raw_target* |[ *raw_target*
:[ string :[ string
:[ /dev/stdout :[ /dev/stdout
:[ It's impossible to set it. Waybar sets it to = /dev/stdout for internal needs :[ Raw output target. A fifo will be created if target does not exist
|[ *menu* |[ *menu*
:[ string :[ string
:[ :[
@@ -136,6 +142,50 @@ libcava lives in:
:[ array :[ array
:[ :[
:[ The actions corresponding to the buttons of the menu. :[ The actions corresponding to the buttons of the menu.
|[ *bar_spacing*
:[ integer
:[
:[ Bars' space between bars in number of characters
|[ *bar_width*
:[ integer
:[
:[ Bars' width between bars in number of characters
|[ *bar_height*
:[ integer
:[
:[ Useless. bar_height is only used for output in "noritake" format
|[ *background*
:[ string
:[
:[ GLSL actual. Support hex code colors only. Must be within ''
|[ *foreground*
:[ string
:[
:[ GLSL actual. Support hex code colors only. Must be within ''
|[ *gradient*
:[ integer
:[ 0
:[ GLSL actual. Gradient mode(0/1 - on/off)
|[ *gradient_count*
:[ integer
:[ 0
:[ GLSL actual. The count of colors for the gradient
|[ *gradient_color_N*
:[ string
:[
:[ GLSL actual. N - the number of the gradient color between 1 and 8. Only hex defined colors are supported. Must be within ''
|[ *sdl_width*
:[ integer
:[
:[ GLSL actual. Manages the width of the waybar cava GLSL frontend module
|[ *sdl_height*
:[ integer
:[
:[ GLSL actual. Manages the height of the waybar cava GLSL frontend module
|[ *continuous_rendering*
:[ integer
:[ 0
:[ GLSL actual. Keep rendering even if no audio. Recommended to set to 1
Configuration can be provided as: Configuration can be provided as:
- The only cava configuration file which is provided through *cava_config*. The rest configuration can be skipped - The only cava configuration file which is provided through *cava_config*. The rest configuration can be skipped
@@ -153,6 +203,7 @@ Configuration can be provided as:
- iniparser - iniparser
- fftw3 - fftw3
- epoxy (GLSL frontend only)
# SOLVING ISSUES # SOLVING ISSUES
@@ -205,3 +256,453 @@ In case when cava releases new version and you're wanna get it, it should be rai
- *#cava* - *#cava*
- *#cava.silent* Applied after no sound has been detected for sleep_timer seconds - *#cava.silent* Applied after no sound has been detected for sleep_timer seconds
- *#cava.updated* Applied when a new frame is shown - *#cava.updated* Applied when a new frame is shown
# FRONTENDS
## RAW
The cava raw frontend uses ASCII characters to visualize incoming audio data. Each ASCII symbol position corresponds to the value of the audio power pulse.
Under the hood:
```
. Incoming audio power pulse list is : 12684
. Configured array of ASCII codes is: ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█" ]. See `format-icons` https://github.com/Alexays/Waybar/wiki/Module:-Cava#example
```
As a result cava frontend will give ▁▂▆█▄
Examples:
waybar config
```
"cava": {
"cava_config": "$XDG_CONFIG_HOME/cava/waybar_raw.conf",
"input_delay": 2,
"format-icons" : ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█" ],
"actions": {
"on-click-right": "mode"
}
},
```
waybar_raw.conf
```
## Configuration file for CAVA.
# Remove the ; to change parameters.
[general]
# Smoothing mode. Can be 'normal', 'scientific' or 'waves'. DEPRECATED as of 0.6.0
# Accepts only non-negative values.
# 'autosens' will attempt to decrease sensitivity if the bars peak. 1 = on, 0 = off
# new as of 0.6.0 autosens of low values (dynamic range)
# 'overshoot' allows bars to overshoot (in % of terminal height) without initiating autosens. DEPRECATED as of 0.6.0
# Manual sensitivity in %. If autosens is enabled, this will only be the initial value.
# 200 means double height. Accepts only non-negative values.
# The number of bars (0-512). 0 sets it to auto (fill up console).
# Bars' width and space between bars in number of characters.
bars = 12
# bar_height is only used for output in "noritake" format
# For SDL width and space between bars is in pixels, defaults are:
# sdl_glsl have these default values, they are only used to calculate max number of bars.
# Lower and higher cutoff frequencies for lowest and highest bars
# the bandwidth of the visualizer.
# Note: there is a minimum total bandwidth of 43Mhz x number of bars.
# Cava will automatically increase the higher cutoff if a too low band is specified.
# Seconds with no input before cava goes to sleep mode. Cava will not perform FFT or drawing and
# only check for input once per second. Cava will wake up once input is detected. 0 = disable.
sleep_timer = 5
[input]
# Audio capturing method. Possible methods are: 'fifo', 'portaudio', 'pipewire', 'alsa', 'pulse', 'sndio', 'oss', 'jack' or 'shmem'
# Defaults to 'oss', 'pipewire', 'sndio', 'jack', 'pulse', 'alsa', 'portaudio' or 'fifo', in that order, dependent on what support cava was built with.
# On Mac it defaults to 'portaudio' or 'fifo'
# On windows this is automatic and no input settings are needed.
#
# All input methods uses the same config variable 'source'
# to define where it should get the audio.
#
# For pulseaudio and pipewire 'source' will be the source. Default: 'auto', which uses the monitor source of the default sink
# (all pulseaudio sinks(outputs) have 'monitor' sources(inputs) associated with them).
#
# For pipewire 'source' will be the object name or object.serial of the device to capture from.
# Both input and output devices are supported.
#
# For alsa 'source' will be the capture device.
# For fifo 'source' will be the path to fifo-file.
# For shmem 'source' will be /squeezelite-AA:BB:CC:DD:EE:FF where 'AA:BB:CC:DD:EE:FF' will be squeezelite's MAC address
#
# For sndio 'source' will be a raw recording audio descriptor or a monitoring sub-device, e.g. 'rsnd/2' or 'snd/1'. Default: 'default'.
# README.md contains further information on how to setup CAVA for sndio.
#
# For oss 'source' will be the path to a audio device, e.g. '/dev/dsp2'. Default: '/dev/dsp', i.e. the default audio device.
# README.md contains further information on how to setup CAVA for OSS on FreeBSD.
#
# For jack 'source' will be the name of the JACK server to connect to, e.g. 'foobar'. Default: 'default'.
# README.md contains further information on how to setup CAVA for JACK.
#
# The options 'sample_rate', 'sample_bits', 'channels' and 'autoconnect' can be configured for some input methods:
# sample_rate: fifo, pipewire, sndio, oss
# sample_bits: fifo, pipewire, sndio, oss
# channels: sndio, oss, jack
# autoconnect: jack
# Other methods ignore these settings.
#
# For 'sndio' and 'oss' they are only preferred values, i.e. if the values are not supported
# by the chosen audio device, the device will use other supported values instead.
# Example: 48000, 32 and 2, but the device only supports 44100, 16 and 1, then it
# will use 44100, 16 and 1.
#
[output]
# Output method. Can be 'ncurses', 'noncurses', 'raw', 'noritake', 'sdl'
# or 'sdl_glsl'.
# 'noncurses' (default) uses a buffer and cursor movements to only print
# changes from frame to frame in the terminal. Uses less resources and is less
# prone to tearing (vsync issues) than 'ncurses'.
#
# 'raw' is an 8 or 16 bit (configurable via the 'bit_format' option) data
# stream of the bar heights that can be used to send to other applications.
# 'raw' defaults to 200 bars, which can be adjusted in the 'bars' option above.
#
# 'noritake' outputs a bitmap in the format expected by a Noritake VFD display
# in graphic mode. It only support the 3000 series graphical VFDs for now.
#
# 'sdl' uses the Simple DirectMedia Layer to render in a graphical context.
# 'sdl_glsl' uses SDL to create an OpenGL context. Write your own shaders or
# use one of the predefined ones.
method = raw
# Orientation of the visualization. Can be 'bottom', 'top', 'left', 'right' or
# 'horizontal'. Default is 'bottom'. 'left and 'right' are only supported on sdl
# and ncruses output. 'horizontal' (bars go up and down from center) is only supported
# on noncurses output.
# Note: many fonts have weird or missing glyphs for characters used in orientations
# other than 'bottom', which can make output not look right.
# Visual channels. Can be 'stereo' or 'mono'.
# 'stereo' mirrors both channels with low frequencies in center.
# 'mono' outputs left to right lowest to highest frequencies.
# 'mono_option' set mono to either take input from 'left', 'right' or 'average'.
# set 'reverse' to 1 to display frequencies the other way around.
# Raw output target. A fifo will be created if target does not exist.
raw_target = /dev/stdout
# Raw data format. Can be 'binary' or 'ascii'.
data_format = ascii
# Binary bit format, can be '8bit' (0-255) or '16bit' (0-65530).
# Ascii max value. In 'ascii' mode range will run from 0 to value specified here
# Ascii delimiters. In ascii format each bar and frame is separated by a delimiters.
# Use decimal value in ascii table (i.e. 59 = ';' and 10 = '\n' (line feed)).
bar_delimiter = 0
# sdl window size and position. -1,-1 is centered.
# set label on bars on the x-axis. Can be 'frequency' or 'none'. Default: 'none'
# 'frequency' displays the lower cut off frequency of the bar above.
# Only supported on ncurses and noncurses output.
# enable synchronized sync. 1 = on, 0 = off
# removes flickering in alacritty terminal emulator.
# defaults to off since the behaviour in other terminal emulators is unknown
# Shaders for sdl_glsl, located in $HOME/.config/cava/shaders
; for glsl output mode, keep rendering even if no audio
# disable console blank (screen saver) in tty
# (Not supported on FreeBSD)
# show a flat bar at the bottom of the screen when idle, 1 = on, 0 = off
# show waveform instead of frequency spectrum, 1 = on, 0 = off
[color]
# Colors can be one of seven predefined: black, blue, cyan, green, magenta, red, white, yellow.
# Or defined by hex code '#xxxxxx' (hex code must be within ''). User defined colors requires
# a terminal that can change color definitions such as Gnome-terminal or rxvt.
# default is to keep current terminal color
# SDL and sdl_glsl only support hex code colors, these are the default:
# Gradient mode, only hex defined colors are supported,
# background must also be defined in hex or remain commented out. 1 = on, 0 = off.
# You can define as many as 8 different colors. They range from bottom to top of screen
[smoothing]
# Disables or enables the so-called "Monstercat smoothing" with or without "waves". Set to 0 to disable.
# Noise reduction, int 0 - 100. default 77
# the raw visualization is very noisy, this factor adjusts the integral and gravity filters to keep the signal smooth
# 100 will be very slow and smooth, 0 will be fast but noisy.
[eq]
# This one is tricky. You can have as much keys as you want.
# Remember to uncomment more than one key! More keys = more precision.
# Look at readme.md on github for further explanations and examples.
```
## GLSL
The Cava GLSL frontend delegates the visualization of incoming audio data to the GPU via OpenGL.
There are some mandatory dependencies that need to be satisfied in order for Cava GLSL to be built and function properly:
. epoxy library must be installed on the system
. Vertex and fragment shaders from the original project must be used. They should be downloaded, and the file paths must be configured correctly in the Waybar Cava configuration:
1. cava shaders [cava shaders](https://github.com/karlstav/cava/tree/master/output/shaders)
2. libcava shaders [libcava shaders](https://github.com/LukashonakV/cava/tree/master/output/shaders)
. It is highly recommended to have a separate cava configuration for the Waybar Cava GLSL module and to use this as the cava_config in the Waybar configuration.
. It is common for cava configurations to be placed in the XDG_CONFIG_HOME directory, including shaders as well. Consider keeping them in the $XDG_CONFIG_HOME/cava/shaders folder.
Key configuration options:
. bars. The more values the parameter has, the more interesting the visualization becomes.
. method in output section must be set to sdl_glsl
. sdl_width and sdl_height manage the size of the module. Adjust them according to your needs.
. Shaders for sdl_glsl, located in $HOME/.config/cava/shaders. Example: "vertex_shader" = "pass_through.vert" "fragment_shader" = "spectrogram.frag"
. Set continuous_rendering to 1 to enable smooth rendering; set it to 0 otherwise. It is recommended to keep it set to 1.
. background, foreground, and gradient_color_N (where N is a number between 1 and 8) must be defined using hex code
Example:
waybar config
```
"cava": {
"cava_config": "$XDG_CONFIG_HOME/cava/waybar_cava#3.conf",
"input_delay": 2,
"actions": {
"on-click-right": "mode"
}
},
```
waybar_raw.conf
```
## Configuration file for CAVA.
# Remove the ; to change parameters.
[general]
# Smoothing mode. Can be 'normal', 'scientific' or 'waves'. DEPRECATED as of 0.6.0
# Accepts only non-negative values.
# 'autosens' will attempt to decrease sensitivity if the bars peak. 1 = on, 0 = off
# new as of 0.6.0 autosens of low values (dynamic range)
# 'overshoot' allows bars to overshoot (in % of terminal height) without initiating autosens. DEPRECATED as of 0.6.0
# Manual sensitivity in %. If autosens is enabled, this will only be the initial value.
# 200 means double height. Accepts only non-negative values.
# The number of bars (0-512). 0 sets it to auto (fill up console).
# Bars' width and space between bars in number of characters.
bars = 50
# bar_height is only used for output in "noritake" format
# For SDL width and space between bars is in pixels, defaults are:
# sdl_glsl have these default values, they are only used to calculate max number of bars.
# Lower and higher cutoff frequencies for lowest and highest bars
# the bandwidth of the visualizer.
# Note: there is a minimum total bandwidth of 43Mhz x number of bars.
# Cava will automatically increase the higher cutoff if a too low band is specified.
# Seconds with no input before cava goes to sleep mode. Cava will not perform FFT or drawing and
# only check for input once per second. Cava will wake up once input is detected. 0 = disable.
sleep_timer = 5
[input]
# Audio capturing method. Possible methods are: 'fifo', 'portaudio', 'pipewire', 'alsa', 'pulse', 'sndio', 'oss', 'jack' or 'shmem'
# Defaults to 'oss', 'pipewire', 'sndio', 'jack', 'pulse', 'alsa', 'portaudio' or 'fifo', in that order, dependent on what support cava was built with.
# On Mac it defaults to 'portaudio' or 'fifo'
# On windows this is automatic and no input settings are needed.
#
# All input methods uses the same config variable 'source'
# to define where it should get the audio.
#
# For pulseaudio and pipewire 'source' will be the source. Default: 'auto', which uses the monitor source of the default sink
# (all pulseaudio sinks(outputs) have 'monitor' sources(inputs) associated with them).
#
# For pipewire 'source' will be the object name or object.serial of the device to capture from.
# Both input and output devices are supported.
#
# For alsa 'source' will be the capture device.
# For fifo 'source' will be the path to fifo-file.
# For shmem 'source' will be /squeezelite-AA:BB:CC:DD:EE:FF where 'AA:BB:CC:DD:EE:FF' will be squeezelite's MAC address
#
# For sndio 'source' will be a raw recording audio descriptor or a monitoring sub-device, e.g. 'rsnd/2' or 'snd/1'. Default: 'default'.
# README.md contains further information on how to setup CAVA for sndio.
#
# For oss 'source' will be the path to a audio device, e.g. '/dev/dsp2'. Default: '/dev/dsp', i.e. the default audio device.
# README.md contains further information on how to setup CAVA for OSS on FreeBSD.
#
# For jack 'source' will be the name of the JACK server to connect to, e.g. 'foobar'. Default: 'default'.
# README.md contains further information on how to setup CAVA for JACK.
#
# The options 'sample_rate', 'sample_bits', 'channels' and 'autoconnect' can be configured for some input methods:
# sample_rate: fifo, pipewire, sndio, oss
# sample_bits: fifo, pipewire, sndio, oss
# channels: sndio, oss, jack
# autoconnect: jack
# Other methods ignore these settings.
#
# For 'sndio' and 'oss' they are only preferred values, i.e. if the values are not supported
# by the chosen audio device, the device will use other supported values instead.
# Example: 48000, 32 and 2, but the device only supports 44100, 16 and 1, then it
# will use 44100, 16 and 1.
#
[output]
# Output method. Can be 'ncurses', 'noncurses', 'raw', 'noritake', 'sdl'
# or 'sdl_glsl'.
# 'noncurses' (default) uses a buffer and cursor movements to only print
# changes from frame to frame in the terminal. Uses less resources and is less
# prone to tearing (vsync issues) than 'ncurses'.
#
# 'raw' is an 8 or 16 bit (configurable via the 'bit_format' option) data
# stream of the bar heights that can be used to send to other applications.
# 'raw' defaults to 200 bars, which can be adjusted in the 'bars' option above.
#
# 'noritake' outputs a bitmap in the format expected by a Noritake VFD display
# in graphic mode. It only support the 3000 series graphical VFDs for now.
#
# 'sdl' uses the Simple DirectMedia Layer to render in a graphical context.
# 'sdl_glsl' uses SDL to create an OpenGL context. Write your own shaders or
# use one of the predefined ones.
method = sdl_glsl
# Orientation of the visualization. Can be 'bottom', 'top', 'left', 'right' or
# 'horizontal'. Default is 'bottom'. 'left and 'right' are only supported on sdl
# and ncruses output. 'horizontal' (bars go up and down from center) is only supported
# on noncurses output.
# Note: many fonts have weird or missing glyphs for characters used in orientations
# other than 'bottom', which can make output not look right.
# Visual channels. Can be 'stereo' or 'mono'.
# 'stereo' mirrors both channels with low frequencies in center.
# 'mono' outputs left to right lowest to highest frequencies.
# 'mono_option' set mono to either take input from 'left', 'right' or 'average'.
# set 'reverse' to 1 to display frequencies the other way around.
# Raw output target. A fifo will be created if target does not exist.
# Raw data format. Can be 'binary' or 'ascii'.
# Binary bit format, can be '8bit' (0-255) or '16bit' (0-65530).
# Ascii max value. In 'ascii' mode range will run from 0 to value specified here
# Ascii delimiters. In ascii format each bar and frame is separated by a delimiters.
# Use decimal value in ascii table (i.e. 59 = ';' and 10 = '\n' (line feed)).
bar_delimiter = 0
# sdl window size and position. -1,-1 is centered.
sdl_width = 150
sdl_height = 39
# set label on bars on the x-axis. Can be 'frequency' or 'none'. Default: 'none'
# 'frequency' displays the lower cut off frequency of the bar above.
# Only supported on ncurses and noncurses output.
# enable synchronized sync. 1 = on, 0 = off
# removes flickering in alacritty terminal emulator.
# defaults to off since the behaviour in other terminal emulators is unknown
# Shaders for sdl_glsl, located in $HOME/.config/cava/shaders
vertex_shader = pass_through.vert
fragment_shader = bar_spectrum.frag
; for glsl output mode, keep rendering even if no audio
continuous_rendering = 1;
# disable console blank (screen saver) in tty
# (Not supported on FreeBSD)
# show a flat bar at the bottom of the screen when idle, 1 = on, 0 = off
# show waveform instead of frequency spectrum, 1 = on, 0 = off
[color]
# Colors can be one of seven predefined: black, blue, cyan, green, magenta, red, white, yellow.
# Or defined by hex code '#xxxxxx' (hex code must be within ''). User defined colors requires
# a terminal that can change color definitions such as Gnome-terminal or rxvt.
# default is to keep current terminal color
# SDL and sdl_glsl only support hex code colors, these are the default:
background = '#282C34'
# Gradient mode, only hex defined colors are supported,
# background must also be defined in hex or remain commented out. 1 = on, 0 = off.
# You can define as many as 8 different colors. They range from bottom to top of screen
gradient = 1
gradient_count = 2
gradient_color_1 = '#282C34'
gradient_color_2 = '#45475A'
; gradient_color_1 = '#59cc33'
; gradient_color_2 = '#80cc33'
gradient_color_3 = '#a6cc33'
gradient_color_4 = '#cccc33'
gradient_color_5 = '#cca633'
gradient_color_6 = '#cc8033'
gradient_color_7 = '#cc5933'
gradient_color_8 = '#cc3333'
[smoothing]
# Percentage value for integral smoothing. Takes values from 0 - 100.
# Higher values means smoother, but less precise. 0 to disable.
# DEPRECATED as of 0.8.0, use noise_reduction instead
# Disables or enables the so-called "Monstercat smoothing" with or without "waves". Set to 0 to disable.
# Set gravity percentage for "drop off". Higher values means bars will drop faster.
# Accepts only non-negative values. 50 means half gravity, 200 means double. Set to 0 to disable "drop off".
# DEPRECATED as of 0.8.0, use noise_reduction instead
# In bar height, bars that would have been lower that this will not be drawn.
# DEPRECATED as of 0.8.0
# Noise reduction, int 0 - 100. default 77
# the raw visualization is very noisy, this factor adjusts the integral and gravity filters to keep the signal smooth
# 100 will be very slow and smooth, 0 will be fast but noisy.
[eq]
# This one is tricky. You can have as much keys as you want.
# Remember to uncomment more than one key! More keys = more precision.
# Look at readme.md on github for further explanations and examples.
```
Different waybar_cava#N.conf see at [cava GLSL](https://github.com/Alexays/Waybar/wiki/Module:-Cava:-GLSL)

View File

@@ -234,7 +234,6 @@ $text\\n$tooltip\\n$class*
``` ```
"custom/pacman": { "custom/pacman": {
"format": "{text} ", "format": "{text} ",
"interval": 3600, // every hour
"exec": "checkupdates | wc -l", // # of updates "exec": "checkupdates | wc -l", // # of updates
"exec-if": "exit 0", // always run; consider advanced run conditions "exec-if": "exit 0", // always run; consider advanced run conditions
"on-click": "termite -e 'sudo pacman -Syu'; pkill -SIGRTMIN+8 waybar", // update system "on-click": "termite -e 'sudo pacman -Syu'; pkill -SIGRTMIN+8 waybar", // update system
@@ -242,7 +241,7 @@ $text\\n$tooltip\\n$class*
} }
``` ```
You can use the signal and update the number of available packages with *pkill -RTMIN+8 waybar*. Under the premise that interval is not defined, you can use the signal and update the number of available packages with *pkill -RTMIN+8 waybar*.
# STYLE # STYLE

View File

@@ -43,6 +43,7 @@ Addressed by *dwl/tags*
- *#tags button* - *#tags button*
- *#tags button.occupied* - *#tags button.occupied*
- *#tags button.empty*
- *#tags button.focused* - *#tags button.focused*
- *#tags button.urgent* - *#tags button.urgent*

View File

@@ -130,6 +130,11 @@ This setting is ignored if *workspace-taskbar.enable* is set to true.
Otherwise, the workspace will open on the monitor where it was previously assigned. Otherwise, the workspace will open on the monitor where it was previously assigned.
Analog to using `focusworkspaceoncurrentmonitor` dispatcher instead of `workspace` in Hyprland. Analog to using `focusworkspaceoncurrentmonitor` dispatcher instead of `workspace` in Hyprland.
*enable-bar-scroll*: ++
typeof: bool ++
default: false ++
If set to false, you can't scroll to cycle throughout workspaces from the entire bar. If set to true this behaviour is enabled.
*ignore-workspaces*: ++ *ignore-workspaces*: ++
typeof: array ++ typeof: array ++
default: [] ++ default: [] ++

View File

@@ -26,7 +26,8 @@ The *image* module displays an image from a path.
*interval*: ++ *interval*: ++
typeof: integer or float ++ typeof: integer or float ++
The interval (in seconds) to re-render the image. ++ The interval (in seconds) to re-render the image. ++
Minimum value is 0.001 (1ms). Values smaller than 1ms will be set to 1ms. ++ If set to a positive value, the minimum is 0.001 (1ms). Values smaller than 1ms will be set to 1ms. ++
Zero or negative values are treated as "once". ++
This is useful if the contents of *path* changes. ++ This is useful if the contents of *path* changes. ++
If no *interval* is defined, the image will only be rendered once. If no *interval* is defined, the image will only be rendered once.

View File

@@ -97,7 +97,11 @@ Additionally, you can control the volume by scrolling *up* or *down* while the c
*reverse-scrolling*: ++ *reverse-scrolling*: ++
typeof: bool ++ typeof: bool ++
Option to reverse the scroll direction. Option to reverse the scroll direction for touchpads.
*reverse-mouse-scrolling*: ++
typeof: bool ++
Option to reverse the scroll direction for mice.
*tooltip*: ++ *tooltip*: ++
typeof: bool ++ typeof: bool ++
@@ -173,8 +177,8 @@ to be selected when the corresponding audio device is muted. This applies to *de
"format-icons": { "format-icons": {
"alsa_output.pci-0000_00_1f.3.analog-stereo": "", "alsa_output.pci-0000_00_1f.3.analog-stereo": "",
"alsa_output.pci-0000_00_1f.3.analog-stereo-muted": "", "alsa_output.pci-0000_00_1f.3.analog-stereo-muted": "",
"headphones": "", "headphone": "",
"handsfree": "", "hands-free": "",
"headset": "", "headset": "",
"phone": "", "phone": "",
"phone-muted": "", "phone-muted": "",

View File

@@ -179,7 +179,6 @@ n.b.: the list of outputs can be obtained from command line using *swaymsg -t ge
"format": "<span size='larger'>{name}</span> {windows}", "format": "<span size='larger'>{name}</span> {windows}",
"format-window-separator": " | ", "format-window-separator": " | ",
"window-rewrite-default": "{name}", "window-rewrite-default": "{name}",
"window-format": "<span color='#e0e0e0'>{name}</span>",
"window-rewrite": { "window-rewrite": {
"class<firefox>": "", "class<firefox>": "",
"class<kitty>": "k", "class<kitty>": "k",

View File

@@ -36,7 +36,7 @@ The visual display elements for waybar use a CSS stylesheet, see *waybar-styles(
*expand-right* ++ *expand-right* ++
typeof: bool ++ typeof: bool ++
default: false ++ default: false ++
Enables the modules-left to consume all left over space dynamically. Enables the modules-right to consume all left over space dynamically.
*layer* ++ *layer* ++
typeof: string ++ typeof: string ++

View File

@@ -1,6 +1,6 @@
project( project(
'waybar', 'cpp', 'c', 'waybar', 'cpp', 'c',
version: '0.14.0', version: '0.15.0',
license: 'MIT', license: 'MIT',
meson_version: '>= 0.59.0', meson_version: '>= 0.59.0',
default_options : [ default_options : [
@@ -497,16 +497,24 @@ else
man_files += files('man/waybar-clock.5.scd') man_files += files('man/waybar-clock.5.scd')
endif endif
cava = dependency('cava', cava = dependency('libcava',
version : '>=0.10.6', version : '>=0.10.7',
required: get_option('cava'), required: get_option('cava'),
fallback : ['cava', 'cava_dep'], fallback : ['libcava', 'cava_dep'],
not_found_message: 'cava is not found. Building waybar without cava') not_found_message: 'cava is not found. Building waybar without cava')
eproxy = dependency('epoxy', required: false)
if cava.found() if cava.found()
add_project_arguments('-DHAVE_LIBCAVA', language: 'cpp') add_project_arguments('-DHAVE_LIBCAVA', language: 'cpp')
src_files += files('src/modules/cava/cava.cpp', 'src/modules/cava/cava_backend.cpp') src_files += files('src/modules/cava/cavaRaw.cpp',
'src/modules/cava/cava_backend.cpp')
man_files += files('man/waybar-cava.5.scd') man_files += files('man/waybar-cava.5.scd')
if eproxy.found()
add_project_arguments('-DHAVE_LIBCAVAGLSL', language: 'cpp')
src_files += files('src/modules/cava/cavaGLSL.cpp')
endif
endif endif
if libgps.found() if libgps.found()
@@ -554,6 +562,7 @@ executable(
tz_dep, tz_dep,
xkbregistry, xkbregistry,
cava, cava,
eproxy,
libgps libgps
], ],
include_directories: inc_dirs, include_directories: inc_dirs,

View File

@@ -5,13 +5,14 @@
version, version,
}: }:
let let
libcava = { libcava = rec {
version = "0.10.7-beta";
src = pkgs.fetchFromGitHub { src = pkgs.fetchFromGitHub {
owner = "LukashonakV"; owner = "LukashonakV";
repo = "cava"; repo = "cava";
# NOTE: Needs to match the cava.wrap # NOTE: Needs to match the cava.wrap
rev = "23efcced43b5a395747b18a2e5f2171fc0925d18"; tag = "v${version}";
hash = "sha256-CNspaoK5KuME0GfaNijpC24BfALngzNi04/VNwPqMvo="; hash = "sha256-IX1B375gTwVDRjpRfwKGuzTAZOV2pgDWzUd4bW2cTDU=";
}; };
}; };
in in
@@ -39,7 +40,7 @@ waybar.overrideAttrs (oldAttrs: {
postUnpack = '' postUnpack = ''
pushd "$sourceRoot" pushd "$sourceRoot"
cp -R --no-preserve=mode,ownership ${libcava.src} subprojects/cava cp -R --no-preserve=mode,ownership ${libcava.src} subprojects/cava-${libcava.version}
patchShebangs . patchShebangs .
popd popd
''; '';

View File

@@ -151,7 +151,7 @@ void AAppIconLabel::updateAppIconName(const std::string& app_identifier,
} }
void AAppIconLabel::updateAppIcon() { void AAppIconLabel::updateAppIcon() {
if (update_app_icon_) { if (update_app_icon_ || (!iconEnabled() && image_.get_visible())) {
update_app_icon_ = false; update_app_icon_ = false;
if (app_icon_name_.empty()) { if (app_icon_name_.empty()) {
image_.set_visible(false); image_.set_visible(false);

View File

@@ -190,7 +190,7 @@ bool waybar::ALabel::handleToggle(GdkEventButton* const& e) {
} }
void ALabel::handleGtkMenuEvent(GtkMenuItem* /*menuitem*/, gpointer data) { void ALabel::handleGtkMenuEvent(GtkMenuItem* /*menuitem*/, gpointer data) {
waybar::util::command::res res = waybar::util::command::exec((char*)data, "GtkMenu"); waybar::util::command::forkExec((char*)data, "GtkMenu");
} }
std::string ALabel::getState(uint8_t value, bool lesser) { std::string ALabel::getState(uint8_t value, bool lesser) {

View File

@@ -229,7 +229,8 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config)
gtk_layer_init_for_window(gtk_window); gtk_layer_init_for_window(gtk_window);
gtk_layer_set_keyboard_mode(gtk_window, GTK_LAYER_SHELL_KEYBOARD_MODE_NONE); gtk_layer_set_keyboard_mode(gtk_window, GTK_LAYER_SHELL_KEYBOARD_MODE_NONE);
gtk_layer_set_monitor(gtk_window, output->monitor->gobj()); gtk_layer_set_monitor(gtk_window, output->monitor->gobj());
gtk_layer_set_namespace(gtk_window, "waybar"); gtk_layer_set_namespace(gtk_window,
config["name"].isString() ? config["name"].asCString() : "waybar");
gtk_layer_set_margin(gtk_window, GTK_LAYER_SHELL_EDGE_LEFT, margins_.left); gtk_layer_set_margin(gtk_window, GTK_LAYER_SHELL_EDGE_LEFT, margins_.left);
gtk_layer_set_margin(gtk_window, GTK_LAYER_SHELL_EDGE_RIGHT, margins_.right); gtk_layer_set_margin(gtk_window, GTK_LAYER_SHELL_EDGE_RIGHT, margins_.right);

View File

@@ -219,13 +219,22 @@ void waybar::Client::bindInterfaces() {
if (xdg_output_manager == nullptr) { if (xdg_output_manager == nullptr) {
throw std::runtime_error("Failed to acquire required resources."); throw std::runtime_error("Failed to acquire required resources.");
} }
// Disconnect previous signal handlers to prevent duplicate handlers on reload
monitor_added_connection_.disconnect();
monitor_removed_connection_.disconnect();
// Clear stale outputs from previous run
outputs_.clear();
// add existing outputs and subscribe to updates // add existing outputs and subscribe to updates
for (auto i = 0; i < gdk_display->get_n_monitors(); ++i) { for (auto i = 0; i < gdk_display->get_n_monitors(); ++i) {
auto monitor = gdk_display->get_monitor(i); auto monitor = gdk_display->get_monitor(i);
handleMonitorAdded(monitor); handleMonitorAdded(monitor);
} }
gdk_display->signal_monitor_added().connect(sigc::mem_fun(*this, &Client::handleMonitorAdded)); monitor_added_connection_ = gdk_display->signal_monitor_added().connect(
gdk_display->signal_monitor_removed().connect( sigc::mem_fun(*this, &Client::handleMonitorAdded));
monitor_removed_connection_ = gdk_display->signal_monitor_removed().connect(
sigc::mem_fun(*this, &Client::handleMonitorRemoved)); sigc::mem_fun(*this, &Client::handleMonitorRemoved));
} }

View File

@@ -108,15 +108,13 @@
#ifdef HAVE_LIBWIREPLUMBER #ifdef HAVE_LIBWIREPLUMBER
#include "modules/wireplumber.hpp" #include "modules/wireplumber.hpp"
#endif #endif
#ifdef HAVE_LIBCAVA
#include "modules/cava/cava.hpp"
#endif
#ifdef HAVE_SYSTEMD_MONITOR #ifdef HAVE_SYSTEMD_MONITOR
#include "modules/systemd_failed_units.hpp" #include "modules/systemd_failed_units.hpp"
#endif #endif
#ifdef HAVE_LIBGPS #ifdef HAVE_LIBGPS
#include "modules/gps.hpp" #include "modules/gps.hpp"
#endif #endif
#include "modules/cava/cava_frontend.hpp"
#include "modules/cffi.hpp" #include "modules/cffi.hpp"
#include "modules/custom.hpp" #include "modules/custom.hpp"
#include "modules/image.hpp" #include "modules/image.hpp"
@@ -341,11 +339,9 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name,
return new waybar::modules::Wireplumber(id, config_[name]); return new waybar::modules::Wireplumber(id, config_[name]);
} }
#endif #endif
#ifdef HAVE_LIBCAVA
if (ref == "cava") { if (ref == "cava") {
return new waybar::modules::cava::Cava(id, config_[name]); return waybar::modules::cava::getModule(id, config_[name]);
} }
#endif
#ifdef HAVE_SYSTEMD_MONITOR #ifdef HAVE_SYSTEMD_MONITOR
if (ref == "systemd-failed-units") { if (ref == "systemd-failed-units") {
return new waybar::modules::SystemdFailedUnits(id, config_[name]); return new waybar::modules::SystemdFailedUnits(id, config_[name]);

View File

@@ -26,7 +26,7 @@ Gtk::RevealerTransitionType getPreferredTransitionType(bool is_vertical) {
Group::Group(const std::string& name, const std::string& id, const Json::Value& config, Group::Group(const std::string& name, const std::string& id, const Json::Value& config,
bool vertical) bool vertical)
: AModule(config, name, id, true, true), : AModule(config, name, id, true, false),
box{vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0}, box{vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0},
revealer_box{vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0} { revealer_box{vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0} {
box.set_name(name_); box.set_name(name_);

View File

@@ -58,11 +58,11 @@ auto waybar::modules::Backlight::update() -> void {
tooltip_format = config_["tooltip-format"].asString(); tooltip_format = config_["tooltip-format"].asString();
} }
if (!tooltip_format.empty()) { if (!tooltip_format.empty()) {
label_.set_tooltip_text(fmt::format(fmt::runtime(tooltip_format), label_.set_tooltip_markup(fmt::format(fmt::runtime(tooltip_format),
fmt::arg("percent", percent), fmt::arg("percent", percent),
fmt::arg("icon", getIcon(percent)))); fmt::arg("icon", getIcon(percent))));
} else { } else {
label_.set_tooltip_text(desc); label_.set_tooltip_markup(desc);
} }
} }
} else { } else {

View File

@@ -7,7 +7,10 @@
#if defined(__FreeBSD__) #if defined(__FreeBSD__)
#include <sys/sysctl.h> #include <sys/sysctl.h>
#endif #endif
#include <libudev.h>
#include <poll.h>
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
#include <sys/signalfd.h>
waybar::modules::Battery::Battery(const std::string& id, const Bar& bar, const Json::Value& config) waybar::modules::Battery::Battery(const std::string& id, const Bar& bar, const Json::Value& config)
: ALabel(config, "battery", id, "{capacity}%", 60), last_event_(""), bar_(bar) { : ALabel(config, "battery", id, "{capacity}%", 60), last_event_(""), bar_(bar) {
@@ -16,17 +19,21 @@ waybar::modules::Battery::Battery(const std::string& id, const Bar& bar, const J
if (battery_watch_fd_ == -1) { if (battery_watch_fd_ == -1) {
throw std::runtime_error("Unable to listen batteries."); throw std::runtime_error("Unable to listen batteries.");
} }
udev_ = std::unique_ptr<udev, util::UdevDeleter>(udev_new());
global_watch_fd_ = inotify_init1(IN_CLOEXEC); if (udev_ == nullptr) {
if (global_watch_fd_ == -1) { throw std::runtime_error("udev_new failed");
throw std::runtime_error("Unable to listen batteries.");
} }
mon_ = std::unique_ptr<udev_monitor, util::UdevMonitorDeleter>(
// Watch the directory for any added or removed batteries udev_monitor_new_from_netlink(udev_.get(), "kernel"));
global_watch = inotify_add_watch(global_watch_fd_, data_dir_.c_str(), IN_CREATE | IN_DELETE); if (mon_ == nullptr) {
if (global_watch < 0) { throw std::runtime_error("udev monitor new failed");
throw std::runtime_error("Could not watch for battery plug/unplug");
} }
if (udev_monitor_filter_add_match_subsystem_devtype(mon_.get(), "power_supply", nullptr) < 0) {
throw std::runtime_error("udev failed to add monitor filter");
}
udev_monitor_enable_receiving(mon_.get());
if (config_["weighted-average"].isBool()) weightedAverage_ = config_["weighted-average"].asBool();
#endif #endif
spdlog::debug("battery: worker interval is {}", interval_.count()); spdlog::debug("battery: worker interval is {}", interval_.count());
worker(); worker();
@@ -36,11 +43,6 @@ waybar::modules::Battery::~Battery() {
#if defined(__linux__) #if defined(__linux__)
std::lock_guard<std::mutex> guard(battery_list_mutex_); std::lock_guard<std::mutex> guard(battery_list_mutex_);
if (global_watch >= 0) {
inotify_rm_watch(global_watch_fd_, global_watch);
}
close(global_watch_fd_);
for (auto it = batteries_.cbegin(), next_it = it; it != batteries_.cend(); it = next_it) { for (auto it = batteries_.cbegin(), next_it = it; it != batteries_.cend(); it = next_it) {
++next_it; ++next_it;
auto watch_id = (*it).second; auto watch_id = (*it).second;
@@ -77,12 +79,18 @@ void waybar::modules::Battery::worker() {
dp.emit(); dp.emit();
}; };
thread_battery_update_ = [this] { thread_battery_update_ = [this] {
struct inotify_event event = {0}; poll_fds_[0].revents = 0;
int nbytes = read(global_watch_fd_, &event, sizeof(event)); poll_fds_[0].events = POLLIN;
if (nbytes != sizeof(event) || event.mask & IN_IGNORED) { poll_fds_[0].fd = udev_monitor_get_fd(mon_.get());
int ret = poll(poll_fds_.data(), poll_fds_.size(), -1);
if (ret < 0) {
thread_.stop(); thread_.stop();
return; return;
} }
if ((poll_fds_[0].revents & POLLIN) != 0) {
signalfd_siginfo signal_info;
read(poll_fds_[0].fd, &signal_info, sizeof(signal_info));
}
refreshBatteries(); refreshBatteries();
dp.emit(); dp.emit();
}; };
@@ -585,8 +593,7 @@ waybar::modules::Battery::getInfos() {
} }
// Handle weighted-average // Handle weighted-average
if ((config_["weighted-average"].isBool() ? config_["weighted-average"].asBool() : false) && if (weightedAverage_ && total_energy_exists && total_energy_full_exists) {
total_energy_exists && total_energy_full_exists) {
if (total_energy_full > 0.0f) if (total_energy_full > 0.0f)
calculated_capacity = ((float)total_energy * 100.0f / (float)total_energy_full); calculated_capacity = ((float)total_energy * 100.0f / (float)total_energy_full);
} }
@@ -679,6 +686,7 @@ auto waybar::modules::Battery::update() -> void {
status = getAdapterStatus(capacity); status = getAdapterStatus(capacity);
} }
auto status_pretty = status; auto status_pretty = status;
// Transform to lowercase and replace space with dash // Transform to lowercase and replace space with dash
std::ranges::transform(status.begin(), status.end(), status.begin(), std::ranges::transform(status.begin(), status.end(), status.begin(),
[](char ch) { return ch == ' ' ? '-' : std::tolower(ch); }); [](char ch) { return ch == ' ' ? '-' : std::tolower(ch); });
@@ -782,16 +790,19 @@ void waybar::modules::Battery::processEvents(std::string& state, std::string& st
if (!events.isObject() || events.empty()) { if (!events.isObject() || events.empty()) {
return; return;
} }
std::string event_name = fmt::format("on-{}-{}", status == "discharging" ? status : "charging", auto exec = [](Json::Value const& event) {
state.empty() ? std::to_string(capacity) : state); if (!event.isString()) return;
if (auto command = event.asString(); !command.empty()) {
util::command::exec(command, "");
}
};
std::string status_name = status == "discharging" ? "on-discharging" : "on-charging";
std::string event_name = status_name + '-' + (state.empty() ? std::to_string(capacity) : state);
if (last_event_ != event_name) { if (last_event_ != event_name) {
spdlog::debug("battery: triggering event {}", event_name); spdlog::debug("battery: triggering event {}", event_name);
if (events[event_name].isString()) { exec(events[event_name]);
std::string exec = events[event_name].asString(); if (!last_event_.empty() && last_event_[3] != event_name[3]) {
// Execute the command if it is not empty exec(events[status_name]);
if (!exec.empty()) {
util::command::exec(exec, "");
}
} }
last_event_ = event_name; last_event_ = event_name;
} }

View File

@@ -264,7 +264,7 @@ auto waybar::modules::Bluetooth::update() -> void {
device_enumerate_.erase(0, 1); device_enumerate_.erase(0, 1);
} }
} }
label_.set_tooltip_text(fmt::format( label_.set_tooltip_markup(fmt::format(
fmt::runtime(tooltip_format), fmt::arg("status", state_), fmt::runtime(tooltip_format), fmt::arg("status", state_),
fmt::arg("num_connections", connected_devices_.size()), fmt::arg("num_connections", connected_devices_.size()),
fmt::arg("controller_address", cur_controller_ ? cur_controller_->address : "null"), fmt::arg("controller_address", cur_controller_ ? cur_controller_->address : "null"),

View File

@@ -0,0 +1,271 @@
#include "modules/cava/cavaGLSL.hpp"
#include <spdlog/spdlog.h>
#include <fstream>
waybar::modules::cava::CavaGLSL::CavaGLSL(const std::string& id, const Json::Value& config)
: AModule(config, "cavaGLSL", id, false, false),
backend_{waybar::modules::cava::CavaBackend::inst(config)} {
set_name(name_);
if (config_["hide_on_silence"].isBool()) hide_on_silence_ = config_["hide_on_silence"].asBool();
if (!id.empty()) {
get_style_context()->add_class(id);
}
get_style_context()->add_class(MODULE_CLASS);
set_use_es(true);
// set_auto_render(true);
signal_realize().connect(sigc::mem_fun(*this, &CavaGLSL::onRealize));
signal_render().connect(sigc::mem_fun(*this, &CavaGLSL::onRender), false);
// Get parameters_config struct from the backend
prm_ = *backend_->getPrm();
// Set widget length
int length{0};
if (config_["min-length"].isUInt())
length = config_["min-length"].asUInt();
else if (config_["max-length"].isUInt())
length = config_["max-length"].asUInt();
else
length = prm_.sdl_width;
set_size_request(length, prm_.sdl_height);
// Subscribe for changes
backend_->signal_audio_raw_update().connect(sigc::mem_fun(*this, &CavaGLSL::onUpdate));
// Subscribe for silence
backend_->signal_silence().connect(sigc::mem_fun(*this, &CavaGLSL::onSilence));
event_box_.add(*this);
}
auto waybar::modules::cava::CavaGLSL::onUpdate(const ::cava::audio_raw& input) -> void {
Glib::signal_idle().connect_once([this, input]() {
m_data_ = std::make_shared<::cava::audio_raw>(input);
if (silence_) {
get_style_context()->remove_class("silent");
if (!get_style_context()->has_class("updated")) get_style_context()->add_class("updated");
show();
silence_ = false;
}
queue_render();
});
}
auto waybar::modules::cava::CavaGLSL::onSilence() -> void {
Glib::signal_idle().connect_once([this]() {
if (!silence_) {
if (get_style_context()->has_class("updated")) get_style_context()->remove_class("updated");
if (hide_on_silence_) hide();
silence_ = true;
get_style_context()->add_class("silent");
// Set clear color to black
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
queue_render();
}
});
}
bool waybar::modules::cava::CavaGLSL::onRender(const Glib::RefPtr<Gdk::GLContext>& context) {
if (!m_data_) return true;
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture_);
glUniform1i(glGetUniformLocation(shaderProgram_, "inputTexture"), 0);
glUniform1fv(uniform_bars_, m_data_->number_of_bars, m_data_->bars_raw);
glUniform1fv(uniform_previous_bars_, m_data_->number_of_bars, m_data_->previous_bars_raw);
glUniform1i(uniform_bars_count_, m_data_->number_of_bars);
++frame_counter;
glUniform1f(uniform_time_, (frame_counter / backend_->getFrameTimeMilsec().count()) / 1e3);
// glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glDrawElements(GL_TRIANGLE_FAN, 4, GL_UNSIGNED_INT, nullptr);
glBindFramebuffer(GL_FRAMEBUFFER, fbo_);
glDrawElements(GL_TRIANGLE_FAN, 4, GL_UNSIGNED_INT, nullptr);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
return true;
}
void waybar::modules::cava::CavaGLSL::onRealize() {
make_current();
initShaders();
initGLSL();
initSurface();
}
struct colors {
uint16_t R;
uint16_t G;
uint16_t B;
};
static void parse_color(char* color_string, struct colors* color) {
if (color_string[0] == '#') {
sscanf(++color_string, "%02hx%02hx%02hx", &color->R, &color->G, &color->B);
}
}
void waybar::modules::cava::CavaGLSL::initGLSL() {
GLint gVertexPos2DLocation{glGetAttribLocation(shaderProgram_, "vertexPosition_modelspace")};
if (gVertexPos2DLocation == -1) {
spdlog::error("{0}. Could not find vertex position shader variable", name_);
}
glClearColor(0.f, 0.f, 0.f, 1.f);
GLfloat vertexData[]{-1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f};
GLint indexData[]{0, 1, 2, 3};
GLuint gVBO{0};
glGenBuffers(1, &gVBO);
glBindBuffer(GL_ARRAY_BUFFER, gVBO);
glBufferData(GL_ARRAY_BUFFER, 2 * 4 * sizeof(GLfloat), vertexData, GL_STATIC_DRAW);
GLuint gIBO{0};
glGenBuffers(1, &gIBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, gIBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, 4 * sizeof(GLuint), indexData, GL_STATIC_DRAW);
GLuint gVAO{0};
glGenVertexArrays(1, &gVAO);
glBindVertexArray(gVAO);
glEnableVertexAttribArray(gVertexPos2DLocation);
glBindBuffer(GL_ARRAY_BUFFER, gVBO);
glVertexAttribPointer(gVertexPos2DLocation, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), nullptr);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, gIBO);
glGenFramebuffers(1, &fbo_);
glBindFramebuffer(GL_FRAMEBUFFER, fbo_);
// Create a texture to attach the framebuffer
glGenTextures(1, &texture_);
glBindTexture(GL_TEXTURE_2D, texture_);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, prm_.sdl_width, prm_.sdl_height, 0, GL_RGBA,
GL_UNSIGNED_BYTE, nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture_, 0);
// Check is framebuffer is complete
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
spdlog::error("{0}. Framebuffer not complete", name_);
}
// Unbind the framebuffer
glBindFramebuffer(GL_FRAMEBUFFER, 0);
uniform_bars_ = glGetUniformLocation(shaderProgram_, "bars");
uniform_previous_bars_ = glGetUniformLocation(shaderProgram_, "previous_bars");
uniform_bars_count_ = glGetUniformLocation(shaderProgram_, "bars_count");
uniform_time_ = glGetUniformLocation(shaderProgram_, "shader_time");
GLuint err{glGetError()};
if (err != 0) {
spdlog::error("{0}. Error on initGLSL: {1}", name_, err);
}
}
void waybar::modules::cava::CavaGLSL::initSurface() {
colors color = {0};
GLint uniform_bg_col{glGetUniformLocation(shaderProgram_, "bg_color")};
parse_color(prm_.bcolor, &color);
glUniform3f(uniform_bg_col, (float)color.R / 255.0, (float)color.G / 255.0,
(float)color.B / 255.0);
GLint uniform_fg_col{glGetUniformLocation(shaderProgram_, "fg_color")};
parse_color(prm_.color, &color);
glUniform3f(uniform_fg_col, (float)color.R / 255.0, (float)color.G / 255.0,
(float)color.B / 255.0);
GLint uniform_res{glGetUniformLocation(shaderProgram_, "u_resolution")};
glUniform3f(uniform_res, (float)prm_.sdl_width, (float)prm_.sdl_height, 0.0f);
GLint uniform_bar_width{glGetUniformLocation(shaderProgram_, "bar_width")};
glUniform1i(uniform_bar_width, prm_.bar_width);
GLint uniform_bar_spacing{glGetUniformLocation(shaderProgram_, "bar_spacing")};
glUniform1i(uniform_bar_spacing, prm_.bar_spacing);
GLint uniform_gradient_count{glGetUniformLocation(shaderProgram_, "gradient_count")};
glUniform1i(uniform_gradient_count, prm_.gradient_count);
GLint uniform_gradient_colors{glGetUniformLocation(shaderProgram_, "gradient_colors")};
GLfloat gradient_colors[8][3];
for (int i{0}; i < prm_.gradient_count; ++i) {
parse_color(prm_.gradient_colors[i], &color);
gradient_colors[i][0] = (float)color.R / 255.0;
gradient_colors[i][1] = (float)color.G / 255.0;
gradient_colors[i][2] = (float)color.B / 255.0;
}
glUniform3fv(uniform_gradient_colors, 8, (const GLfloat*)gradient_colors);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glDrawElements(GL_TRIANGLE_FAN, 4, GL_UNSIGNED_INT, nullptr);
}
void waybar::modules::cava::CavaGLSL::initShaders() {
shaderProgram_ = glCreateProgram();
GLuint vertexShader{loadShader(prm_.vertex_shader, GL_VERTEX_SHADER)};
GLuint fragmentShader{loadShader(prm_.fragment_shader, GL_FRAGMENT_SHADER)};
glAttachShader(shaderProgram_, vertexShader);
glAttachShader(shaderProgram_, fragmentShader);
glLinkProgram(shaderProgram_);
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
// Check for linking errors
GLint success, len;
glGetProgramiv(shaderProgram_, GL_LINK_STATUS, &success);
if (!success) {
glGetProgramiv(shaderProgram_, GL_INFO_LOG_LENGTH, &len);
GLchar* infoLog{(char*)'\0'};
glGetProgramInfoLog(shaderProgram_, len, &len, infoLog);
spdlog::error("{0}. Shader linking error: {1}", name_, infoLog);
}
glReleaseShaderCompiler();
glUseProgram(shaderProgram_);
}
GLuint waybar::modules::cava::CavaGLSL::loadShader(const std::string& fileName, GLenum type) {
spdlog::debug("{0}. loadShader: {1}", name_, fileName);
// Read shader source code from the file
std::ifstream shaderFile{fileName};
if (!shaderFile.is_open()) {
spdlog::error("{0}. Could not open shader file: {1}", name_, fileName);
}
std::ostringstream buffer;
buffer << shaderFile.rdbuf(); // read file content into stringstream
std::string str{buffer.str()};
const char* shaderSource = str.c_str();
shaderFile.close();
GLuint shaderID{glCreateShader(type)};
if (shaderID == 0) spdlog::error("{0}. Error creating shader type: {0}", type);
glShaderSource(shaderID, 1, &shaderSource, nullptr);
glCompileShader(shaderID);
// Check for compilation errors
GLint success, len;
glGetShaderiv(shaderID, GL_COMPILE_STATUS, &success);
if (!success) {
glGetShaderiv(shaderID, GL_INFO_LOG_LENGTH, &len);
GLchar* infoLog{(char*)'\0'};
glGetShaderInfoLog(shaderID, len, nullptr, infoLog);
spdlog::error("{0}. Shader compilation error in {1}: {2}", name_, fileName, infoLog);
}
return shaderID;
}

View File

@@ -1,4 +1,4 @@
#include "modules/cava/cava.hpp" #include "modules/cava/cavaRaw.hpp"
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
@@ -24,8 +24,11 @@ auto waybar::modules::cava::Cava::doAction(const std::string& name) -> void {
// Cava actions // Cava actions
void waybar::modules::cava::Cava::pause_resume() { backend_->doPauseResume(); } void waybar::modules::cava::Cava::pause_resume() { backend_->doPauseResume(); }
auto waybar::modules::cava::Cava::onUpdate(const std::string& input) -> void { auto waybar::modules::cava::Cava::onUpdate(const std::string& input) -> void {
Glib::signal_idle().connect_once([this, input]() {
if (silence_) { if (silence_) {
silence_ = false;
label_.get_style_context()->remove_class("silent"); label_.get_style_context()->remove_class("silent");
if (!label_.get_style_context()->has_class("updated"))
label_.get_style_context()->add_class("updated"); label_.get_style_context()->add_class("updated");
} }
label_text_.clear(); label_text_.clear();
@@ -35,17 +38,23 @@ auto waybar::modules::cava::Cava::onUpdate(const std::string& input) -> void {
label_.set_markup(label_text_); label_.set_markup(label_text_);
label_.show(); label_.show();
ALabel::update(); ALabel::update();
silence_ = false; });
} }
auto waybar::modules::cava::Cava::onSilence() -> void { auto waybar::modules::cava::Cava::onSilence() -> void {
Glib::signal_idle().connect_once([this]() {
if (!silence_) { if (!silence_) {
if (label_.get_style_context()->has_class("updated"))
label_.get_style_context()->remove_class("updated"); label_.get_style_context()->remove_class("updated");
if (hide_on_silence_) if (hide_on_silence_) {
// Clear the label markup before hiding to prevent GTK from rendering a NULL Pango layout
label_.set_markup("");
label_.hide(); label_.hide();
else if (config_["format_silent"].isString()) } else if (config_["format_silent"].isString())
label_.set_markup(format_silent_); label_.set_markup(format_silent_);
silence_ = true; silence_ = true;
label_.get_style_context()->add_class("silent"); label_.get_style_context()->add_class("silent");
} }
});
} }

View File

@@ -9,91 +9,9 @@ std::shared_ptr<waybar::modules::cava::CavaBackend> waybar::modules::cava::CavaB
return backend_ptr; return backend_ptr;
} }
waybar::modules::cava::CavaBackend::CavaBackend(const Json::Value& config) { waybar::modules::cava::CavaBackend::CavaBackend(const Json::Value& config) : config_(config) {
// Load waybar module config // Load waybar module config
char cfgPath[PATH_MAX]; loadConfig();
cfgPath[0] = '\0';
if (config["cava_config"].isString()) strcpy(cfgPath, config["cava_config"].asString().data());
// Load cava config
error_.length = 0;
if (!load_config(cfgPath, &prm_, false, &error_, 0)) {
spdlog::error("cava backend. Error loading config. {0}", error_.message);
exit(EXIT_FAILURE);
}
// Override cava parameters by the user config
prm_.inAtty = 0;
prm_.output = ::cava::output_method::OUTPUT_RAW;
if (prm_.data_format) free(prm_.data_format);
prm_.data_format = strdup("ascii");
if (prm_.raw_target) free(prm_.raw_target);
prm_.raw_target = strdup("/dev/stdout");
prm_.ascii_range = config["format-icons"].size() - 1;
prm_.bar_width = 2;
prm_.bar_spacing = 0;
prm_.bar_height = 32;
prm_.bar_width = 1;
prm_.orientation = ::cava::ORIENT_TOP;
prm_.xaxis = ::cava::xaxis_scale::NONE;
prm_.mono_opt = ::cava::AVERAGE;
prm_.autobars = 0;
prm_.gravity = 0;
prm_.integral = 1;
if (config["framerate"].isInt()) prm_.framerate = config["framerate"].asInt();
// Calculate delay for Update() thread
frame_time_milsec_ = std::chrono::milliseconds((int)(1e3 / prm_.framerate));
if (config["autosens"].isInt()) prm_.autosens = config["autosens"].asInt();
if (config["sensitivity"].isInt()) prm_.sens = config["sensitivity"].asInt();
if (config["bars"].isInt()) prm_.fixedbars = config["bars"].asInt();
if (config["lower_cutoff_freq"].isNumeric())
prm_.lower_cut_off = config["lower_cutoff_freq"].asLargestInt();
if (config["higher_cutoff_freq"].isNumeric())
prm_.upper_cut_off = config["higher_cutoff_freq"].asLargestInt();
if (config["sleep_timer"].isInt()) prm_.sleep_timer = config["sleep_timer"].asInt();
if (config["method"].isString())
prm_.input = ::cava::input_method_by_name(config["method"].asString().c_str());
if (config["source"].isString()) {
if (prm_.audio_source) free(prm_.audio_source);
prm_.audio_source = config["source"].asString().data();
}
if (config["sample_rate"].isNumeric()) prm_.samplerate = config["sample_rate"].asLargestInt();
if (config["sample_bits"].isInt()) prm_.samplebits = config["sample_bits"].asInt();
if (config["stereo"].isBool()) prm_.stereo = config["stereo"].asBool();
if (config["reverse"].isBool()) prm_.reverse = config["reverse"].asBool();
if (config["bar_delimiter"].isInt()) prm_.bar_delim = config["bar_delimiter"].asInt();
if (config["monstercat"].isBool()) prm_.monstercat = config["monstercat"].asBool();
if (config["waves"].isBool()) prm_.waves = config["waves"].asBool();
if (config["noise_reduction"].isDouble())
prm_.noise_reduction = config["noise_reduction"].asDouble();
if (config["input_delay"].isInt())
fetch_input_delay_ = std::chrono::seconds(config["input_delay"].asInt());
audio_raw_.height = prm_.ascii_range;
audio_data_.format = -1;
audio_data_.rate = 0;
audio_data_.samples_counter = 0;
audio_data_.channels = 2;
audio_data_.IEEE_FLOAT = 0;
audio_data_.input_buffer_size = BUFFER_SIZE * audio_data_.channels;
audio_data_.cava_buffer_size = audio_data_.input_buffer_size * 8;
audio_data_.terminate = 0;
audio_data_.suspendFlag = false;
input_source_ = get_input(&audio_data_, &prm_);
if (!input_source_) {
spdlog::error("cava backend API didn't provide input audio source method");
exit(EXIT_FAILURE);
}
// Make cava parameters configuration
// Init cava plan, audio_raw structure
audio_raw_init(&audio_data_, &audio_raw_, &prm_, &plan_);
if (!plan_) spdlog::error("cava backend plan is not provided");
audio_raw_.previous_frame[0] = -1; // For first Update() call need to rePaint text message
// Read audio source trough cava API. Cava orginizes this process via infinity loop // Read audio source trough cava API. Cava orginizes this process via infinity loop
read_thread_ = [this] { read_thread_ = [this] {
try { try {
@@ -102,41 +20,38 @@ waybar::modules::cava::CavaBackend::CavaBackend(const Json::Value& config) {
spdlog::warn("Cava backend. Read source error: {0}", e.what()); spdlog::warn("Cava backend. Read source error: {0}", e.what());
} }
read_thread_.sleep_for(fetch_input_delay_); read_thread_.sleep_for(fetch_input_delay_);
loadConfig();
}; };
// Write outcoming data. Emit signals
thread_ = [this] { out_thread_ = [this] {
doUpdate(); doUpdate(false);
thread_.sleep_for(frame_time_milsec_); out_thread_.sleep_for(frame_time_milsec_);
}; };
} }
waybar::modules::cava::CavaBackend::~CavaBackend() { waybar::modules::cava::CavaBackend::~CavaBackend() {
thread_.stop(); out_thread_.stop();
read_thread_.stop(); read_thread_.stop();
cava_destroy(plan_);
delete plan_; freeBackend();
plan_ = nullptr;
audio_raw_clean(&audio_raw_);
pthread_mutex_lock(&audio_data_.lock);
audio_data_.terminate = 1;
pthread_mutex_unlock(&audio_data_.lock);
config_clean(&prm_);
free(audio_data_.source);
free(audio_data_.cava_in);
} }
static void upThreadDelay(std::chrono::milliseconds& delay, std::chrono::seconds& delta) { static bool upThreadDelay(std::chrono::milliseconds& delay, std::chrono::seconds& delta) {
if (delta == std::chrono::seconds{0}) { if (delta == std::chrono::seconds{0}) {
delta += std::chrono::seconds{1}; delta += std::chrono::seconds{1};
delay += delta; delay += delta;
return true;
} }
return false;
} }
static void downThreadDelay(std::chrono::milliseconds& delay, std::chrono::seconds& delta) { static bool downThreadDelay(std::chrono::milliseconds& delay, std::chrono::seconds& delta) {
if (delta > std::chrono::seconds{0}) { if (delta > std::chrono::seconds{0}) {
delay -= delta; delay -= delta;
delta -= std::chrono::seconds{1}; delta -= std::chrono::seconds{1};
return true;
} }
return false;
} }
bool waybar::modules::cava::CavaBackend::isSilence() { bool waybar::modules::cava::CavaBackend::isSilence() {
@@ -186,6 +101,7 @@ void waybar::modules::cava::CavaBackend::doPauseResume() {
upThreadDelay(frame_time_milsec_, suspend_silence_delay_); upThreadDelay(frame_time_milsec_, suspend_silence_delay_);
} }
pthread_mutex_unlock(&audio_data_.lock); pthread_mutex_unlock(&audio_data_.lock);
Update();
} }
waybar::modules::cava::CavaBackend::type_signal_update waybar::modules::cava::CavaBackend::type_signal_update
@@ -193,6 +109,11 @@ waybar::modules::cava::CavaBackend::signal_update() {
return m_signal_update_; return m_signal_update_;
} }
waybar::modules::cava::CavaBackend::type_signal_audio_raw_update
waybar::modules::cava::CavaBackend::signal_audio_raw_update() {
return m_signal_audio_raw_;
}
waybar::modules::cava::CavaBackend::type_signal_silence waybar::modules::cava::CavaBackend::type_signal_silence
waybar::modules::cava::CavaBackend::signal_silence() { waybar::modules::cava::CavaBackend::signal_silence() {
return m_signal_silence_; return m_signal_silence_;
@@ -215,12 +136,138 @@ void waybar::modules::cava::CavaBackend::doUpdate(bool force) {
} }
if (!silence_ || prm_.sleep_timer == 0) { if (!silence_ || prm_.sleep_timer == 0) {
downThreadDelay(frame_time_milsec_, suspend_silence_delay_); if (downThreadDelay(frame_time_milsec_, suspend_silence_delay_)) Update();
execute(); execute();
if (re_paint_ == 1 || force) m_signal_update_.emit(output_); if (re_paint_ == 1 || force || prm_.continuous_rendering) {
m_signal_update_.emit(output_);
m_signal_audio_raw_.emit(audio_raw_);
}
} else { } else {
upThreadDelay(frame_time_milsec_, suspend_silence_delay_); if (upThreadDelay(frame_time_milsec_, suspend_silence_delay_)) Update();
if (silence_ != silence_prev_ || force) m_signal_silence_.emit(); if (silence_ != silence_prev_ || force) m_signal_silence_.emit();
} }
silence_prev_ = silence_; silence_prev_ = silence_;
} }
void waybar::modules::cava::CavaBackend::freeBackend() {
if (plan_ != NULL) {
cava_destroy(plan_);
plan_ = NULL;
}
audio_raw_clean(&audio_raw_);
pthread_mutex_lock(&audio_data_.lock);
audio_data_.terminate = 1;
pthread_mutex_unlock(&audio_data_.lock);
free_config(&prm_);
free(audio_data_.source);
free(audio_data_.cava_in);
}
void waybar::modules::cava::CavaBackend::loadConfig() {
freeBackend();
// Load waybar module config
char cfgPath[PATH_MAX];
cfgPath[0] = '\0';
if (config_["cava_config"].isString()) strcpy(cfgPath, config_["cava_config"].asString().data());
// Load cava config
error_.length = 0;
if (!load_config(cfgPath, &prm_, &error_)) {
spdlog::error("cava backend. Error loading config. {0}", error_.message);
exit(EXIT_FAILURE);
}
// Override cava parameters by the user config
prm_.inAtty = 0;
auto const output{prm_.output};
// prm_.output = ::cava::output_method::OUTPUT_RAW;
if (prm_.data_format) free(prm_.data_format);
// Default to ascii for format-icons output; allow user override
prm_.data_format = strdup(
config_["data_format"].isString() ? config_["data_format"].asString().c_str() : "ascii");
if (config_["raw_target"].isString()) {
if (prm_.raw_target) free(prm_.raw_target);
prm_.raw_target = strdup(config_["raw_target"].asString().c_str());
}
prm_.ascii_range = config_["format-icons"].size() - 1;
if (config_["bar_spacing"].isInt()) prm_.bar_spacing = config_["bar_spacing"].asInt();
if (config_["bar_width"].isInt()) prm_.bar_width = config_["bar_width"].asInt();
if (config_["bar_height"].isInt()) prm_.bar_height = config_["bar_height"].asInt();
prm_.orientation = ::cava::ORIENT_TOP;
prm_.xaxis = ::cava::xaxis_scale::NONE;
prm_.mono_opt = ::cava::AVERAGE;
prm_.autobars = 0;
if (config_["gravity"].isInt()) prm_.gravity = config_["gravity"].asInt();
if (config_["integral"].isInt()) prm_.integral = config_["integral"].asInt();
if (config_["framerate"].isInt()) prm_.framerate = config_["framerate"].asInt();
// Calculate delay for Update() thread
frame_time_milsec_ = std::chrono::milliseconds((int)(1e3 / prm_.framerate));
if (config_["autosens"].isInt()) prm_.autosens = config_["autosens"].asInt();
if (config_["sensitivity"].isInt()) prm_.sens = config_["sensitivity"].asInt();
if (config_["bars"].isInt()) prm_.fixedbars = config_["bars"].asInt();
if (config_["lower_cutoff_freq"].isNumeric())
prm_.lower_cut_off = config_["lower_cutoff_freq"].asLargestInt();
if (config_["higher_cutoff_freq"].isNumeric())
prm_.upper_cut_off = config_["higher_cutoff_freq"].asLargestInt();
if (config_["sleep_timer"].isInt()) prm_.sleep_timer = config_["sleep_timer"].asInt();
if (config_["method"].isString())
prm_.input = ::cava::input_method_by_name(config_["method"].asString().c_str());
if (config_["source"].isString()) {
if (prm_.audio_source) free(prm_.audio_source);
prm_.audio_source = config_["source"].asString().data();
}
if (config_["sample_rate"].isNumeric()) prm_.samplerate = config_["sample_rate"].asLargestInt();
if (config_["sample_bits"].isInt()) prm_.samplebits = config_["sample_bits"].asInt();
if (config_["stereo"].isBool()) prm_.stereo = config_["stereo"].asBool();
if (config_["reverse"].isBool()) prm_.reverse = config_["reverse"].asBool();
if (config_["bar_delimiter"].isInt()) prm_.bar_delim = config_["bar_delimiter"].asInt();
if (config_["monstercat"].isBool()) prm_.monstercat = config_["monstercat"].asBool();
if (config_["waves"].isBool()) prm_.waves = config_["waves"].asBool();
if (config_["noise_reduction"].isDouble())
prm_.noise_reduction = config_["noise_reduction"].asDouble();
if (config_["input_delay"].isInt())
fetch_input_delay_ = std::chrono::seconds(config_["input_delay"].asInt());
if (config_["gradient"].isInt()) prm_.gradient = config_["gradient"].asInt();
if (prm_.gradient == 0)
prm_.gradient_count = 0;
else if (config_["gradient_count"].isInt())
prm_.gradient_count = config_["gradient_count"].asInt();
if (config_["sdl_width"].isInt()) prm_.sdl_width = config_["sdl_width"].asInt();
if (config_["sdl_height"].isInt()) prm_.sdl_height = config_["sdl_height"].asInt();
audio_raw_.height = prm_.ascii_range;
audio_data_.format = -1;
audio_data_.rate = 0;
audio_data_.samples_counter = 0;
audio_data_.channels = 2;
audio_data_.IEEE_FLOAT = 0;
audio_data_.input_buffer_size = BUFFER_SIZE * audio_data_.channels;
audio_data_.cava_buffer_size = audio_data_.input_buffer_size * 8;
audio_data_.terminate = 0;
audio_data_.suspendFlag = false;
input_source_ = get_input(&audio_data_, &prm_);
if (!input_source_) {
spdlog::error("cava backend API didn't provide input audio source method");
exit(EXIT_FAILURE);
}
prm_.output = ::cava::output_method::OUTPUT_RAW;
// Make cava parameters configuration
// Init cava plan, audio_raw structure
audio_raw_init(&audio_data_, &audio_raw_, &prm_, &plan_);
if (!plan_) spdlog::error("cava backend plan is not provided");
audio_raw_.previous_frame[0] = -1; // For first Update() call need to rePaint text message
prm_.output = output;
}
const struct ::cava::config_params* waybar::modules::cava::CavaBackend::getPrm() { return &prm_; }
std::chrono::milliseconds waybar::modules::cava::CavaBackend::getFrameTimeMilsec() {
return frame_time_milsec_;
};

View File

@@ -26,9 +26,7 @@ auto waybar::modules::Cpu::update() -> void {
auto [load1, load5, load15] = Load::getLoad(); auto [load1, load5, load15] = Load::getLoad();
auto [cpu_usage, tooltip] = CpuUsage::getCpuUsage(prev_times_); auto [cpu_usage, tooltip] = CpuUsage::getCpuUsage(prev_times_);
auto [max_frequency, min_frequency, avg_frequency] = CpuFrequency::getCpuFrequency(); auto [max_frequency, min_frequency, avg_frequency] = CpuFrequency::getCpuFrequency();
if (tooltipEnabled()) {
label_.set_tooltip_text(tooltip);
}
auto format = format_; auto format = format_;
auto total_usage = cpu_usage.empty() ? 0 : cpu_usage[0]; auto total_usage = cpu_usage.empty() ? 0 : cpu_usage[0];
auto state = getState(total_usage); auto state = getState(total_usage);
@@ -56,6 +54,15 @@ auto waybar::modules::Cpu::update() -> void {
store.push_back(fmt::arg(icon_format.c_str(), getIcon(cpu_usage[i], icons))); 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));
if (tooltipEnabled()) {
if (config_["tooltip-format"].isString()) {
tooltip = config_["tooltip-format"].asString();
label_.set_tooltip_markup(fmt::vformat(tooltip, store));
} else {
label_.set_tooltip_markup(tooltip);
}
}
} }
// Call parent update // Call parent update

View File

@@ -20,12 +20,7 @@ waybar::modules::CpuFrequency::CpuFrequency(const std::string& id, const Json::V
auto waybar::modules::CpuFrequency::update() -> void { auto waybar::modules::CpuFrequency::update() -> void {
// TODO: as creating dynamic fmt::arg arrays is buggy we have to calc both // TODO: as creating dynamic fmt::arg arrays is buggy we have to calc both
auto [max_frequency, min_frequency, avg_frequency] = CpuFrequency::getCpuFrequency(); auto [max_frequency, min_frequency, avg_frequency] = CpuFrequency::getCpuFrequency();
if (tooltipEnabled()) {
auto tooltip =
fmt::format("Minimum frequency: {}\nAverage frequency: {}\nMaximum frequency: {}\n",
min_frequency, avg_frequency, max_frequency);
label_.set_tooltip_text(tooltip);
}
auto format = format_; auto format = format_;
auto state = getState(avg_frequency); auto state = getState(avg_frequency);
if (!state.empty() && config_["format-" + state].isString()) { if (!state.empty() && config_["format-" + state].isString()) {
@@ -43,6 +38,18 @@ auto waybar::modules::CpuFrequency::update() -> void {
store.push_back(fmt::arg("min_frequency", min_frequency)); store.push_back(fmt::arg("min_frequency", min_frequency));
store.push_back(fmt::arg("avg_frequency", avg_frequency)); store.push_back(fmt::arg("avg_frequency", avg_frequency));
label_.set_markup(fmt::vformat(format, store)); label_.set_markup(fmt::vformat(format, store));
if (tooltipEnabled()) {
std::string tooltip;
if (config_["tooltip-format"].isString()) {
tooltip = config_["tooltip-format"].asString();
label_.set_tooltip_markup(fmt::vformat(tooltip, store));
} else {
tooltip = "Minimum frequency: {}\nAverage frequency: {}\nMaximum frequency: {}\n";
label_.set_tooltip_markup(
fmt::format(fmt::runtime(tooltip), min_frequency, avg_frequency, max_frequency));
}
}
} }
// Call parent update // Call parent update

View File

@@ -20,9 +20,7 @@ waybar::modules::CpuUsage::CpuUsage(const std::string& id, const Json::Value& co
auto waybar::modules::CpuUsage::update() -> void { auto waybar::modules::CpuUsage::update() -> void {
// TODO: as creating dynamic fmt::arg arrays is buggy we have to calc both // TODO: as creating dynamic fmt::arg arrays is buggy we have to calc both
auto [cpu_usage, tooltip] = CpuUsage::getCpuUsage(prev_times_); auto [cpu_usage, tooltip] = CpuUsage::getCpuUsage(prev_times_);
if (tooltipEnabled()) {
label_.set_tooltip_text(tooltip);
}
auto format = format_; auto format = format_;
auto total_usage = cpu_usage.empty() ? 0 : cpu_usage[0]; auto total_usage = cpu_usage.empty() ? 0 : cpu_usage[0];
auto state = getState(total_usage); auto state = getState(total_usage);
@@ -46,6 +44,15 @@ auto waybar::modules::CpuUsage::update() -> void {
store.push_back(fmt::arg(icon_format.c_str(), getIcon(cpu_usage[i], icons))); 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));
if (tooltipEnabled()) {
if (config_["tooltip-format"].isString()) {
tooltip = config_["tooltip-format"].asString();
label_.set_tooltip_markup(fmt::vformat(tooltip, store));
} else {
label_.set_tooltip_markup(tooltip);
}
}
} }
// Call parent update // Call parent update

View File

@@ -136,7 +136,7 @@ void waybar::modules::Custom::waitingWorker() {
} }
void waybar::modules::Custom::refresh(int sig) { void waybar::modules::Custom::refresh(int sig) {
if (sig == SIGRTMIN + config_["signal"].asInt()) { if (config_["signal"].isInt() && sig == SIGRTMIN + config_["signal"].asInt()) {
thread_.wake_up(); thread_.wake_up();
} }
} }
@@ -187,15 +187,11 @@ auto waybar::modules::Custom::update() -> void {
fmt::arg("icon", getIcon(percentage_, alt_)), fmt::arg("percentage", percentage_)); fmt::arg("icon", getIcon(percentage_, alt_)), fmt::arg("percentage", percentage_));
label_.set_tooltip_markup(tooltip); label_.set_tooltip_markup(tooltip);
} else if (text_ == tooltip_) { } else if (text_ == tooltip_) {
if (label_.get_tooltip_markup() != str) {
label_.set_tooltip_markup(str); label_.set_tooltip_markup(str);
}
} else { } else {
if (label_.get_tooltip_markup() != tooltip_) {
label_.set_tooltip_markup(tooltip_); label_.set_tooltip_markup(tooltip_);
} }
} }
}
auto style = label_.get_style_context(); auto style = label_.get_style_context();
auto classes = style->list_classes(); auto classes = style->list_classes();
for (auto const& c : classes) { for (auto const& c : classes) {

View File

@@ -81,7 +81,7 @@ auto waybar::modules::Disk::update() -> void {
if (config_["tooltip-format"].isString()) { if (config_["tooltip-format"].isString()) {
tooltip_format = config_["tooltip-format"].asString(); tooltip_format = config_["tooltip-format"].asString();
} }
label_.set_tooltip_text(fmt::format( label_.set_tooltip_markup(fmt::format(
fmt::runtime(tooltip_format), stats.f_bavail * 100 / stats.f_blocks, fmt::arg("free", free), fmt::runtime(tooltip_format), stats.f_bavail * 100 / stats.f_blocks, fmt::arg("free", free),
fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks), fmt::arg("used", used), fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks), fmt::arg("used", used),
fmt::arg("percentage_used", percentage_used), fmt::arg("total", total), fmt::arg("percentage_used", percentage_used), fmt::arg("total", total),

View File

@@ -76,8 +76,8 @@ static void handle_global(void *data, struct wl_registry *registry, uint32_t nam
} }
if (std::strcmp(interface, wl_seat_interface.name) == 0) { if (std::strcmp(interface, wl_seat_interface.name) == 0) {
version = std::min<uint32_t>(version, 1); version = std::min<uint32_t>(version, 1);
static_cast<Tags *>(data)->seat_ = static_cast<struct wl_seat *>( static_cast<Tags*>(data)->seat_ =
wl_registry_bind(registry, name, &wl_seat_interface, version)); static_cast<struct wl_seat*>(wl_registry_bind(registry, name, &wl_seat_interface, version));
} }
} }
@@ -187,6 +187,12 @@ void Tags::handle_view_tags(uint32_t tag, uint32_t state, uint32_t clients, uint
button.get_style_context()->remove_class("occupied"); button.get_style_context()->remove_class("occupied");
} }
if (clients & TAG_INACTIVE) {
button.get_style_context()->remove_class("empty");
} else {
button.get_style_context()->add_class("empty");
}
if (state & TAG_ACTIVE) { if (state & TAG_ACTIVE) {
button.get_style_context()->add_class("focused"); button.get_style_context()->add_class("focused");
} else { } else {

View File

@@ -116,7 +116,7 @@ void Window::handle_frame() {
updateAppIconName(appid_, ""); updateAppIconName(appid_, "");
updateAppIcon(); updateAppIcon();
if (tooltipEnabled()) { if (tooltipEnabled()) {
label_.set_tooltip_text(title_); label_.set_tooltip_markup(title_);
} }
} }

View File

@@ -212,7 +212,7 @@ auto Gamemode::update() -> void {
// Tooltip // Tooltip
if (tooltip) { if (tooltip) {
std::string text = fmt::format(fmt::runtime(tooltip_format), fmt::arg("count", gameCount)); std::string text = fmt::format(fmt::runtime(tooltip_format), fmt::arg("count", gameCount));
box_.set_tooltip_text(text); box_.set_tooltip_markup(text);
} }
// Label format // Label format

View File

@@ -44,12 +44,23 @@ auto Submap::parseConfig(const Json::Value& config) -> void {
auto Submap::update() -> void { auto Submap::update() -> void {
std::lock_guard<std::mutex> lg(mutex_); std::lock_guard<std::mutex> lg(mutex_);
// Handle style class changes
if (!prev_submap_.empty()) {
label_.get_style_context()->remove_class(prev_submap_);
}
if (!submap_.empty()) {
label_.get_style_context()->add_class(submap_);
}
prev_submap_ = submap_;
if (submap_.empty()) { if (submap_.empty()) {
event_box_.hide(); event_box_.hide();
} else { } else {
label_.set_markup(fmt::format(fmt::runtime(format_), submap_)); label_.set_markup(fmt::format(fmt::runtime(format_), submap_));
if (tooltipEnabled()) { if (tooltipEnabled()) {
label_.set_tooltip_text(submap_); label_.set_tooltip_markup(submap_);
} }
event_box_.show(); event_box_.show();
} }
@@ -66,18 +77,12 @@ void Submap::onEvent(const std::string& ev) {
auto submapName = ev.substr(ev.find_first_of('>') + 2); auto submapName = ev.substr(ev.find_first_of('>') + 2);
if (!submap_.empty()) {
label_.get_style_context()->remove_class(submap_);
}
submap_ = submapName; submap_ = submapName;
if (submap_.empty() && always_on_) { if (submap_.empty() && always_on_) {
submap_ = default_submap_; submap_ = default_submap_;
} }
label_.get_style_context()->add_class(submap_);
spdlog::debug("hyprland submap onevent with {}", submap_); spdlog::debug("hyprland submap onevent with {}", submap_);
dp.emit(); dp.emit();

View File

@@ -45,6 +45,8 @@ Window::~Window() {
auto Window::update() -> void { auto Window::update() -> void {
std::shared_lock<std::shared_mutex> windowIpcShareLock(windowIpcSmtx); std::shared_lock<std::shared_mutex> windowIpcShareLock(windowIpcSmtx);
queryActiveWorkspace();
std::string windowName = waybar::util::sanitize_string(workspace_.last_window_title); std::string windowName = waybar::util::sanitize_string(workspace_.last_window_title);
std::string windowAddress = workspace_.last_window; std::string windowAddress = workspace_.last_window;
@@ -70,13 +72,13 @@ auto Window::update() -> void {
tooltip_format = config_["tooltip-format"].asString(); tooltip_format = config_["tooltip-format"].asString();
} }
if (!tooltip_format.empty()) { if (!tooltip_format.empty()) {
label_.set_tooltip_text( label_.set_tooltip_markup(
fmt::format(fmt::runtime(tooltip_format), fmt::arg("title", windowName), fmt::format(fmt::runtime(tooltip_format), fmt::arg("title", windowName),
fmt::arg("initialTitle", windowData_.initial_title), fmt::arg("initialTitle", windowData_.initial_title),
fmt::arg("class", windowData_.class_name), fmt::arg("class", windowData_.class_name),
fmt::arg("initialClass", windowData_.initial_class_name))); fmt::arg("initialClass", windowData_.initial_class_name)));
} else if (!label_text.empty()) { } else if (!label_text.empty()) {
label_.set_tooltip_text(label_text); label_.set_tooltip_markup(label_text);
} }
} }
@@ -235,11 +237,7 @@ void Window::queryActiveWorkspace() {
} }
} }
void Window::onEvent(const std::string& ev) { void Window::onEvent(const std::string& ev) { dp.emit(); }
queryActiveWorkspace();
dp.emit();
}
void Window::setClass(const std::string& classname, bool enable) { void Window::setClass(const std::string& classname, bool enable) {
if (enable) { if (enable) {

View File

@@ -39,6 +39,8 @@ WindowCount::~WindowCount() {
auto WindowCount::update() -> void { auto WindowCount::update() -> void {
std::lock_guard<std::mutex> lg(mutex_); std::lock_guard<std::mutex> lg(mutex_);
queryActiveWorkspace();
std::string format = config_["format"].asString(); std::string format = config_["format"].asString();
std::string formatEmpty = config_["format-empty"].asString(); std::string formatEmpty = config_["format-empty"].asString();
std::string formatWindowed = config_["format-windowed"].asString(); std::string formatWindowed = config_["format-windowed"].asString();
@@ -56,7 +58,7 @@ auto WindowCount::update() -> void {
} else if (!format.empty()) { } else if (!format.empty()) {
label_.set_markup(fmt::format(fmt::runtime(format), workspace_.windows)); label_.set_markup(fmt::format(fmt::runtime(format), workspace_.windows));
} else { } else {
label_.set_text(fmt::format("{}", workspace_.windows)); label_.set_markup(fmt::format("{}", workspace_.windows));
} }
label_.show(); label_.show();
@@ -116,8 +118,6 @@ auto WindowCount::Workspace::parse(const Json::Value& value) -> WindowCount::Wor
} }
void WindowCount::queryActiveWorkspace() { void WindowCount::queryActiveWorkspace() {
std::lock_guard<std::mutex> lg(mutex_);
if (separateOutputs_) { if (separateOutputs_) {
workspace_ = getActiveWorkspace(this->bar_.output->name); workspace_ = getActiveWorkspace(this->bar_.output->name);
} else { } else {
@@ -125,10 +125,7 @@ void WindowCount::queryActiveWorkspace() {
} }
} }
void WindowCount::onEvent(const std::string& ev) { void WindowCount::onEvent(const std::string& ev) { dp.emit(); }
queryActiveWorkspace();
dp.emit();
}
void WindowCount::setClass(const std::string& classname, bool enable) { void WindowCount::setClass(const std::string& classname, bool enable) {
if (enable) { if (enable) {

View File

@@ -284,9 +284,7 @@ bool Workspace::isEmpty() const {
// Otherwise, check if every window in the map should be skipped (ignored) // Otherwise, check if every window in the map should be skipped (ignored)
return std::all_of( return std::all_of(
m_windowMap.begin(), m_windowMap.end(), m_windowMap.begin(), m_windowMap.end(),
[this, &ignore_list](const auto &window_repr) { [this, &ignore_list](const auto& window_repr) { return shouldSkipWindow(window_repr); });
return shouldSkipWindow(window_repr);
});
} }
void Workspace::updateTaskbar(const std::string& workspace_icon) { void Workspace::updateTaskbar(const std::string& workspace_icon) {
@@ -310,7 +308,7 @@ void Workspace::updateTaskbar(const std::string &workspace_icon) {
} }
auto window_box = Gtk::make_managed<Gtk::Box>(Gtk::ORIENTATION_HORIZONTAL); auto window_box = Gtk::make_managed<Gtk::Box>(Gtk::ORIENTATION_HORIZONTAL);
window_box->set_tooltip_text(window_repr.window_title); window_box->set_tooltip_markup(window_repr.window_title);
window_box->get_style_context()->add_class("taskbar-window"); window_box->get_style_context()->add_class("taskbar-window");
if (window_repr.isActive) { if (window_repr.isActive) {
window_box->get_style_context()->add_class("active"); window_box->get_style_context()->add_class("active");

View File

@@ -43,6 +43,13 @@ void Workspaces::init() {
m_activeWorkspaceId = m_ipc.getSocket1JsonReply("activeworkspace")["id"].asInt(); m_activeWorkspaceId = m_ipc.getSocket1JsonReply("activeworkspace")["id"].asInt();
initializeWorkspaces(); initializeWorkspaces();
if (barScroll()) {
auto &window = const_cast<Bar &>(m_bar).window;
window.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);
window.signal_scroll_event().connect(sigc::mem_fun(*this, &Workspaces::handleScroll));
}
dp.emit(); dp.emit();
} }
@@ -296,6 +303,11 @@ void Workspaces::loadPersistentWorkspacesFromWorkspaceRules(const Json::Value &c
auto workspace = rule.isMember("defaultName") ? rule["defaultName"].asString() auto workspace = rule.isMember("defaultName") ? rule["defaultName"].asString()
: rule["workspaceString"].asString(); : rule["workspaceString"].asString();
// There could be persistent special workspaces, only show those when show-special is enabled.
if (workspace.starts_with("special:") && !showSpecial()) {
continue;
}
// The prefix "name:" cause mismatches with workspace names taken anywhere else. // The prefix "name:" cause mismatches with workspace names taken anywhere else.
if (workspace.starts_with("name:")) { if (workspace.starts_with("name:")) {
workspace = workspace.substr(5); workspace = workspace.substr(5);
@@ -631,6 +643,7 @@ auto Workspaces::parseConfig(const Json::Value &config) -> void {
populateBoolConfig(config, "persistent-only", m_persistentOnly); populateBoolConfig(config, "persistent-only", m_persistentOnly);
populateBoolConfig(config, "active-only", m_activeOnly); populateBoolConfig(config, "active-only", m_activeOnly);
populateBoolConfig(config, "move-to-monitor", m_moveToMonitor); populateBoolConfig(config, "move-to-monitor", m_moveToMonitor);
populateBoolConfig(config, "enable-bar-scroll", m_barScroll);
m_persistentWorkspaceConfig = config.get("persistent-workspaces", Json::Value()); m_persistentWorkspaceConfig = config.get("persistent-workspaces", Json::Value());
populateSortByConfig(config); populateSortByConfig(config);
@@ -934,7 +947,7 @@ void Workspaces::sortWorkspaces() {
case SortMethod::NUMBER: case SortMethod::NUMBER:
try { try {
return std::stoi(a->name()) < std::stoi(b->name()); return std::stoi(a->name()) < std::stoi(b->name());
} catch (const std::invalid_argument &) { } catch (const std::exception& e) {
// Handle the exception if necessary. // Handle the exception if necessary.
break; break;
} }
@@ -1146,4 +1159,31 @@ std::optional<int> Workspaces::parseWorkspaceId(std::string const &workspaceIdSt
} }
} }
bool Workspaces::handleScroll(GdkEventScroll *e) {
// Ignore emulated scroll events on window
if (gdk_event_get_pointer_emulated((GdkEvent *)e)) {
return false;
}
auto dir = AModule::getScrollDir(e);
if (dir == SCROLL_DIR::NONE) {
return true;
}
if (dir == SCROLL_DIR::DOWN || dir == SCROLL_DIR::RIGHT) {
if (allOutputs()) {
m_ipc.getSocket1Reply("dispatch workspace e+1");
} else {
m_ipc.getSocket1Reply("dispatch workspace m+1");
}
} else if (dir == SCROLL_DIR::UP || dir == SCROLL_DIR::LEFT) {
if (allOutputs()) {
m_ipc.getSocket1Reply("dispatch workspace e-1");
} else {
m_ipc.getSocket1Reply("dispatch workspace m-1");
}
}
return true;
}
} // namespace waybar::modules::hyprland } // namespace waybar::modules::hyprland

View File

@@ -14,22 +14,27 @@ waybar::modules::Image::Image(const std::string& id, const Json::Value& config)
size_ = config["size"].asInt(); size_ = config["size"].asInt();
interval_ = config_["interval"] == "once" const auto once = std::chrono::milliseconds::max();
? std::chrono::milliseconds::max() if (!config_.isMember("interval") || config_["interval"].isNull() ||
: std::chrono::milliseconds(std::max( config_["interval"] == "once") {
1L, // Minimum 1ms due to millisecond precision interval_ = once;
static_cast<long>( } else if (config_["interval"].isNumeric()) {
(config_["interval"].isNumeric() ? config_["interval"].asDouble() : 0) * const auto interval_seconds = config_["interval"].asDouble();
1000))); if (interval_seconds <= 0) {
interval_ = once;
} else {
interval_ =
std::chrono::milliseconds(std::max(1L, // Minimum 1ms due to millisecond precision
static_cast<long>(interval_seconds * 1000)));
}
} else {
interval_ = once;
}
if (size_ == 0) { if (size_ == 0) {
size_ = 16; size_ = 16;
} }
if (interval_.count() == 0) {
interval_ = std::chrono::milliseconds::max();
}
delayWorker(); delayWorker();
} }
@@ -41,7 +46,7 @@ void waybar::modules::Image::delayWorker() {
} }
void waybar::modules::Image::refresh(int sig) { void waybar::modules::Image::refresh(int sig) {
if (sig == SIGRTMIN + config_["signal"].asInt()) { if (config_["signal"].isInt() && sig == SIGRTMIN + config_["signal"].asInt()) {
thread_.wake_up(); thread_.wake_up();
} }
} }

View File

@@ -123,7 +123,7 @@ auto Inhibitor::update() -> void {
label_.get_style_context()->add_class(status_text); label_.get_style_context()->add_class(status_text);
if (tooltipEnabled()) { if (tooltipEnabled()) {
label_.set_tooltip_text(status_text); label_.set_tooltip_markup(status_text);
} }
return ALabel::update(); return ALabel::update();

View File

@@ -80,7 +80,7 @@ auto JACK::update() -> void {
if (tooltipEnabled()) { if (tooltipEnabled()) {
std::string tooltip_format = "{bufsize}/{samplerate} {latency}ms"; std::string tooltip_format = "{bufsize}/{samplerate} {latency}ms";
if (config_["tooltip-format"].isString()) tooltip_format = config_["tooltip-format"].asString(); if (config_["tooltip-format"].isString()) tooltip_format = config_["tooltip-format"].asString();
label_.set_tooltip_text(fmt::format( label_.set_tooltip_markup(fmt::format(
fmt::runtime(tooltip_format), fmt::arg("load", std::round(load_)), fmt::runtime(tooltip_format), fmt::arg("load", std::round(load_)),
fmt::arg("bufsize", bufsize_), fmt::arg("samplerate", samplerate_), fmt::arg("bufsize", bufsize_), fmt::arg("samplerate", samplerate_),
fmt::arg("latency", fmt::format("{:.2f}", latency)), fmt::arg("xruns", xruns_))); fmt::arg("latency", fmt::format("{:.2f}", latency)), fmt::arg("xruns", xruns_)));

View File

@@ -232,9 +232,12 @@ waybar::modules::KeyboardState::KeyboardState(const std::string& id, const Bar&
} }
tryAddDevice(dev_path); tryAddDevice(dev_path);
} else if (event->mask & IN_DELETE) { } else if (event->mask & IN_DELETE) {
std::lock_guard<std::mutex> lock(devices_mutex_);
auto it = libinput_devices_.find(dev_path); auto it = libinput_devices_.find(dev_path);
if (it != libinput_devices_.end()) { if (it != libinput_devices_.end()) {
spdlog::info("Keyboard {} has been removed.", dev_path); spdlog::info("Keyboard {} has been removed.", dev_path);
libinput_path_remove_device(it->second);
libinput_device_unref(it->second);
libinput_devices_.erase(it); libinput_devices_.erase(it);
} }
} }
@@ -245,6 +248,7 @@ waybar::modules::KeyboardState::KeyboardState(const std::string& id, const Bar&
} }
waybar::modules::KeyboardState::~KeyboardState() { waybar::modules::KeyboardState::~KeyboardState() {
std::lock_guard<std::mutex> lock(devices_mutex_);
for (const auto& [_, dev_ptr] : libinput_devices_) { for (const auto& [_, dev_ptr] : libinput_devices_) {
libinput_path_remove_device(dev_ptr); libinput_path_remove_device(dev_ptr);
} }
@@ -256,12 +260,18 @@ auto waybar::modules::KeyboardState::update() -> void {
try { try {
std::string dev_path; std::string dev_path;
{
std::lock_guard<std::mutex> lock(devices_mutex_);
if (libinput_devices_.empty()) {
return;
}
if (config_["device-path"].isString() && if (config_["device-path"].isString() &&
libinput_devices_.find(config_["device-path"].asString()) != libinput_devices_.end()) { libinput_devices_.find(config_["device-path"].asString()) != libinput_devices_.end()) {
dev_path = config_["device-path"].asString(); dev_path = config_["device-path"].asString();
} else { } else {
dev_path = libinput_devices_.begin()->first; dev_path = libinput_devices_.begin()->first;
} }
}
int fd = openFile(dev_path, O_NONBLOCK | O_CLOEXEC | O_RDONLY); int fd = openFile(dev_path, O_NONBLOCK | O_CLOEXEC | O_RDONLY);
auto dev = openDevice(fd); auto dev = openDevice(fd);
numl = libevdev_get_event_value(dev, EV_LED, LED_NUML); numl = libevdev_get_event_value(dev, EV_LED, LED_NUML);
@@ -308,10 +318,15 @@ auto waybar::modules ::KeyboardState::tryAddDevice(const std::string& dev_path)
auto dev = openDevice(fd); auto dev = openDevice(fd);
if (supportsLockStates(dev)) { if (supportsLockStates(dev)) {
spdlog::info("Found device {} at '{}'", libevdev_get_name(dev), dev_path); spdlog::info("Found device {} at '{}'", libevdev_get_name(dev), dev_path);
std::lock_guard<std::mutex> lock(devices_mutex_);
if (libinput_devices_.find(dev_path) == libinput_devices_.end()) { if (libinput_devices_.find(dev_path) == libinput_devices_.end()) {
auto device = libinput_path_add_device(libinput_, dev_path.c_str()); auto device = libinput_path_add_device(libinput_, dev_path.c_str());
if (device) {
libinput_device_ref(device); libinput_device_ref(device);
libinput_devices_[dev_path] = device; libinput_devices_[dev_path] = device;
} else {
spdlog::warn("keyboard-state: Failed to add device to libinput: {}", dev_path);
}
} }
} }
libevdev_free(dev); libevdev_free(dev);

View File

@@ -22,7 +22,7 @@ auto waybar::modules::Load::update() -> void {
auto [load1, load5, load15] = Load::getLoad(); auto [load1, load5, load15] = Load::getLoad();
if (tooltipEnabled()) { if (tooltipEnabled()) {
auto tooltip = fmt::format("Load 1: {}\nLoad 5: {}\nLoad 15: {}", load1, load5, load15); auto tooltip = fmt::format("Load 1: {}\nLoad 5: {}\nLoad 15: {}", load1, load5, load15);
label_.set_tooltip_text(tooltip); label_.set_tooltip_markup(tooltip);
} }
auto format = format_; auto format = format_;
auto state = getState(load1); auto state = getState(load1);

View File

@@ -36,7 +36,7 @@ auto waybar::modules::Memory::update() -> void {
float total_swap_gigabytes = 0.01 * round(swaptotal / 10485.76); float total_swap_gigabytes = 0.01 * round(swaptotal / 10485.76);
int used_ram_percentage = 100 * (memtotal - memfree) / memtotal; int used_ram_percentage = 100 * (memtotal - memfree) / memtotal;
int used_swap_percentage = 0; int used_swap_percentage = 0;
if (swaptotal && swapfree) { if (swaptotal) {
used_swap_percentage = 100 * (swaptotal - swapfree) / swaptotal; used_swap_percentage = 100 * (swaptotal - swapfree) / swaptotal;
} }
float used_ram_gigabytes = 0.01 * round((memtotal - memfree) / 10485.76); float used_ram_gigabytes = 0.01 * round((memtotal - memfree) / 10485.76);
@@ -69,7 +69,7 @@ auto waybar::modules::Memory::update() -> void {
if (tooltipEnabled()) { if (tooltipEnabled()) {
if (config_["tooltip-format"].isString()) { if (config_["tooltip-format"].isString()) {
auto tooltip_format = config_["tooltip-format"].asString(); auto tooltip_format = config_["tooltip-format"].asString();
label_.set_tooltip_text(fmt::format( label_.set_tooltip_markup(fmt::format(
fmt::runtime(tooltip_format), used_ram_percentage, fmt::runtime(tooltip_format), used_ram_percentage,
fmt::arg("total", total_ram_gigabytes), fmt::arg("swapTotal", total_swap_gigabytes), fmt::arg("total", total_ram_gigabytes), fmt::arg("swapTotal", total_swap_gigabytes),
fmt::arg("percentage", used_ram_percentage), fmt::arg("percentage", used_ram_percentage),
@@ -78,7 +78,7 @@ auto waybar::modules::Memory::update() -> void {
fmt::arg("swapUsed", used_swap_gigabytes), fmt::arg("avail", available_ram_gigabytes), fmt::arg("swapUsed", used_swap_gigabytes), fmt::arg("avail", available_ram_gigabytes),
fmt::arg("swapAvail", available_swap_gigabytes))); fmt::arg("swapAvail", available_swap_gigabytes)));
} else { } else {
label_.set_tooltip_text(fmt::format("{:.{}f}GiB used", used_ram_gigabytes, 1)); label_.set_tooltip_markup(fmt::format("{:.{}f}GiB used", used_ram_gigabytes, 1));
} }
} }
} else { } else {

View File

@@ -112,7 +112,7 @@ void waybar::modules::MPD::setLabel() {
? config_["tooltip-format-disconnected"].asString() ? config_["tooltip-format-disconnected"].asString()
: "MPD (disconnected)"; : "MPD (disconnected)";
// Nothing to format // Nothing to format
label_.set_tooltip_text(tooltip_format); label_.set_tooltip_markup(tooltip_format);
} }
return; return;
} }
@@ -229,7 +229,7 @@ void waybar::modules::MPD::setLabel() {
fmt::arg("stateIcon", stateIcon), fmt::arg("consumeIcon", consumeIcon), fmt::arg("stateIcon", stateIcon), fmt::arg("consumeIcon", consumeIcon),
fmt::arg("randomIcon", randomIcon), fmt::arg("repeatIcon", repeatIcon), fmt::arg("randomIcon", randomIcon), fmt::arg("repeatIcon", repeatIcon),
fmt::arg("singleIcon", singleIcon), fmt::arg("filename", filename), fmt::arg("uri", uri)); fmt::arg("singleIcon", singleIcon), fmt::arg("filename", filename), fmt::arg("uri", uri));
label_.set_tooltip_text(tooltip_text); label_.set_tooltip_markup(tooltip_text);
} catch (fmt::format_error const& e) { } catch (fmt::format_error const& e) {
spdlog::warn("mpd: format error (tooltip): {}", e.what()); spdlog::warn("mpd: format error (tooltip): {}", e.what());
} }

View File

@@ -734,7 +734,7 @@ auto Mpris::update() -> void {
fmt::arg("player_icon", getIconFromJson(config_["player-icons"], info.name)), fmt::arg("player_icon", getIconFromJson(config_["player-icons"], info.name)),
fmt::arg("status_icon", getIconFromJson(config_["status-icons"], info.status_string))); fmt::arg("status_icon", getIconFromJson(config_["status-icons"], info.status_string)));
label_.set_tooltip_text(tooltip_text); label_.set_tooltip_markup(tooltip_text);
} catch (fmt::format_error const& e) { } catch (fmt::format_error const& e) {
spdlog::warn("mpris: format error (tooltip): {}", e.what()); spdlog::warn("mpris: format error (tooltip): {}", e.what());
} }

View File

@@ -67,7 +67,7 @@ void Window::doUpdate() {
updateAppIconName(appId, ""); updateAppIconName(appId, "");
if (tooltipEnabled()) label_.set_tooltip_text(title); if (tooltipEnabled()) label_.set_tooltip_markup(title);
const auto id = window["id"].asUInt64(); const auto id = window["id"].asUInt64();
const auto workspaceId = window["workspace_id"].asUInt64(); const auto workspaceId = window["workspace_id"].asUInt64();

View File

@@ -174,11 +174,11 @@ std::string Workspaces::getIcon(const std::string &value, const Json::Value &ws)
if (ws["is_urgent"].asBool() && icons["urgent"]) return icons["urgent"].asString(); if (ws["is_urgent"].asBool() && icons["urgent"]) return icons["urgent"].asString();
if (ws["active_window_id"].isNull() && icons["empty"]) return icons["empty"].asString(); if (ws["is_active"].asBool() && icons["active"]) return icons["active"].asString();
if (ws["is_focused"].asBool() && icons["focused"]) return icons["focused"].asString(); if (ws["is_focused"].asBool() && icons["focused"]) return icons["focused"].asString();
if (ws["is_active"].asBool() && icons["active"]) return icons["active"].asString(); if (ws["active_window_id"].isNull() && icons["empty"]) return icons["empty"].asString();
if (ws["name"]) { if (ws["name"]) {
const auto& name = ws["name"].asString(); const auto& name = ws["name"].asString();

View File

@@ -157,7 +157,7 @@ auto PowerProfilesDaemon::update() -> void {
store.push_back(fmt::arg("icon", getIcon(0, profile.name))); store.push_back(fmt::arg("icon", getIcon(0, profile.name)));
label_.set_markup(fmt::vformat(format_, store)); label_.set_markup(fmt::vformat(format_, store));
if (tooltipEnabled()) { if (tooltipEnabled()) {
label_.set_tooltip_text(fmt::vformat(tooltipFormat_, store)); label_.set_tooltip_markup(fmt::vformat(tooltipFormat_, store));
} }
// Set CSS class // Set CSS class

View File

@@ -138,7 +138,7 @@ auto waybar::modules::Pulseaudio::update() -> void {
fmt::arg("source_volume", source_volume), fmt::arg("source_desc", source_desc), fmt::arg("source_volume", source_volume), fmt::arg("source_desc", source_desc),
fmt::arg("icon", getIcon(sink_volume, getPulseIcon())))); fmt::arg("icon", getIcon(sink_volume, getPulseIcon()))));
} else { } else {
label_.set_tooltip_text(sink_desc); label_.set_tooltip_markup(sink_desc);
} }
} }

View File

@@ -86,8 +86,8 @@ static void handle_global(void *data, struct wl_registry *registry, uint32_t nam
if (std::strcmp(interface, wl_seat_interface.name) == 0) { if (std::strcmp(interface, wl_seat_interface.name) == 0) {
version = std::min<uint32_t>(version, 1); version = std::min<uint32_t>(version, 1);
static_cast<Layout *>(data)->seat_ = static_cast<struct wl_seat *>( static_cast<Layout*>(data)->seat_ =
wl_registry_bind(registry, name, &wl_seat_interface, version)); static_cast<struct wl_seat*>(wl_registry_bind(registry, name, &wl_seat_interface, version));
} }
} }

View File

@@ -46,8 +46,8 @@ static void handle_global(void *data, struct wl_registry *registry, uint32_t nam
wl_registry_bind(registry, name, &zriver_status_manager_v1_interface, version)); wl_registry_bind(registry, name, &zriver_status_manager_v1_interface, version));
} else if (std::strcmp(interface, wl_seat_interface.name) == 0) { } else if (std::strcmp(interface, wl_seat_interface.name) == 0) {
version = std::min<uint32_t>(version, 1); version = std::min<uint32_t>(version, 1);
static_cast<Mode *>(data)->seat_ = static_cast<struct wl_seat *>( static_cast<Mode*>(data)->seat_ =
wl_registry_bind(registry, name, &wl_seat_interface, version)); static_cast<struct wl_seat*>(wl_registry_bind(registry, name, &wl_seat_interface, version));
} }
} }

View File

@@ -69,8 +69,8 @@ static void handle_global(void *data, struct wl_registry *registry, uint32_t nam
if (std::strcmp(interface, wl_seat_interface.name) == 0) { if (std::strcmp(interface, wl_seat_interface.name) == 0) {
version = std::min(version, 1u); version = std::min(version, 1u);
static_cast<Tags *>(data)->seat_ = static_cast<struct wl_seat *>( static_cast<Tags*>(data)->seat_ =
wl_registry_bind(registry, name, &wl_seat_interface, version)); static_cast<struct wl_seat*>(wl_registry_bind(registry, name, &wl_seat_interface, version));
} }
} }

View File

@@ -46,8 +46,8 @@ static void handle_global(void *data, struct wl_registry *registry, uint32_t nam
if (std::strcmp(interface, wl_seat_interface.name) == 0) { if (std::strcmp(interface, wl_seat_interface.name) == 0) {
version = std::min<uint32_t>(version, 1); version = std::min<uint32_t>(version, 1);
static_cast<Window *>(data)->seat_ = static_cast<struct wl_seat *>( static_cast<Window*>(data)->seat_ =
wl_registry_bind(registry, name, &wl_seat_interface, version)); static_cast<struct wl_seat*>(wl_registry_bind(registry, name, &wl_seat_interface, version));
} }
} }

View File

@@ -25,9 +25,9 @@ auto waybar::modules::Clock::update() -> void {
if (config_["tooltip-format"].isString()) { if (config_["tooltip-format"].isString()) {
auto tooltip_format = config_["tooltip-format"].asString(); auto tooltip_format = config_["tooltip-format"].asString();
auto tooltip_text = fmt::format(fmt::runtime(tooltip_format), localtime); auto tooltip_text = fmt::format(fmt::runtime(tooltip_format), localtime);
label_.set_tooltip_text(tooltip_text); label_.set_tooltip_markup(tooltip_text);
} else { } else {
label_.set_tooltip_text(text); label_.set_tooltip_markup(text);
} }
} }
// Call parent update // Call parent update

View File

@@ -79,6 +79,10 @@ Item::~Item() {
this->gtk_menu->popdown(); this->gtk_menu->popdown();
this->gtk_menu->detach(); this->gtk_menu->detach();
} }
if (this->dbus_menu != nullptr) {
g_object_weak_unref(G_OBJECT(this->dbus_menu), (GWeakNotify)onMenuDestroyed, this);
this->dbus_menu = nullptr;
}
} }
bool Item::handleMouseEnter(GdkEventCrossing* const& e) { bool Item::handleMouseEnter(GdkEventCrossing* const& e) {
@@ -233,9 +237,13 @@ void Item::setCustomIcon(const std::string& id) {
std::string custom_icon = IconManager::instance().getIconForApp(id); std::string custom_icon = IconManager::instance().getIconForApp(id);
if (!custom_icon.empty()) { if (!custom_icon.empty()) {
if (std::filesystem::exists(custom_icon)) { if (std::filesystem::exists(custom_icon)) {
try {
Glib::RefPtr<Gdk::Pixbuf> custom_pixbuf = Gdk::Pixbuf::create_from_file(custom_icon); Glib::RefPtr<Gdk::Pixbuf> custom_pixbuf = Gdk::Pixbuf::create_from_file(custom_icon);
icon_name = ""; // icon_name has priority over pixmap icon_name = ""; // icon_name has priority over pixmap
icon_pixmap = custom_pixbuf; icon_pixmap = custom_pixbuf;
} catch (const Glib::Error& e) {
spdlog::error("Failed to load custom icon {}: {}", custom_icon, e.what());
}
} else { // if file doesn't exist it's most likely an icon_name } else { // if file doesn't exist it's most likely an icon_name
icon_name = custom_icon; icon_name = custom_icon;
} }

View File

@@ -44,7 +44,7 @@ auto Mode::update() -> void {
} else { } else {
label_.set_markup(fmt::format(fmt::runtime(format_), mode_)); label_.set_markup(fmt::format(fmt::runtime(format_), mode_));
if (tooltipEnabled()) { if (tooltipEnabled()) {
label_.set_tooltip_text(mode_); label_.set_tooltip_markup(mode_);
} }
event_box_.show(); event_box_.show();
} }

View File

@@ -99,7 +99,7 @@ auto Window::update() -> void {
fmt::arg("shell", shell_), fmt::arg("marks", marks_)), fmt::arg("shell", shell_), fmt::arg("marks", marks_)),
config_["rewrite"])); config_["rewrite"]));
if (tooltipEnabled()) { if (tooltipEnabled()) {
label_.set_tooltip_text(window_); label_.set_tooltip_markup(window_);
} }
updateAppIcon(); updateAppIcon();

View File

@@ -331,7 +331,7 @@ auto Workspaces::update() -> void {
} }
std::string output = (*it)["name"].asString(); std::string output = (*it)["name"].asString();
std::string windows = ""; std::string windows = "";
if (config_["window-format"].isString()) { if (config_["window-rewrite"].isObject()) {
updateWindows((*it), windows); updateWindows((*it), windows);
} }
if (config_["format"].isString()) { if (config_["format"].isString()) {

View File

@@ -101,7 +101,7 @@ auto waybar::modules::Temperature::update() -> void {
if (config_["tooltip-format"].isString()) { if (config_["tooltip-format"].isString()) {
tooltip_format = config_["tooltip-format"].asString(); tooltip_format = config_["tooltip-format"].asString();
} }
label_.set_tooltip_text(fmt::format( label_.set_tooltip_markup(fmt::format(
fmt::runtime(tooltip_format), fmt::arg("temperatureC", temperature_c), fmt::runtime(tooltip_format), fmt::arg("temperatureC", temperature_c),
fmt::arg("temperatureF", temperature_f), fmt::arg("temperatureK", temperature_k))); fmt::arg("temperatureF", temperature_f), fmt::arg("temperatureK", temperature_k)));
} }

View File

@@ -14,16 +14,18 @@ waybar::modules::Wireplumber::Wireplumber(const std::string& id, const Json::Val
mixer_api_(nullptr), mixer_api_(nullptr),
def_nodes_api_(nullptr), def_nodes_api_(nullptr),
default_node_name_(nullptr), default_node_name_(nullptr),
default_source_name_(nullptr),
pending_plugins_(0), pending_plugins_(0),
muted_(false), muted_(false),
source_muted_(false),
volume_(0.0), volume_(0.0),
source_volume_(0.0),
min_step_(0.0), min_step_(0.0),
node_id_(0), node_id_(0),
node_name_(""),
source_name_(""),
type_(nullptr),
source_node_id_(0), source_node_id_(0),
type_(nullptr) { source_muted_(false),
source_volume_(0.0),
default_source_name_(nullptr) {
waybar::modules::Wireplumber::modules.push_back(this); waybar::modules::Wireplumber::modules.push_back(this);
wp_init(WP_INIT_PIPEWIRE); wp_init(WP_INIT_PIPEWIRE);
@@ -477,12 +479,12 @@ auto waybar::modules::Wireplumber::update() -> void {
} }
if (!tooltipFormat.empty()) { if (!tooltipFormat.empty()) {
label_.set_tooltip_text(fmt::format( label_.set_tooltip_markup(fmt::format(
fmt::runtime(tooltipFormat), fmt::arg("node_name", node_name_), fmt::arg("volume", vol), fmt::runtime(tooltipFormat), fmt::arg("node_name", node_name_), fmt::arg("volume", vol),
fmt::arg("icon", getIcon(vol)), fmt::arg("format_source", formatted_source), fmt::arg("icon", getIcon(vol)), fmt::arg("format_source", formatted_source),
fmt::arg("source_volume", source_vol), fmt::arg("source_desc", source_name_))); fmt::arg("source_volume", source_vol), fmt::arg("source_desc", source_name_)));
} else { } else {
label_.set_tooltip_text(node_name_); label_.set_tooltip_markup(node_name_);
} }
} }

View File

@@ -509,7 +509,7 @@ void Task::update() {
if (markup) if (markup)
button.set_tooltip_markup(txt); button.set_tooltip_markup(txt);
else else
button.set_tooltip_text(txt); button.set_tooltip_markup(txt);
} }
} }

View File

@@ -8,6 +8,8 @@
#include <optional> #include <optional>
#include <utility> #include <utility>
#include "util/udev_deleter.hpp"
namespace { namespace {
class FileDescriptor { class FileDescriptor {
public: public:
@@ -29,22 +31,6 @@ class FileDescriptor {
int fd_; int fd_;
}; };
struct UdevDeleter {
void operator()(udev *ptr) { udev_unref(ptr); }
};
struct UdevDeviceDeleter {
void operator()(udev_device *ptr) { udev_device_unref(ptr); }
};
struct UdevEnumerateDeleter {
void operator()(udev_enumerate *ptr) { udev_enumerate_unref(ptr); }
};
struct UdevMonitorDeleter {
void operator()(udev_monitor *ptr) { udev_monitor_unref(ptr); }
};
void check_eq(int rc, int expected, const char* message = "eq, rc was: ") { void check_eq(int rc, int expected, const char* message = "eq, rc was: ") {
if (rc != expected) { if (rc != expected) {
throw std::runtime_error(fmt::format(fmt::runtime(message), rc)); throw std::runtime_error(fmt::format(fmt::runtime(message), rc));
@@ -287,7 +273,7 @@ int BacklightBackend::get_scaled_brightness(const std::string &preferred_device)
GET_BEST_DEVICE(best, (*this), preferred_device); GET_BEST_DEVICE(best, (*this), preferred_device);
if (best != nullptr) { if (best != nullptr) {
return best->get_actual() * 100 / best->get_max(); return static_cast<int>(std::round(best->get_actual() * 100.0F / best->get_max()));
} }
return 0; return 0;

View File

@@ -1,11 +0,0 @@
[wrap-git]
url = https://github.com/LukashonakV/cava.git
revision = 23efcced43b5a395747b18a2e5f2171fc0925d18
depth = 1
#directory = cava-0.10.6
#source_url = https://github.com/LukashonakV/cava/archive/0.10.6.tar.gz
#source_filename = cava-0.10.6.tar.gz
#source_hash = e715c4c6a625b8dc063e57e8e81c80e4d1015ec1b98db69a283b2c6770f839f4
[provide]
cava = cava_dep

12
subprojects/libcava.wrap Normal file
View File

@@ -0,0 +1,12 @@
#[wrap-git]
#url = https://github.com/LukashonakV/cava.git
#revision = 866cfec40b7b9d38e97148d004d3134c1385b52f
#depth = 1
[wrap-file]
directory = cava-0.10.7-beta
source_url = https://github.com/LukashonakV/cava/archive/v0.10.7-beta.tar.gz
source_filename = cava-0.10.7.tar.gz
source_hash = 8915d7214f2046554c158fe6f2ae518881dfb573e421ea848727be11a5dfa8c4
[provide]
libcava = cava_dep