Merge branch 'master' into fix/zjeffer/thread-sanitizer-warning

This commit is contained in:
Alexis Rouillard
2025-10-05 10:51:47 +02:00
committed by GitHub
61 changed files with 875 additions and 453 deletions

View File

@ -20,8 +20,12 @@ debug-run: build-debug
./build/waybar --log-level debug ./build/waybar --log-level debug
test: test:
meson test -C build --no-rebuild --verbose --suite waybar meson test -C build --verbose --suite waybar
.PHONY: test .PHONY: test
test-detailed:
meson test -C build --verbose --print-errorlogs --test-args='--reporter console -s'
.PHONY: test-detailed
clean: clean:
rm -rf build rm -rf build

6
flake.lock generated
View File

@ -18,11 +18,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1753694789, "lastModified": 1759036355,
"narHash": "sha256-cKgvtz6fKuK1Xr5LQW/zOUiAC0oSQoA9nOISB0pJZqM=", "narHash": "sha256-0m27AKv6ka+q270dw48KflE0LwQYrO7Fm4/2//KCVWg=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "dc9637876d0dcc8c9e5e22986b857632effeb727", "rev": "e9f00bd893984bc8ce46c895c3bf7cac95331127",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@ -65,7 +65,7 @@
nativeBuildInputs = nativeBuildInputs =
pkgs.waybar.nativeBuildInputs pkgs.waybar.nativeBuildInputs
++ (with pkgs; [ ++ (with pkgs; [
nixfmt-rfc-style nixfmt
clang-tools clang-tools
gdb gdb
]); ]);
@ -75,28 +75,26 @@
formatter = genSystems ( formatter = genSystems (
pkgs: pkgs:
pkgs.treefmt.withConfig { pkgs.treefmt.withConfig {
settings = [ settings = {
{ formatter = {
formatter = { clang-format = {
clang-format = { options = [ "-i" ];
options = [ "-i" ]; command = lib.getExe' pkgs.clang-tools "clang-format";
command = lib.getExe' pkgs.clang-tools "clang-format"; excludes = [ ];
excludes = [ ]; includes = [
includes = [ "*.c"
"*.c" "*.cpp"
"*.cpp" "*.h"
"*.h" "*.hpp"
"*.hpp" ];
];
};
nixfmt = {
command = lib.getExe pkgs.nixfmt-rfc-style;
includes = [ "*.nix" ];
};
}; };
tree-root-file = ".git/index"; nixfmt = {
} command = lib.getExe pkgs.nixfmt;
]; includes = [ "*.nix" ];
};
};
tree-root-file = ".git/index";
};
} }
); );

View File

@ -21,7 +21,7 @@ class ALabel : public AModule {
protected: protected:
Gtk::Label label_; Gtk::Label label_;
std::string format_; std::string format_;
const std::chrono::seconds interval_; const std::chrono::milliseconds interval_;
bool alt_ = false; bool alt_ = false;
std::string default_format_; std::string default_format_;

View File

@ -35,7 +35,8 @@ class Config {
void setupConfig(Json::Value &dst, const std::string &config_file, int depth); void setupConfig(Json::Value &dst, const std::string &config_file, int depth);
void resolveConfigIncludes(Json::Value &config, int depth); void resolveConfigIncludes(Json::Value &config, int depth);
void mergeConfig(Json::Value &a_config_, Json::Value &b_config_); void mergeConfig(Json::Value &a_config_, Json::Value &b_config_);
static std::optional<std::string> findIncludePath(const std::string &name); static std::vector<std::string> findIncludePath(
const std::string &name, const std::vector<std::string> &dirs = CONFIG_DIRS);
std::string config_file_; std::string config_file_;

View File

@ -1,59 +0,0 @@
#pragma once
#include "ALabel.hpp"
#include "util/sleeper_thread.hpp"
namespace cava {
extern "C" {
// Need sdl_glsl output feature to be enabled on libcava
#ifndef SDL_GLSL
#define SDL_GLSL
#endif
#include <cava/common.h>
#ifdef SDL_GLSL
#undef SDL_GLSL
#endif
}
} // namespace cava
namespace waybar::modules {
using namespace std::literals::chrono_literals;
class Cava final : public ALabel {
public:
Cava(const std::string&, const Json::Value&);
virtual ~Cava();
auto update() -> void override;
auto doAction(const std::string& name) -> void override;
private:
util::SleeperThread thread_;
util::SleeperThread thread_fetch_input_;
struct cava::error_s error_{}; // cava errors
struct cava::config_params prm_{}; // cava parameters
struct cava::audio_raw audio_raw_{}; // cava handled raw audio data(is based on audio_data)
struct cava::audio_data audio_data_{}; // cava audio data
struct cava::cava_plan* plan_; //{new cava_plan{}};
// Cava API to read audio source
cava::ptr input_source_;
// Delay to handle audio source
std::chrono::milliseconds frame_time_milsec_{1s};
// Text to display
std::string text_{""};
int rePaint_{1};
std::chrono::seconds fetch_input_delay_{4};
std::chrono::seconds suspend_silence_delay_{0};
bool silence_{false};
bool hide_on_silence_{false};
std::string format_silent_{""};
int sleep_counter_{0};
// Cava method
void pause_resume();
// ModuleActionMap
static inline std::map<const std::string, void (waybar::modules::Cava::* const)()> actionMap_{
{"mode", &waybar::modules::Cava::pause_resume}};
};
} // namespace waybar::modules

View File

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

View File

@ -0,0 +1,74 @@
#pragma once
#include <json/json.h>
#include <sigc++/sigc++.h>
#include "util/sleeper_thread.hpp"
namespace cava {
extern "C" {
// Need sdl_glsl output feature to be enabled on libcava
#ifndef SDL_GLSL
#define SDL_GLSL
#endif
#include <cava/common.h>
#ifdef SDL_GLSL
#undef SDL_GLSL
#endif
}
} // namespace cava
namespace waybar::modules::cava {
using namespace std::literals::chrono_literals;
class CavaBackend final {
public:
static std::shared_ptr<CavaBackend> inst(const Json::Value& config);
virtual ~CavaBackend();
// Methods
int getAsciiRange();
void doPauseResume();
void Update();
// Signal accessor
using type_signal_update = sigc::signal<void(const std::string&)>;
type_signal_update signal_update();
using type_signal_silence = sigc::signal<void()>;
type_signal_silence signal_silence();
private:
CavaBackend(const Json::Value& config);
util::SleeperThread thread_;
util::SleeperThread read_thread_;
// Cava API to read audio source
::cava::ptr input_source_;
struct ::cava::error_s error_{}; // cava errors
struct ::cava::config_params prm_{}; // cava parameters
struct ::cava::audio_raw audio_raw_{}; // cava handled raw audio data(is based on audio_data)
struct ::cava::audio_data audio_data_{}; // cava audio data
struct ::cava::cava_plan* plan_; //{new cava_plan{}};
std::chrono::seconds fetch_input_delay_{4};
// Delay to handle audio source
std::chrono::milliseconds frame_time_milsec_{1s};
int re_paint_{0};
bool silence_{false};
bool silence_prev_{false};
std::chrono::seconds suspend_silence_delay_{0};
int sleep_counter_{0};
std::string output_{};
// Methods
void invoke();
void execute();
bool isSilence();
void doUpdate(bool force = false);
// Signal
type_signal_update m_signal_update_;
type_signal_silence m_signal_silence_;
};
} // namespace waybar::modules::cava

View File

@ -48,8 +48,9 @@ class Clock final : public ALabel {
std::string cldYearCached_; // calendar Year mode. Cached calendar std::string cldYearCached_; // calendar Year mode. Cached calendar
date::year_month cldMonShift_; // calendar Month mode. Cached ym date::year_month cldMonShift_; // calendar Month mode. Cached ym
std::string cldMonCached_; // calendar Month mode. Cached calendar std::string cldMonCached_; // calendar Month mode. Cached calendar
date::day cldBaseDay_{0}; // calendar Cached day. Is used when today is changing(midnight) date::day cldBaseDay_{0}; // calendar Cached day. Is used when today is changing(midnight)
std::string cldText_{""}; // calendar text to print std::string cldText_{""}; // calendar text to print
bool iso8601Calendar_{false}; // whether the calendar is in ISO8601
CldMode cldMode_{CldMode::MONTH}; CldMode cldMode_{CldMode::MONTH};
auto get_calendar(const date::year_month_day& today, const date::year_month_day& ymd, auto get_calendar(const date::year_month_day& today, const date::year_month_day& ymd,
const date::time_zone* tz) -> const std::string; const date::time_zone* tz) -> const std::string;
@ -62,6 +63,7 @@ class Clock final : public ALabel {
std::vector<const date::time_zone*> tzList_; // time zones list std::vector<const date::time_zone*> tzList_; // time zones list
int tzCurrIdx_; // current time zone index for tzList_ int tzCurrIdx_; // current time zone index for tzList_
std::string tzText_{""}; // time zones text to print std::string tzText_{""}; // time zones text to print
std::string tzTooltipFormat_{""}; // optional timezone tooltip format
util::SleeperThread thread_; util::SleeperThread thread_;
// ordinal date in tooltip // ordinal date in tooltip

View File

@ -136,8 +136,6 @@ class Workspace {
Gtk::Button button_; Gtk::Button button_;
Gtk::Box content_; Gtk::Box content_;
Gtk::Label label_; Gtk::Label label_;
bool needs_updating_ = false;
}; };
} // namespace waybar::modules::ext } // namespace waybar::modules::ext

View File

@ -46,6 +46,7 @@ class IPC {
util::JsonParser parser_; util::JsonParser parser_;
std::list<std::pair<std::string, EventHandler*>> callbacks_; std::list<std::pair<std::string, EventHandler*>> callbacks_;
int socketfd_; // the hyprland socket file descriptor int socketfd_; // the hyprland socket file descriptor
pid_t socketOwnerPid_;
bool running_ = true; // the ipcThread will stop running when this is false bool running_ = true; // the ipcThread will stop running when this is false
}; };
}; // namespace waybar::modules::hyprland }; // namespace waybar::modules::hyprland

View File

@ -42,7 +42,6 @@ class Workspace {
bool isPersistentConfig() const { return m_isPersistentConfig; }; bool isPersistentConfig() const { return m_isPersistentConfig; };
bool isPersistentRule() const { return m_isPersistentRule; }; bool isPersistentRule() const { return m_isPersistentRule; };
bool isVisible() const { return m_isVisible; }; bool isVisible() const { return m_isVisible; };
bool isEmpty() const { return m_windows == 0; };
bool isUrgent() const { return m_isUrgent; }; bool isUrgent() const { return m_isUrgent; };
bool handleClicked(GdkEventButton* bt) const; bool handleClicked(GdkEventButton* bt) const;
@ -88,6 +87,7 @@ class Workspace {
Gtk::Label m_labelBefore; Gtk::Label m_labelBefore;
Gtk::Label m_labelAfter; Gtk::Label m_labelAfter;
bool isEmpty() const;
void updateTaskbar(const std::string& workspace_icon); void updateTaskbar(const std::string& workspace_icon);
bool handleClick(const GdkEventButton* event_button, WindowAddress const& addr) const; bool handleClick(const GdkEventButton* event_button, WindowAddress const& addr) const;
bool shouldSkipWindow(const WindowRepr& window_repr) const; bool shouldSkipWindow(const WindowRepr& window_repr) const;

View File

@ -51,9 +51,13 @@ class Workspaces : public AModule, public EventHandler {
auto taskbarFormatAfter() const -> std::string { return m_taskbarFormatAfter; } auto taskbarFormatAfter() const -> std::string { return m_taskbarFormatAfter; }
auto taskbarIconSize() const -> int { return m_taskbarIconSize; } auto taskbarIconSize() const -> int { return m_taskbarIconSize; }
auto taskbarOrientation() const -> Gtk::Orientation { return m_taskbarOrientation; } auto taskbarOrientation() const -> Gtk::Orientation { return m_taskbarOrientation; }
auto taskbarReverseDirection() const -> bool { return m_taskbarReverseDirection; }
auto onClickWindow() const -> std::string { return m_onClickWindow; } auto onClickWindow() const -> std::string { return m_onClickWindow; }
auto getIgnoredWindows() const -> std::vector<std::regex> { return m_ignoreWindows; } auto getIgnoredWindows() const -> std::vector<std::regex> { return m_ignoreWindows; }
enum class ActiveWindowPosition { NONE, FIRST, LAST };
auto activeWindowPosition() const -> ActiveWindowPosition { return m_activeWindowPosition; }
std::string getRewrite(std::string window_class, std::string window_title); std::string getRewrite(std::string window_class, std::string window_title);
std::string& getWindowSeparator() { return m_formatWindowSeparator; } std::string& getWindowSeparator() { return m_formatWindowSeparator; }
bool isWorkspaceIgnored(std::string const& workspace_name); bool isWorkspaceIgnored(std::string const& workspace_name);
@ -183,6 +187,14 @@ class Workspaces : public AModule, public EventHandler {
std::string m_taskbarFormatAfter; std::string m_taskbarFormatAfter;
int m_taskbarIconSize = 16; int m_taskbarIconSize = 16;
Gtk::Orientation m_taskbarOrientation = Gtk::ORIENTATION_HORIZONTAL; Gtk::Orientation m_taskbarOrientation = Gtk::ORIENTATION_HORIZONTAL;
bool m_taskbarReverseDirection = false;
util::EnumParser<ActiveWindowPosition> m_activeWindowEnumParser;
ActiveWindowPosition m_activeWindowPosition = ActiveWindowPosition::NONE;
std::map<std::string, ActiveWindowPosition> m_activeWindowPositionMap = {
{"NONE", ActiveWindowPosition::NONE},
{"FIRST", ActiveWindowPosition::FIRST},
{"LAST", ActiveWindowPosition::LAST},
};
std::string m_onClickWindow; std::string m_onClickWindow;
std::string m_currentActiveWindowAddress; std::string m_currentActiveWindowAddress;

View File

@ -31,7 +31,7 @@ class Image : public AModule {
std::string path_; std::string path_;
std::string tooltip_; std::string tooltip_;
int size_; int size_;
int interval_; std::chrono::milliseconds interval_;
util::command::res output_; util::command::res output_;
util::SleeperThread thread_; util::SleeperThread thread_;

View File

@ -33,6 +33,7 @@ class Language : public ALabel, public EventHandler {
std::vector<Layout> layouts_; std::vector<Layout> layouts_;
unsigned current_idx_; unsigned current_idx_;
std::string last_short_name_;
}; };
} // namespace waybar::modules::niri } // namespace waybar::modules::niri

View File

@ -21,6 +21,7 @@ class Tags : public waybar::AModule {
void handle_view_tags(struct wl_array *tags); void handle_view_tags(struct wl_array *tags);
void handle_urgent_tags(uint32_t tags); void handle_urgent_tags(uint32_t tags);
void handle_show();
void handle_primary_clicked(uint32_t tag); void handle_primary_clicked(uint32_t tag);
bool handle_button_press(GdkEventButton *event_button, uint32_t tag); bool handle_button_press(GdkEventButton *event_button, uint32_t tag);

View File

@ -26,7 +26,7 @@ struct ToolTip {
class Item : public sigc::trackable { class Item : public sigc::trackable {
public: public:
Item(const std::string&, const std::string&, const Json::Value&, const Bar&); Item(const std::string&, const std::string&, const Json::Value&, const Bar&);
~Item() = default; ~Item();
std::string bus_name; std::string bus_name;
std::string object_path; std::string object_path;

View File

@ -39,6 +39,12 @@ $XDG_CONFIG_HOME/waybar/config ++
:[ A list of timezones (as in *timezone*) to use for time display, changed using :[ A list of timezones (as in *timezone*) to use for time display, changed using
the scroll wheel. Do not specify *timezone* option when *timezones* is specified. the scroll wheel. Do not specify *timezone* option when *timezones* is specified.
"" represents the system's local timezone "" represents the system's local timezone
|[ *timezone-tooltip-format*
:[ string
:[
:[ Format to use for displaying timezones in the tooltip. When set, this allows showing
timezone information (like timezone abbreviations) in the tooltip while keeping the
main display clean. Uses the same format options as *format*
|[ *locale* |[ *locale*
:[ string :[ string
:[ :[
@ -126,6 +132,12 @@ View all valid format options in *strftime(3)* or have a look https://en.cpprefe
:[ 1 :[ 1
:[ Value to scroll months/years forward/backward. Can be negative. Is :[ Value to scroll months/years forward/backward. Can be negative. Is
configured under *on-scroll* option configured under *on-scroll* option
|[ *iso8601*
:[ bool
:[ false
:[ When enabled, the calendar follows the ISO 8601 standard: weeks begin on
Monday, and the first week of the year is numbered 1. The default week format is
'{:%V}'.
3. Addressed by *clock: calendar: format* 3. Addressed by *clock: calendar: format*
[- *Option* [- *Option*
@ -229,6 +241,25 @@ View all valid format options in *strftime(3)* or have a look https://en.cpprefe
} }
``` ```
4. Show timezone in tooltip only
```
"clock": {
"interval": 60,
"format": "{:%H:%M}",
"timezone-tooltip-format": "{:%H:%M %Z}",
"timezones": [
"",
"America/Chicago",
"America/Los_Angeles",
"Europe/Paris",
"UTC"
],
"tooltip": true,
"tooltip-format": "{tz_list}"
}
```
# STYLE # STYLE
- *#clock* - *#clock*

View File

@ -11,9 +11,10 @@ The *cpu* module displays the current CPU utilization.
# CONFIGURATION # CONFIGURATION
*interval*: ++ *interval*: ++
typeof: integer ++ typeof: integer or float ++
default: 10 ++ default: 10 ++
The interval in which the information gets polled. The interval in which the information gets polled. ++
Minimum value is 0.001 (1ms). Values smaller than 1ms will be set to 1ms.
*format*: ++ *format*: ++
typeof: string ++ typeof: string ++

View File

@ -35,15 +35,17 @@ Addressed by *custom/<name>*
See *return-type* See *return-type*
*interval*: ++ *interval*: ++
typeof: integer ++ typeof: integer or float ++
The interval (in seconds) in which the information gets polled. ++ The interval (in seconds) in which the information gets polled. ++
Minimum value is 0.001 (1ms). Values smaller than 1ms will be set to 1ms. ++
Use *once* if you want to execute the module only on startup. ++ Use *once* if you want to execute the module only on startup. ++
You can update it manually with a signal. If no *interval* or *signal* is defined, it is assumed that the out script loops itself. ++ You can update it manually with a signal. If no *interval* or *signal* is defined, it is assumed that the out script loops itself. ++
If a *signal* is defined then the script will run once on startup and will only update with a signal. If a *signal* is defined then the script will run once on startup and will only update with a signal.
*restart-interval*: ++ *restart-interval*: ++
typeof: integer ++ typeof: integer or float ++
The restart interval (in seconds). ++ The restart interval (in seconds). ++
Minimum value is 0.001 (1ms). Values smaller than 1ms will be set to 1ms. ++
Can't be used with the *interval* option, so only with continuous scripts. ++ Can't be used with the *interval* option, so only with continuous scripts. ++
Once the script exits, it'll be re-executed after the *restart-interval*. Once the script exits, it'll be re-executed after the *restart-interval*.

View File

@ -64,6 +64,8 @@ Addressed by *ext/workspaces*
*activate*: Switch to workspace. *activate*: Switch to workspace.
*deactivate*: Deactivate the workspace.
*close*: Close the workspace. *close*: Close the workspace.
# ICONS # ICONS

View File

@ -50,6 +50,21 @@ This setting is ignored if *workspace-taskbar.enable* is set to true.
default: false ++ default: false ++
Enables the workspace taskbar mode. Enables the workspace taskbar mode.
*update-active-window*: ++
typeof: bool ++
default: false ++
If true, the active/focused window will have an 'active' class. Could cause higher CPU usage due to more frequent redraws.
*reverse-direction*: ++
typeof: bool ++
default: false ++
If true, the taskbar windows will be added in reverse order (right to left if orientation is horizontal, bottom to top if vertical).
*active-window-position*: ++
typeof: "none" | "first" | "last" ++
default: "none" ++
If set to "first", the active window will be moved at the beginning of the taskbar. If set to "last", it will be moved at the end. It will only work if *update-active-window* is set to true.
*format*: ++ *format*: ++
typeof: string ++ typeof: string ++
default: {icon} ++ default: {icon} ++
@ -70,6 +85,19 @@ This setting is ignored if *workspace-taskbar.enable* is set to true.
default: horizontal ++ default: horizontal ++
Direction in which the workspace taskbar is displayed. Direction in which the workspace taskbar is displayed.
*ignore-list*: ++
typeof: array ++
default: [] ++
Regex patterns to match against window class or window title. If a window's class OR title matches any of the patterns, it will not be shown.
*on-click-window*: ++
typeof: string ++
default: "" ++
Command to run when a window is clicked. Available placeholders are: ++
- {address} Hyprland address of the clicked window. ++
- {button} Pressed button number, see https://api.gtkd.org/gdk.c.types.GdkEventButton.button.html. ++
See https://github.com/Alexays/Waybar/wiki/Module:-Hyprland#workspace-taskbars-example for a full example.
*show-special*: ++ *show-special*: ++
typeof: bool ++ typeof: bool ++
default: false ++ default: false ++
@ -216,4 +244,6 @@ Additional to workspace name matching, the following *format-icons* can be set.
- *#workspaces button.special* - *#workspaces button.special*
- *#workspaces button.urgent* - *#workspaces button.urgent*
- *#workspaces button.hosting-monitor* (gets applied if workspace-monitor == waybar-monitor) - *#workspaces button.hosting-monitor* (gets applied if workspace-monitor == waybar-monitor)
- *#workspaces .taskbar-window* (each window in the taskbar) - *#workspaces .workspace-label*
- *#workspaces .taskbar-window* (each window in the taskbar, only if 'workspace-taskbar.enable' is true)
- *#workspaces .taskbar-window.active* (applied to the focused window, only if 'workspace-taskbar.update-active-window' is true)

View File

@ -24,8 +24,9 @@ The *image* module displays an image from a path.
The width/height to render the image. The width/height to render the image.
*interval*: ++ *interval*: ++
typeof: integer ++ 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. ++
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

@ -45,7 +45,7 @@ Addressed by *jack*
The format of information displayed in the tooltip. The format of information displayed in the tooltip.
*interval*: ++ *interval*: ++
typeof: integer ++ typeof: integer or float ++
default: 1 ++ default: 1 ++
The interval in which the information gets polled. The interval in which the information gets polled.

View File

@ -87,15 +87,15 @@ Module config :
``` ```
"custom/power": { "custom/power": {
"format" : "⏻ ", "format" : "⏻ ",
"tooltip": false, "tooltip": false,
"menu": "on-click", "menu": "on-click",
"menu-file": "~/.config/waybar/power_menu.xml", "menu-file": "~/.config/waybar/power_menu.xml",
"menu-actions": { "menu-actions": {
"shutdown": "shutdown", "shutdown": "shutdown",
"reboot": "reboot", "reboot": "reboot",
"suspend": "systemctl suspend", "suspend": "systemctl suspend",
"hibernate": "systemctl hibernate", "hibernate": "systemctl hibernate",
}, },
}, },
``` ```
@ -104,28 +104,28 @@ Module config :
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<interface> <interface>
<object class="GtkMenu" id="menu"> <object class="GtkMenu" id="menu">
<child> <child>
<object class="GtkMenuItem" id="suspend"> <object class="GtkMenuItem" id="suspend">
<property name="label">Suspend</property> <property name="label">Suspend</property>
</object> </object>
</child> </child>
<child> <child>
<object class="GtkMenuItem" id="hibernat"> <object class="GtkMenuItem" id="hibernate">
<property name="label">Hibernate</property> <property name="label">Hibernate</property>
</object> </object>
</child> </child>
<child> <child>
<object class="GtkMenuItem" id="shutdown"> <object class="GtkMenuItem" id="shutdown">
<property name="label">Shutdown</property> <property name="label">Shutdown</property>
</object> </object>
</child> </child>
<child> <child>
<object class="GtkSeparatorMenuItem" id="delimiter1"/> <object class="GtkSeparatorMenuItem" id="delimiter1"/>
</child> </child>
<child> <child>
<object class="GtkMenuItem" id="reboot"> <object class="GtkMenuItem" id="reboot">
<property name="label">Reboot</property> <property name="label">Reboot</property>
</object> </object>
</child> </child>
</object> </object>
</interface> </interface>

View File

@ -52,8 +52,8 @@ Addressed by *niri/language*
``` ```
"niri/language": { "niri/language": {
"format": "Lang: {long}" "format": "Lang: {long}",
"format-en": "AMERICA, HELL YEAH!" "format-en": "AMERICA, HELL YEAH!",
"format-tr": "As bayrakları" "format-tr": "As bayrakları"
} }
``` ```
@ -61,3 +61,12 @@ Addressed by *niri/language*
# STYLE # STYLE
- *#language* - *#language*
Additionally, a CSS class matching the current layout's short name is added to the widget. This
allows per-language styling, for example:
```
#language.us { color: #00ff00; }
#language.de { color: #ff0000; }
#language.fr { color: #0000ff; }
```

View File

@ -40,7 +40,7 @@ Addressed by *temperature*
The threshold before it is considered critical (Celsius). The threshold before it is considered critical (Celsius).
*interval*: ++ *interval*: ++
typeof: integer ++ typeof: integer or float ++
default: 10 ++ default: 10 ++
The interval in which the information gets polled. The interval in which the information gets polled.
@ -160,4 +160,5 @@ Addressed by *temperature*
# STYLE # STYLE
- *#temperature* - *#temperature*
- *#temperature.warning*
- *#temperature.critical* - *#temperature.critical*

View File

@ -437,4 +437,4 @@ A group may hide all but one element, showing them only on mouse hover. In order
# SEE ALSO # SEE ALSO
*sway-output(5)* *sway-output(5)*
*waybar-styles(5)" *waybar-styles(5)*

View File

@ -69,7 +69,7 @@ is_openbsd = host_machine.system() == 'openbsd'
thread_dep = dependency('threads') thread_dep = dependency('threads')
fmt = dependency('fmt', version : ['>=8.1.1'], fallback : ['fmt', 'fmt_dep']) fmt = dependency('fmt', version : ['>=8.1.1'], fallback : ['fmt', 'fmt_dep'])
spdlog = dependency('spdlog', version : ['>=1.10.0'], fallback : ['spdlog', 'spdlog_dep'], default_options : ['external_fmt=enabled', 'std_format=disabled', 'tests=disabled']) spdlog = dependency('spdlog', version : ['>=1.15.2'], fallback : ['spdlog', 'spdlog_dep'], default_options : ['std_format=disabled', 'tests=disabled'])
wayland_client = dependency('wayland-client') wayland_client = dependency('wayland-client')
wayland_cursor = dependency('wayland-cursor') wayland_cursor = dependency('wayland-cursor')
wayland_protos = dependency('wayland-protocols') wayland_protos = dependency('wayland-protocols')
@ -505,7 +505,7 @@ cava = dependency('cava',
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.cpp') src_files += files('src/modules/cava/cava.cpp', 'src/modules/cava/cava_backend.cpp')
man_files += files('man/waybar-cava.5.scd') man_files += files('man/waybar-cava.5.scd')
endif endif

View File

@ -30,13 +30,12 @@ in
# nixpkgs checks version, no need when building locally # nixpkgs checks version, no need when building locally
nativeInstallCheckInputs = [ ]; nativeInstallCheckInputs = [ ];
buildInputs = (builtins.filter (p: buildInputs =
p.pname != "wireplumber" && (builtins.filter (p: p.pname != "wireplumber" && p.pname != "gps") oldAttrs.buildInputs)
p.pname != "gps" ++ [
) oldAttrs.buildInputs) ++ [ pkgs.wireplumber
pkgs.wireplumber pkgs.gpsd
pkgs.gpsd ];
];
postUnpack = '' postUnpack = ''
pushd "$sourceRoot" pushd "$sourceRoot"

View File

@ -63,7 +63,7 @@ button:hover {
background: rgba(0, 0, 0, 0.2); background: rgba(0, 0, 0, 0.2);
} }
#workspaces button.focused { #workspaces button.focused, #workspaces button.active {
background-color: #64727D; background-color: #64727D;
box-shadow: inset 0 -3px #ffffff; box-shadow: inset 0 -3px #ffffff;
} }

View File

@ -63,7 +63,8 @@ std::optional<std::string> getDesktopFilePath(const std::string& app_identifier,
return {}; return {};
} }
const auto data_dirs = Glib::get_system_data_dirs(); auto data_dirs = Glib::get_system_data_dirs();
data_dirs.insert(data_dirs.begin(), Glib::get_user_data_dir());
for (const auto& data_dir : data_dirs) { for (const auto& data_dir : data_dirs) {
const auto data_app_dir = data_dir + "/applications/"; const auto data_app_dir = data_dir + "/applications/";
auto desktop_file_suffix = app_identifier + ".desktop"; auto desktop_file_suffix = app_identifier + ".desktop";

View File

@ -36,11 +36,12 @@ AIconLabel::AIconLabel(const Json::Value &config, const std::string &name, const
box_.set_spacing(spacing); box_.set_spacing(spacing);
bool swap_icon_label = false; bool swap_icon_label = false;
if (config_.isMember("swap-icon-label")) { if (config_["swap-icon-label"].isNull()) {
if (!config_["swap-icon-label"].isBool()) } else if (config_["swap-icon-label"].isBool()) {
spdlog::warn("'swap-icon-label' must be a bool."); swap_icon_label = config_["swap-icon-label"].asBool();
else } else {
swap_icon_label = config_["swap-icon-label"].asBool(); spdlog::warn("'swap-icon-label' must be a bool, found '{}'. Using default value (false).",
config_["swap-icon-label"].asString());
} }
if ((rot == 0 || rot == 3) ^ swap_icon_label) { if ((rot == 0 || rot == 3) ^ swap_icon_label) {

View File

@ -17,10 +17,17 @@ ALabel::ALabel(const Json::Value& config, const std::string& name, const std::st
config["format-alt"].isString() || config["menu"].isString() || enable_click, config["format-alt"].isString() || config["menu"].isString() || enable_click,
enable_scroll), enable_scroll),
format_(config_["format"].isString() ? config_["format"].asString() : format), format_(config_["format"].isString() ? config_["format"].asString() : format),
// Leave the default option outside of the std::max(1L, ...), because the zero value
// (default) is used in modules/custom.cpp to make the difference between
// two types of custom scripts. Fixes #4521.
interval_(config_["interval"] == "once" interval_(config_["interval"] == "once"
? std::chrono::seconds::max() ? std::chrono::milliseconds::max()
: std::chrono::seconds( : std::chrono::milliseconds(
config_["interval"].isUInt() ? config_["interval"].asUInt() : interval)), (config_["interval"].isNumeric()
? std::max(1L, // Minimum 1ms due to millisecond precision
static_cast<long>(config_["interval"].asDouble()) * 1000)
: 1000 * (long)interval))),
default_format_(format_) { default_format_(format_) {
label_.set_name(name); label_.set_name(name);
if (!id.empty()) { if (!id.empty()) {

View File

@ -172,6 +172,10 @@ bool AModule::handleUserEvent(GdkEventButton* const& e) {
// Popup the menu // Popup the menu
gtk_widget_show_all(GTK_WIDGET(menu_)); gtk_widget_show_all(GTK_WIDGET(menu_));
gtk_menu_popup_at_pointer(GTK_MENU(menu_), reinterpret_cast<GdkEvent*>(e)); gtk_menu_popup_at_pointer(GTK_MENU(menu_), reinterpret_cast<GdkEvent*>(e));
// Manually reset prelight to make sure the module doesn't stay in a hover state
if (auto* module = event_box_.get_child(); module != nullptr) {
module->unset_state_flags(Gtk::StateFlags::STATE_FLAG_PRELIGHT);
}
} }
} }
// Second call user scripts // Second call user scripts

View File

@ -433,7 +433,18 @@ void waybar::Bar::onMap(GdkEventAny* /*unused*/) {
/* /*
* Obtain a pointer to the custom layer surface for modules that require it (idle_inhibitor). * Obtain a pointer to the custom layer surface for modules that require it (idle_inhibitor).
*/ */
auto* gdk_window = window.get_window()->gobj(); auto gdk_window_ref = window.get_window();
if (!gdk_window_ref) {
spdlog::warn("Failed to get GDK window during onMap, deferring surface initialization");
return;
}
auto* gdk_window = gdk_window_ref->gobj();
if (!gdk_window) {
spdlog::warn("GDK window object is null during onMap, deferring surface initialization");
return;
}
surface = gdk_wayland_window_get_wl_surface(gdk_window); surface = gdk_wayland_window_get_wl_surface(gdk_window);
configureGlobalOffset(gdk_window_get_width(gdk_window), gdk_window_get_height(gdk_window)); configureGlobalOffset(gdk_window_get_width(gdk_window), gdk_window_get_height(gdk_window));

View File

@ -106,12 +106,24 @@ void Config::setupConfig(Json::Value &dst, const std::string &config_file, int d
mergeConfig(dst, tmp_config); mergeConfig(dst, tmp_config);
} }
std::optional<std::string> Config::findIncludePath(const std::string &name) { std::vector<std::string> Config::findIncludePath(const std::string &name,
const std::vector<std::string> &dirs) {
auto match1 = tryExpandPath(name, ""); auto match1 = tryExpandPath(name, "");
if (!match1.empty()) { if (!match1.empty()) {
return match1.front(); return match1;
} }
return findConfigPath({name}); if (const char *dir = std::getenv(Config::CONFIG_PATH_ENV)) {
if (auto res = tryExpandPath(dir, name); !res.empty()) {
return res;
}
}
for (const auto &dir : dirs) {
if (auto res = tryExpandPath(dir, name); !res.empty()) {
return res;
}
}
return {};
} }
void Config::resolveConfigIncludes(Json::Value &config, int depth) { void Config::resolveConfigIncludes(Json::Value &config, int depth) {
@ -119,18 +131,22 @@ void Config::resolveConfigIncludes(Json::Value &config, int depth) {
if (includes.isArray()) { if (includes.isArray()) {
for (const auto &include : includes) { for (const auto &include : includes) {
spdlog::info("Including resource file: {}", include.asString()); spdlog::info("Including resource file: {}", include.asString());
auto match = findIncludePath(include.asString()); auto matches = findIncludePath(include.asString());
if (match.has_value()) { if (!matches.empty()) {
setupConfig(config, match.value(), depth + 1); for (const auto &match : matches) {
setupConfig(config, match, depth + 1);
}
} else { } else {
spdlog::warn("Unable to find resource file: {}", include.asString()); spdlog::warn("Unable to find resource file: {}", include.asString());
} }
} }
} else if (includes.isString()) { } else if (includes.isString()) {
spdlog::info("Including resource file: {}", includes.asString()); spdlog::info("Including resource file: {}", includes.asString());
auto match = findIncludePath(includes.asString()); auto matches = findIncludePath(includes.asString());
if (match.has_value()) { if (!matches.empty()) {
setupConfig(config, match.value(), depth + 1); for (const auto &match : matches) {
setupConfig(config, match, depth + 1);
}
} else { } else {
spdlog::warn("Unable to find resource file: {}", includes.asString()); spdlog::warn("Unable to find resource file: {}", includes.asString());
} }

View File

@ -109,7 +109,7 @@
#include "modules/wireplumber.hpp" #include "modules/wireplumber.hpp"
#endif #endif
#ifdef HAVE_LIBCAVA #ifdef HAVE_LIBCAVA
#include "modules/cava.hpp" #include "modules/cava/cava.hpp"
#endif #endif
#ifdef HAVE_SYSTEMD_MONITOR #ifdef HAVE_SYSTEMD_MONITOR
#include "modules/systemd_failed_units.hpp" #include "modules/systemd_failed_units.hpp"
@ -343,7 +343,7 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name,
#endif #endif
#ifdef HAVE_LIBCAVA #ifdef HAVE_LIBCAVA
if (ref == "cava") { if (ref == "cava") {
return new waybar::modules::Cava(id, config_[name]); return new waybar::modules::cava::Cava(id, config_[name]);
} }
#endif #endif
#ifdef HAVE_SYSTEMD_MONITOR #ifdef HAVE_SYSTEMD_MONITOR

View File

@ -1,211 +0,0 @@
#include "modules/cava.hpp"
#include <spdlog/spdlog.h>
waybar::modules::Cava::Cava(const std::string& id, const Json::Value& config)
: ALabel(config, "cava", id, "{}", 60, false, false, false) {
// 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_, false, &error_)) {
spdlog::error("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;
strcpy(prm_.data_format, "ascii");
strcpy(prm_.raw_target, "/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();
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()) 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_["hide_on_silence"].isBool()) hide_on_silence_ = config_["hide_on_silence"].asBool();
if (config_["format_silent"].isString()) format_silent_ = config_["format_silent"].asString();
// Make cava parameters configuration
plan_ = new cava::cava_plan{};
audio_raw_.height = prm_.ascii_range;
audio_data_.format = -1;
audio_data_.source = new char[1 + strlen(prm_.audio_source)];
audio_data_.source[0] = '\0';
strcpy(audio_data_.source, prm_.audio_source);
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_.cava_in = new double[audio_data_.cava_buffer_size]{0.0};
audio_data_.terminate = 0;
audio_data_.suspendFlag = false;
input_source_ = get_input(&audio_data_, &prm_);
if (!input_source_) {
spdlog::error("cava API didn't provide input audio source method");
exit(EXIT_FAILURE);
}
// Calculate delay for Update() thread
frame_time_milsec_ = std::chrono::milliseconds((int)(1e3 / prm_.framerate));
// Init cava plan, audio_raw structure
audio_raw_init(&audio_data_, &audio_raw_, &prm_, plan_);
if (!plan_) spdlog::error("cava 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
thread_fetch_input_ = [this] {
thread_fetch_input_.sleep_for(fetch_input_delay_);
input_source_(&audio_data_);
};
thread_ = [this] {
dp.emit();
thread_.sleep_for(frame_time_milsec_);
};
}
waybar::modules::Cava::~Cava() {
thread_fetch_input_.stop();
thread_.stop();
delete plan_;
plan_ = nullptr;
}
void upThreadDelay(std::chrono::milliseconds& delay, std::chrono::seconds& delta) {
if (delta == std::chrono::seconds{0}) {
delta += std::chrono::seconds{1};
delay += delta;
}
}
void downThreadDelay(std::chrono::milliseconds& delay, std::chrono::seconds& delta) {
if (delta > std::chrono::seconds{0}) {
delay -= delta;
delta -= std::chrono::seconds{1};
}
}
auto waybar::modules::Cava::update() -> void {
if (audio_data_.suspendFlag) return;
silence_ = true;
for (int i{0}; i < audio_data_.input_buffer_size; ++i) {
if (audio_data_.cava_in[i]) {
silence_ = false;
sleep_counter_ = 0;
break;
}
}
if (silence_ && prm_.sleep_timer != 0) {
if (sleep_counter_ <=
(int)(std::chrono::milliseconds(prm_.sleep_timer * 1s) / frame_time_milsec_)) {
++sleep_counter_;
silence_ = false;
}
}
if (!silence_ || prm_.sleep_timer == 0) {
downThreadDelay(frame_time_milsec_, suspend_silence_delay_);
// Process: execute cava
pthread_mutex_lock(&audio_data_.lock);
cava::cava_execute(audio_data_.cava_in, audio_data_.samples_counter, audio_raw_.cava_out,
plan_);
if (audio_data_.samples_counter > 0) audio_data_.samples_counter = 0;
pthread_mutex_unlock(&audio_data_.lock);
// Do transformation under raw data
audio_raw_fetch(&audio_raw_, &prm_, &rePaint_, plan_);
if (rePaint_ == 1) {
text_.clear();
for (int i{0}; i < audio_raw_.number_of_bars; ++i) {
audio_raw_.previous_frame[i] = audio_raw_.bars[i];
text_.append(
getIcon((audio_raw_.bars[i] > prm_.ascii_range) ? prm_.ascii_range : audio_raw_.bars[i],
"", prm_.ascii_range + 1));
if (prm_.bar_delim != 0) text_.push_back(prm_.bar_delim);
}
label_.set_markup(text_);
label_.show();
ALabel::update();
label_.get_style_context()->add_class("updated");
}
label_.get_style_context()->remove_class("silent");
} else {
upThreadDelay(frame_time_milsec_, suspend_silence_delay_);
if (hide_on_silence_)
label_.hide();
else if (config_["format_silent"].isString())
label_.set_markup(format_silent_);
label_.get_style_context()->add_class("silent");
label_.get_style_context()->remove_class("updated");
}
}
auto waybar::modules::Cava::doAction(const std::string& name) -> void {
if ((actionMap_[name])) {
(this->*actionMap_[name])();
} else
spdlog::error("Cava. Unsupported action \"{0}\"", name);
}
// Cava actions
void waybar::modules::Cava::pause_resume() {
pthread_mutex_lock(&audio_data_.lock);
if (audio_data_.suspendFlag) {
audio_data_.suspendFlag = false;
pthread_cond_broadcast(&audio_data_.resumeCond);
downThreadDelay(frame_time_milsec_, suspend_silence_delay_);
} else {
audio_data_.suspendFlag = true;
upThreadDelay(frame_time_milsec_, suspend_silence_delay_);
}
pthread_mutex_unlock(&audio_data_.lock);
}

51
src/modules/cava/cava.cpp Normal file
View File

@ -0,0 +1,51 @@
#include "modules/cava/cava.hpp"
#include <spdlog/spdlog.h>
waybar::modules::cava::Cava::Cava(const std::string& id, const Json::Value& config)
: ALabel(config, "cava", id, "{}", 60, false, false, false),
backend_{waybar::modules::cava::CavaBackend::inst(config)} {
if (config_["hide_on_silence"].isBool()) hide_on_silence_ = config_["hide_on_silence"].asBool();
if (config_["format_silent"].isString()) format_silent_ = config_["format_silent"].asString();
ascii_range_ = backend_->getAsciiRange();
backend_->signal_update().connect(sigc::mem_fun(*this, &Cava::onUpdate));
backend_->signal_silence().connect(sigc::mem_fun(*this, &Cava::onSilence));
backend_->Update();
}
auto waybar::modules::cava::Cava::doAction(const std::string& name) -> void {
if ((actionMap_[name])) {
(this->*actionMap_[name])();
} else
spdlog::error("Cava. Unsupported action \"{0}\"", name);
}
// Cava actions
void waybar::modules::cava::Cava::pause_resume() { backend_->doPauseResume(); }
auto waybar::modules::cava::Cava::onUpdate(const std::string& input) -> void {
if (silence_) {
label_.get_style_context()->remove_class("silent");
label_.get_style_context()->add_class("updated");
}
label_text_.clear();
for (auto& ch : input)
label_text_.append(getIcon((ch > ascii_range_) ? ascii_range_ : ch, "", ascii_range_ + 1));
label_.set_markup(label_text_);
label_.show();
ALabel::update();
silence_ = false;
}
auto waybar::modules::cava::Cava::onSilence() -> void {
if (!silence_) {
label_.get_style_context()->remove_class("updated");
if (hide_on_silence_)
label_.hide();
else if (config_["format_silent"].isString())
label_.set_markup(format_silent_);
silence_ = true;
label_.get_style_context()->add_class("silent");
}
}

View File

@ -0,0 +1,223 @@
#include "modules/cava/cava_backend.hpp"
#include <spdlog/spdlog.h>
std::shared_ptr<waybar::modules::cava::CavaBackend> waybar::modules::cava::CavaBackend::inst(
const Json::Value& config) {
static auto* backend = new CavaBackend(config);
static std::shared_ptr<CavaBackend> backend_ptr{backend};
return backend_ptr;
}
waybar::modules::cava::CavaBackend::CavaBackend(const Json::Value& config) {
// 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_, false, &error_)) {
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;
strcpy(prm_.data_format, "ascii");
strcpy(prm_.raw_target, "/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()) 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());
// Make cava parameters configuration
plan_ = new ::cava::cava_plan{};
audio_raw_.height = prm_.ascii_range;
audio_data_.format = -1;
audio_data_.source = new char[1 + strlen(prm_.audio_source)];
audio_data_.source[0] = '\0';
strcpy(audio_data_.source, prm_.audio_source);
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_.cava_in = new double[audio_data_.cava_buffer_size]{0.0};
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);
}
// 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_thread_ = [this] {
try {
input_source_(&audio_data_);
} catch (const std::runtime_error& e) {
spdlog::warn("Cava backend. Read source error: {0}", e.what());
}
read_thread_.sleep_for(fetch_input_delay_);
};
thread_ = [this] {
doUpdate();
thread_.sleep_for(frame_time_milsec_);
};
}
waybar::modules::cava::CavaBackend::~CavaBackend() {
thread_.stop();
read_thread_.stop();
delete plan_;
plan_ = nullptr;
}
static void upThreadDelay(std::chrono::milliseconds& delay, std::chrono::seconds& delta) {
if (delta == std::chrono::seconds{0}) {
delta += std::chrono::seconds{1};
delay += delta;
}
}
static void downThreadDelay(std::chrono::milliseconds& delay, std::chrono::seconds& delta) {
if (delta > std::chrono::seconds{0}) {
delay -= delta;
delta -= std::chrono::seconds{1};
}
}
bool waybar::modules::cava::CavaBackend::isSilence() {
for (int i{0}; i < audio_data_.input_buffer_size; ++i) {
if (audio_data_.cava_in[i]) {
return false;
}
}
return true;
}
int waybar::modules::cava::CavaBackend::getAsciiRange() { return prm_.ascii_range; }
// Process: execute cava
void waybar::modules::cava::CavaBackend::invoke() {
pthread_mutex_lock(&audio_data_.lock);
::cava::cava_execute(audio_data_.cava_in, audio_data_.samples_counter, audio_raw_.cava_out,
plan_);
if (audio_data_.samples_counter > 0) audio_data_.samples_counter = 0;
pthread_mutex_unlock(&audio_data_.lock);
}
// Do transformation under raw data
void waybar::modules::cava::CavaBackend::execute() {
invoke();
audio_raw_fetch(&audio_raw_, &prm_, &re_paint_, plan_);
if (re_paint_ == 1) {
output_.clear();
for (int i{0}; i < audio_raw_.number_of_bars; ++i) {
audio_raw_.previous_frame[i] = audio_raw_.bars[i];
output_.push_back(audio_raw_.bars[i]);
if (prm_.bar_delim != 0) output_.push_back(prm_.bar_delim);
}
}
}
void waybar::modules::cava::CavaBackend::doPauseResume() {
pthread_mutex_lock(&audio_data_.lock);
if (audio_data_.suspendFlag) {
audio_data_.suspendFlag = false;
pthread_cond_broadcast(&audio_data_.resumeCond);
downThreadDelay(frame_time_milsec_, suspend_silence_delay_);
} else {
audio_data_.suspendFlag = true;
upThreadDelay(frame_time_milsec_, suspend_silence_delay_);
}
pthread_mutex_unlock(&audio_data_.lock);
}
waybar::modules::cava::CavaBackend::type_signal_update
waybar::modules::cava::CavaBackend::signal_update() {
return m_signal_update_;
}
waybar::modules::cava::CavaBackend::type_signal_silence
waybar::modules::cava::CavaBackend::signal_silence() {
return m_signal_silence_;
}
void waybar::modules::cava::CavaBackend::Update() { doUpdate(true); }
void waybar::modules::cava::CavaBackend::doUpdate(bool force) {
if (audio_data_.suspendFlag && !force) return;
silence_ = isSilence();
if (!silence_) sleep_counter_ = 0;
if (silence_ && prm_.sleep_timer != 0) {
if (sleep_counter_ <=
(int)(std::chrono::milliseconds(prm_.sleep_timer * 1s) / frame_time_milsec_)) {
++sleep_counter_;
silence_ = false;
}
}
if (!silence_ || prm_.sleep_timer == 0) {
downThreadDelay(frame_time_milsec_, suspend_silence_delay_);
execute();
if (re_paint_ == 1 || force) m_signal_update_.emit(output_);
} else {
upThreadDelay(frame_time_milsec_, suspend_silence_delay_);
if (silence_ != silence_prev_ || force) m_signal_silence_.emit();
}
silence_prev_ = silence_;
}

View File

@ -30,6 +30,9 @@ waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
cldMonShift_{year(1900) / January}, cldMonShift_{year(1900) / January},
tzInTooltip_{m_tlpFmt_.find("{" + kTZPlaceholder + "}") != std::string::npos}, tzInTooltip_{m_tlpFmt_.find("{" + kTZPlaceholder + "}") != std::string::npos},
tzCurrIdx_{0}, tzCurrIdx_{0},
tzTooltipFormat_{config_["timezone-tooltip-format"].isString()
? config_["timezone-tooltip-format"].asString()
: ""},
ordInTooltip_{m_tlpFmt_.find("{" + kOrdPlaceholder + "}") != std::string::npos} { ordInTooltip_{m_tlpFmt_.find("{" + kOrdPlaceholder + "}") != std::string::npos} {
m_tlpText_ = m_tlpFmt_; m_tlpText_ = m_tlpFmt_;
@ -63,8 +66,8 @@ waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
if (cldInTooltip_) { if (cldInTooltip_) {
if (config_[kCldPlaceholder]["mode"].isString()) { if (config_[kCldPlaceholder]["mode"].isString()) {
const std::string cfgMode{config_[kCldPlaceholder]["mode"].asString()}; const std::string cfgMode{config_[kCldPlaceholder]["mode"].asString()};
const std::map<std::string_view, const CldMode&> monthModes{{"month", CldMode::MONTH}, const std::map<std::string, CldMode> monthModes{{"month", CldMode::MONTH},
{"year", CldMode::YEAR}}; {"year", CldMode::YEAR}};
if (monthModes.find(cfgMode) != monthModes.end()) if (monthModes.find(cfgMode) != monthModes.end())
cldMode_ = monthModes.at(cfgMode); cldMode_ = monthModes.at(cfgMode);
else else
@ -73,6 +76,11 @@ waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
"using instead", "using instead",
cfgMode); cfgMode);
} }
if (config_[kCldPlaceholder]["iso8601"].isBool()) {
iso8601Calendar_ = config_[kCldPlaceholder]["iso8601"].asBool();
}
if (config_[kCldPlaceholder]["weeks-pos"].isString()) { if (config_[kCldPlaceholder]["weeks-pos"].isString()) {
if (config_[kCldPlaceholder]["weeks-pos"].asString() == "left") cldWPos_ = WS::LEFT; if (config_[kCldPlaceholder]["weeks-pos"].asString() == "left") cldWPos_ = WS::LEFT;
if (config_[kCldPlaceholder]["weeks-pos"].asString() == "right") cldWPos_ = WS::RIGHT; if (config_[kCldPlaceholder]["weeks-pos"].asString() == "right") cldWPos_ = WS::RIGHT;
@ -92,23 +100,25 @@ waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
fmtMap_.insert({2, "{}"}); fmtMap_.insert({2, "{}"});
if (config_[kCldPlaceholder]["format"]["today"].isString()) { if (config_[kCldPlaceholder]["format"]["today"].isString()) {
fmtMap_.insert({3, config_[kCldPlaceholder]["format"]["today"].asString()}); fmtMap_.insert({3, config_[kCldPlaceholder]["format"]["today"].asString()});
cldBaseDay_ = auto local_time = zoned_time{local_zone(), system_clock::now()}.get_local_time();
year_month_day{ cldBaseDay_ = year_month_day{floor<days>(local_time)}.day();
floor<days>(zoned_time{local_zone(), system_clock::now()}.get_local_time())}
.day();
} else } else
fmtMap_.insert({3, "{}"}); fmtMap_.insert({3, "{}"});
if (config_[kCldPlaceholder]["format"]["weeks"].isString() && cldWPos_ != WS::HIDDEN) { if (config_[kCldPlaceholder]["format"]["weeks"].isString() && cldWPos_ != WS::HIDDEN) {
const auto defaultFmt =
iso8601Calendar_ ? "{:%V}" : ((first_day_of_week() == Monday) ? "{:%W}" : "{:%U}");
fmtMap_.insert({4, std::regex_replace(config_[kCldPlaceholder]["format"]["weeks"].asString(), fmtMap_.insert({4, std::regex_replace(config_[kCldPlaceholder]["format"]["weeks"].asString(),
std::regex("\\{\\}"), std::regex("\\{\\}"), defaultFmt)});
(first_day_of_week() == Monday) ? "{:%W}" : "{:%U}")});
Glib::ustring tmp{std::regex_replace(fmtMap_[4], std::regex("</?[^>]+>|\\{.*\\}"), "")}; Glib::ustring tmp{std::regex_replace(fmtMap_[4], std::regex("</?[^>]+>|\\{.*\\}"), "")};
cldWnLen_ += tmp.size(); cldWnLen_ += tmp.size();
} else { } else {
if (cldWPos_ != WS::HIDDEN) if (cldWPos_ != WS::HIDDEN) {
fmtMap_.insert({4, (first_day_of_week() == Monday) ? "{:%W}" : "{:%U}"}); const auto defaultFmt =
else iso8601Calendar_ ? "{:%V}" : ((first_day_of_week() == Monday) ? "{:%W}" : "{:%U}");
fmtMap_.insert({4, defaultFmt});
} else {
cldWnLen_ = 0; cldWnLen_ = 0;
}
} }
if (config_[kCldPlaceholder]["mode-mon-col"].isInt()) { if (config_[kCldPlaceholder]["mode-mon-col"].isInt()) {
cldMonCols_ = config_[kCldPlaceholder]["mode-mon-col"].asInt(); cldMonCols_ = config_[kCldPlaceholder]["mode-mon-col"].asInt();
@ -188,11 +198,26 @@ auto waybar::modules::Clock::getTZtext(sys_seconds now) -> std::string {
if (tzList_.size() == 1) return ""; if (tzList_.size() == 1) return "";
std::stringstream os; std::stringstream os;
bool first = true;
for (size_t tz_idx{0}; tz_idx < tzList_.size(); ++tz_idx) { for (size_t tz_idx{0}; tz_idx < tzList_.size(); ++tz_idx) {
if (static_cast<int>(tz_idx) == tzCurrIdx_) continue; // Skip local timezone (nullptr) - never show it in tooltip
const auto* tz = tzList_[tz_idx] != nullptr ? tzList_[tz_idx] : local_zone(); if (tzList_[tz_idx] == nullptr) continue;
// Skip current timezone unless timezone-tooltip-format is specified
if (static_cast<int>(tz_idx) == tzCurrIdx_ && tzTooltipFormat_.empty()) continue;
const auto* tz = tzList_[tz_idx];
auto zt{zoned_time{tz, now}}; auto zt{zoned_time{tz, now}};
os << fmt_lib::vformat(m_locale_, format_, fmt_lib::make_format_args(zt)) << '\n';
// Add newline before each entry except the first
if (!first) {
os << '\n';
}
first = false;
// Use timezone-tooltip-format if specified, otherwise use format_
const std::string& fmt = tzTooltipFormat_.empty() ? format_ : tzTooltipFormat_;
os << fmt_lib::vformat(m_locale_, fmt, fmt_lib::make_format_args(zt));
} }
return os.str(); return os.str();
@ -204,9 +229,11 @@ const unsigned cldRowsInMonth(const year_month& ym, const weekday& firstdow) {
auto cldGetWeekForLine(const year_month& ym, const weekday& firstdow, const unsigned line) auto cldGetWeekForLine(const year_month& ym, const weekday& firstdow, const unsigned line)
-> const year_month_weekday { -> const year_month_weekday {
unsigned index{line - 2}; const unsigned idx = line - 2;
if (weekday{ym / 1} == firstdow) ++index; const std::chrono::weekday_indexed indexed_first_day_of_week =
return ym / firstdow[index]; weekday{ym / 1} == firstdow ? firstdow[idx + 1] : firstdow[idx];
return ym / indexed_first_day_of_week;
} }
auto getCalendarLine(const year_month_day& currDate, const year_month ym, const unsigned line, auto getCalendarLine(const year_month_day& currDate, const year_month ym, const unsigned line,
@ -265,7 +292,7 @@ auto getCalendarLine(const year_month_day& currDate, const year_month ym, const
} }
// Print non-first week // Print non-first week
default: { default: {
auto ymdTmp{cldGetWeekForLine(ym, firstdow, line)}; const auto ymdTmp{cldGetWeekForLine(ym, firstdow, line)};
if (ymdTmp.ok()) { if (ymdTmp.ok()) {
auto d{year_month_day{ymdTmp}.day()}; auto d{year_month_day{ymdTmp}.day()};
const auto dlast{(ym / last).day()}; const auto dlast{(ym / last).day()};
@ -356,8 +383,9 @@ auto waybar::modules::Clock::get_calendar(const year_month_day& today, const yea
: static_cast<const zoned_seconds&&>(zoned_seconds{ : static_cast<const zoned_seconds&&>(zoned_seconds{
tz, local_days{cldGetWeekForLine(ymTmp, firstdow, line)}}))) tz, local_days{cldGetWeekForLine(ymTmp, firstdow, line)}})))
<< ' '; << ' ';
} else } else {
os << pads; os << pads;
}
} }
} }
@ -481,6 +509,9 @@ using deleting_unique_ptr = std::unique_ptr<T, deleter_from_fn<fn>>;
// Computations done similarly to Linux cal utility. // Computations done similarly to Linux cal utility.
auto waybar::modules::Clock::first_day_of_week() -> weekday { auto waybar::modules::Clock::first_day_of_week() -> weekday {
if (iso8601Calendar_) {
return Monday;
}
#ifdef HAVE_LANGINFO_1STDAY #ifdef HAVE_LANGINFO_1STDAY
deleting_unique_ptr<std::remove_pointer<locale_t>::type, freelocale> posix_locale{ deleting_unique_ptr<std::remove_pointer<locale_t>::type, freelocale> posix_locale{
newlocale(LC_ALL, m_locale_.name().c_str(), nullptr)}; newlocale(LC_ALL, m_locale_.name().c_str(), nullptr)};

View File

@ -89,9 +89,11 @@ void waybar::modules::Custom::continuousWorker() {
dp.emit(); dp.emit();
spdlog::error("{} stopped unexpectedly, is it endless?", name_); spdlog::error("{} stopped unexpectedly, is it endless?", name_);
} }
if (config_["restart-interval"].isUInt()) { if (config_["restart-interval"].isNumeric()) {
pid_ = -1; pid_ = -1;
thread_.sleep_for(std::chrono::seconds(config_["restart-interval"].asUInt())); thread_.sleep_for(std::chrono::milliseconds(
std::max(1L, // Minimum 1ms due to millisecond precision
static_cast<long>(config_["restart-interval"].asDouble() * 1000))));
fp_ = util::command::open(cmd, pid_, output_name_); fp_ = util::command::open(cmd, pid_, output_name_);
if (!fp_) { if (!fp_) {
throw std::runtime_error("Unable to open " + cmd); throw std::runtime_error("Unable to open " + cmd);

View File

@ -349,11 +349,11 @@ Workspace::Workspace(const Json::Value &config, WorkspaceManager &manager,
} }
const bool config_on_click_middle = config["on-click-middle"].isString(); const bool config_on_click_middle = config["on-click-middle"].isString();
if (config_on_click_middle) { if (config_on_click_middle) {
on_click_middle_action_ = config["on-click"].asString(); on_click_middle_action_ = config["on-click-middle"].asString();
} }
const bool config_on_click_right = config["on-click-right"].isString(); const bool config_on_click_right = config["on-click-right"].isString();
if (config_on_click_right) { if (config_on_click_right) {
on_click_right_action_ = config["on-click"].asString(); on_click_right_action_ = config["on-click-right"].asString();
} }
// setup UI // setup UI
@ -377,16 +377,19 @@ Workspace::~Workspace() {
} }
void Workspace::update() { void Workspace::update() {
if (!needs_updating_) { const auto style_context = button_.get_style_context();
return;
}
// update style and visibility // update style and visibility
const auto style_context = button_.get_style_context(); if (!has_state(EXT_WORKSPACE_HANDLE_V1_STATE_ACTIVE)) {
style_context->remove_class("active"); style_context->remove_class("active");
style_context->remove_class("urgent"); }
style_context->remove_class("hidden"); if (!has_state(EXT_WORKSPACE_HANDLE_V1_STATE_URGENT)) {
style_context->remove_class("urgent");
}
if (!has_state(EXT_WORKSPACE_HANDLE_V1_STATE_HIDDEN)) {
style_context->remove_class("hidden");
}
if (has_state(EXT_WORKSPACE_HANDLE_V1_STATE_ACTIVE)) { if (has_state(EXT_WORKSPACE_HANDLE_V1_STATE_ACTIVE)) {
button_.set_visible(true); button_.set_visible(true);
@ -408,34 +411,26 @@ void Workspace::update() {
label_.set_markup(fmt::format(fmt::runtime(format_), fmt::arg("name", name_), label_.set_markup(fmt::format(fmt::runtime(format_), fmt::arg("name", name_),
fmt::arg("id", workspace_id_), fmt::arg("id", workspace_id_),
fmt::arg("icon", with_icon_ ? icon() : ""))); fmt::arg("icon", with_icon_ ? icon() : "")));
needs_updating_ = false;
} }
void Workspace::handle_id(const std::string &id) { void Workspace::handle_id(const std::string &id) {
spdlog::debug("[ext/workspaces]: ID for workspace {}: {}", id_, id); spdlog::debug("[ext/workspaces]: ID for workspace {}: {}", id_, id);
workspace_id_ = id; workspace_id_ = id;
needs_updating_ = true;
workspace_manager_.set_needs_sorting(); workspace_manager_.set_needs_sorting();
} }
void Workspace::handle_name(const std::string &name) { void Workspace::handle_name(const std::string &name) {
spdlog::debug("[ext/workspaces]: Name for workspace {}: {}", id_, name); spdlog::debug("[ext/workspaces]: Name for workspace {}: {}", id_, name);
name_ = name; name_ = name;
needs_updating_ = true;
workspace_manager_.set_needs_sorting(); workspace_manager_.set_needs_sorting();
} }
void Workspace::handle_coordinates(const std::vector<uint32_t> &coordinates) { void Workspace::handle_coordinates(const std::vector<uint32_t> &coordinates) {
coordinates_ = coordinates; coordinates_ = coordinates;
needs_updating_ = true;
workspace_manager_.set_needs_sorting(); workspace_manager_.set_needs_sorting();
} }
void Workspace::handle_state(uint32_t state) { void Workspace::handle_state(uint32_t state) { state_ = state; }
state_ = state;
needs_updating_ = true;
}
void Workspace::handle_capabilities(uint32_t capabilities) { void Workspace::handle_capabilities(uint32_t capabilities) {
spdlog::debug("[ext/workspaces]: Capabilities for workspace {}:", id_); spdlog::debug("[ext/workspaces]: Capabilities for workspace {}:", id_);
@ -451,7 +446,6 @@ void Workspace::handle_capabilities(uint32_t capabilities) {
if ((capabilities & EXT_WORKSPACE_HANDLE_V1_WORKSPACE_CAPABILITIES_ASSIGN) == capabilities) { if ((capabilities & EXT_WORKSPACE_HANDLE_V1_WORKSPACE_CAPABILITIES_ASSIGN) == capabilities) {
spdlog::debug("[ext/workspaces]: - assign"); spdlog::debug("[ext/workspaces]: - assign");
} }
needs_updating_ = true;
} }
void Workspace::handle_removed() { void Workspace::handle_removed() {
@ -475,6 +469,8 @@ bool Workspace::handle_clicked(const GdkEventButton *button) const {
if (action == "activate") { if (action == "activate") {
ext_workspace_handle_v1_activate(ext_handle_); ext_workspace_handle_v1_activate(ext_handle_);
} else if (action == "deactivate") {
ext_workspace_handle_v1_deactivate(ext_handle_);
} else if (action == "close") { } else if (action == "close") {
ext_workspace_handle_v1_remove(ext_handle_); ext_workspace_handle_v1_remove(ext_handle_);
} else { } else {

View File

@ -53,7 +53,6 @@ Gamemode::Gamemode(const std::string& id, const Json::Value& config)
if (config_["icon-spacing"].isUInt()) { if (config_["icon-spacing"].isUInt()) {
iconSpacing = config_["icon-spacing"].asUInt(); iconSpacing = config_["icon-spacing"].asUInt();
} }
box_.set_spacing(iconSpacing);
// Whether to use icon or not // Whether to use icon or not
if (config_["use-icon"].isBool()) { if (config_["use-icon"].isBool()) {
@ -64,7 +63,6 @@ Gamemode::Gamemode(const std::string& id, const Json::Value& config)
if (config_["icon-size"].isUInt()) { if (config_["icon-size"].isUInt()) {
iconSize = config_["icon-size"].asUInt(); iconSize = config_["icon-size"].asUInt();
} }
icon_.set_pixel_size(iconSize);
// Format // Format
if (config_["format"].isString()) { if (config_["format"].isString()) {
@ -228,6 +226,11 @@ auto Gamemode::update() -> void {
iconName = DEFAULT_ICON_NAME; iconName = DEFAULT_ICON_NAME;
} }
icon_.set_from_icon_name(iconName, Gtk::ICON_SIZE_INVALID); icon_.set_from_icon_name(iconName, Gtk::ICON_SIZE_INVALID);
box_.set_spacing(iconSpacing);
icon_.set_pixel_size(iconSize);
} else {
box_.set_spacing(0);
icon_.set_pixel_size(0);
} }
// Call parent update // Call parent update

View File

@ -46,9 +46,14 @@ std::filesystem::path IPC::getSocketFolder(const char* instanceSig) {
IPC::IPC() { IPC::IPC() {
// will start IPC and relay events to parseIPC // will start IPC and relay events to parseIPC
ipcThread_ = std::thread([this]() { socketListener(); }); ipcThread_ = std::thread([this]() { socketListener(); });
socketOwnerPid_ = getpid();
} }
IPC::~IPC() { IPC::~IPC() {
// Do no stop Hyprland IPC if a child process (with successful fork() but
// failed exec()) exits.
if (getpid() != socketOwnerPid_) return;
running_ = false; running_ = false;
spdlog::info("Hyprland IPC stopping..."); spdlog::info("Hyprland IPC stopping...");
if (socketfd_ != -1) { if (socketfd_ != -1) {

View File

@ -104,8 +104,25 @@ void Workspace::initializeWindowMap(const Json::Value &clients_data) {
} }
void Workspace::setActiveWindow(WindowAddress const &addr) { void Workspace::setActiveWindow(WindowAddress const &addr) {
for (auto &window : m_windowMap) { std::optional<long> activeIdx;
window.setActive(window.address == addr); for (size_t i = 0; i < m_windowMap.size(); ++i) {
auto &window = m_windowMap[i];
bool isActive = (window.address == addr);
window.setActive(isActive);
if (isActive) {
activeIdx = i;
}
}
auto activeWindowPos = m_workspaceManager.activeWindowPosition();
if (activeIdx.has_value() && activeWindowPos != Workspaces::ActiveWindowPosition::NONE) {
auto window = std::move(m_windowMap[*activeIdx]);
m_windowMap.erase(m_windowMap.begin() + *activeIdx);
if (activeWindowPos == Workspaces::ActiveWindowPosition::FIRST) {
m_windowMap.insert(m_windowMap.begin(), std::move(window));
} else if (activeWindowPos == Workspaces::ActiveWindowPosition::LAST) {
m_windowMap.emplace_back(std::move(window));
}
} }
} }
@ -251,6 +268,17 @@ void Workspace::update(const std::string &workspace_icon) {
} }
} }
bool Workspace::isEmpty() const {
auto ignore_list = m_workspaceManager.getIgnoredWindows();
if (ignore_list.empty()) {
return m_windows == 0;
}
// If there are windows but they are all ignored, consider the workspace empty
return std::all_of(
m_windowMap.begin(), m_windowMap.end(),
[this, &ignore_list](const auto &window_repr) { return shouldSkipWindow(window_repr); });
}
void Workspace::updateTaskbar(const std::string &workspace_icon) { void Workspace::updateTaskbar(const std::string &workspace_icon) {
for (auto child : m_content.get_children()) { for (auto child : m_content.get_children()) {
if (child != &m_labelBefore) { if (child != &m_labelBefore) {
@ -259,9 +287,9 @@ void Workspace::updateTaskbar(const std::string &workspace_icon) {
} }
bool isFirst = true; bool isFirst = true;
for (const auto &window_repr : m_windowMap) { auto processWindow = [&](const WindowRepr &window_repr) {
if (shouldSkipWindow(window_repr)) { if (shouldSkipWindow(window_repr)) {
continue; return; // skip
} }
if (isFirst) { if (isFirst) {
isFirst = false; isFirst = false;
@ -270,6 +298,7 @@ void Workspace::updateTaskbar(const std::string &workspace_icon) {
m_content.pack_start(*windowSeparator, false, false); m_content.pack_start(*windowSeparator, false, false);
windowSeparator->show(); windowSeparator->show();
} }
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_text(window_repr.window_title);
window_box->get_style_context()->add_class("taskbar-window"); window_box->get_style_context()->add_class("taskbar-window");
@ -307,6 +336,16 @@ void Workspace::updateTaskbar(const std::string &workspace_icon) {
m_content.pack_start(*event_box, true, false); m_content.pack_start(*event_box, true, false);
event_box->show_all(); event_box->show_all();
};
if (m_workspaceManager.taskbarReverseDirection()) {
for (auto it = m_windowMap.rbegin(); it != m_windowMap.rend(); ++it) {
processWindow(*it);
}
} else {
for (const auto &window_repr : m_windowMap) {
processWindow(window_repr);
}
} }
auto formatAfter = m_workspaceManager.formatAfter(); auto formatAfter = m_workspaceManager.formatAfter();

View File

@ -727,6 +727,7 @@ auto Workspaces::populateWorkspaceTaskbarConfig(const Json::Value &config) -> vo
populateBoolConfig(workspaceTaskbar, "enable", m_enableTaskbar); populateBoolConfig(workspaceTaskbar, "enable", m_enableTaskbar);
populateBoolConfig(workspaceTaskbar, "update-active-window", m_updateActiveWindow); populateBoolConfig(workspaceTaskbar, "update-active-window", m_updateActiveWindow);
populateBoolConfig(workspaceTaskbar, "reverse-direction", m_taskbarReverseDirection);
if (workspaceTaskbar["format"].isString()) { if (workspaceTaskbar["format"].isString()) {
/* The user defined a format string, use it */ /* The user defined a format string, use it */
@ -774,6 +775,18 @@ auto Workspaces::populateWorkspaceTaskbarConfig(const Json::Value &config) -> vo
} }
} }
} }
if (workspaceTaskbar["active-window-position"].isString()) {
auto posStr = workspaceTaskbar["active-window-position"].asString();
try {
m_activeWindowPosition =
m_activeWindowEnumParser.parseStringToEnum(posStr, m_activeWindowPositionMap);
} catch (const std::invalid_argument &e) {
spdlog::warn(
"Invalid string representation for active-window-position. Falling back to 'none'.");
m_activeWindowPosition = ActiveWindowPosition::NONE;
}
}
} }
void Workspaces::registerOrphanWindow(WindowCreationPayload create_window_payload) { void Workspaces::registerOrphanWindow(WindowCreationPayload create_window_payload) {
@ -1128,9 +1141,9 @@ std::optional<int> Workspaces::parseWorkspaceId(std::string const &workspaceIdSt
try { try {
return workspaceIdStr == "special" ? -99 : std::stoi(workspaceIdStr); return workspaceIdStr == "special" ? -99 : std::stoi(workspaceIdStr);
} catch (std::exception const &e) { } catch (std::exception const &e) {
spdlog::error("Failed to parse workspace ID: {}", e.what()); spdlog::debug("Workspace \"{}\" is not bound to an id: {}", workspaceIdStr, e.what());
return std::nullopt; return std::nullopt;
} }
} }
} // namespace waybar::modules::hyprland } // namespace waybar::modules::hyprland

View File

@ -14,14 +14,20 @@ waybar::modules::Image::Image(const std::string& id, const Json::Value& config)
size_ = config["size"].asInt(); size_ = config["size"].asInt();
interval_ = config_["interval"].asInt(); interval_ = config_["interval"] == "once"
? std::chrono::milliseconds::max()
: std::chrono::milliseconds(std::max(
1L, // Minimum 1ms due to millisecond precision
static_cast<long>(
(config_["interval"].isNumeric() ? config_["interval"].asDouble() : 0) *
1000)));
if (size_ == 0) { if (size_ == 0) {
size_ = 16; size_ = 16;
} }
if (interval_ == 0) { if (interval_.count() == 0) {
interval_ = INT_MAX; interval_ = std::chrono::milliseconds::max();
} }
delayWorker(); delayWorker();
@ -30,8 +36,7 @@ waybar::modules::Image::Image(const std::string& id, const Json::Value& config)
void waybar::modules::Image::delayWorker() { void waybar::modules::Image::delayWorker() {
thread_ = [this] { thread_ = [this] {
dp.emit(); dp.emit();
auto interval = std::chrono::seconds(interval_); thread_.sleep_for(interval_);
thread_.sleep_for(interval);
}; };
} }

View File

@ -333,18 +333,23 @@ auto waybar::modules::Network::update() -> void {
fmt::arg("ipaddr", final_ipaddr_), fmt::arg("gwaddr", gwaddr_), fmt::arg("cidr", cidr_), fmt::arg("ipaddr", final_ipaddr_), fmt::arg("gwaddr", gwaddr_), fmt::arg("cidr", cidr_),
fmt::arg("cidr6", cidr6_), fmt::arg("frequency", fmt::format("{:.1f}", frequency_)), fmt::arg("cidr6", cidr6_), fmt::arg("frequency", fmt::format("{:.1f}", frequency_)),
fmt::arg("icon", getIcon(signal_strength_, state_)), fmt::arg("icon", getIcon(signal_strength_, state_)),
fmt::arg("bandwidthDownBits", pow_format(bandwidth_down * 8ull / interval_.count(), "b/s")), fmt::arg("bandwidthDownBits",
fmt::arg("bandwidthUpBits", pow_format(bandwidth_up * 8ull / interval_.count(), "b/s")), pow_format(bandwidth_down * 8ull / (interval_.count() / 1000.0), "b/s")),
fmt::arg("bandwidthTotalBits", fmt::arg("bandwidthUpBits",
pow_format((bandwidth_up + bandwidth_down) * 8ull / interval_.count(), "b/s")), pow_format(bandwidth_up * 8ull / (interval_.count() / 1000.0), "b/s")),
fmt::arg("bandwidthDownOctets", pow_format(bandwidth_down / interval_.count(), "o/s")), fmt::arg(
fmt::arg("bandwidthUpOctets", pow_format(bandwidth_up / interval_.count(), "o/s")), "bandwidthTotalBits",
pow_format((bandwidth_up + bandwidth_down) * 8ull / (interval_.count() / 1000.0), "b/s")),
fmt::arg("bandwidthDownOctets",
pow_format(bandwidth_down / (interval_.count() / 1000.0), "o/s")),
fmt::arg("bandwidthUpOctets", pow_format(bandwidth_up / (interval_.count() / 1000.0), "o/s")),
fmt::arg("bandwidthTotalOctets", fmt::arg("bandwidthTotalOctets",
pow_format((bandwidth_up + bandwidth_down) / interval_.count(), "o/s")), pow_format((bandwidth_up + bandwidth_down) / (interval_.count() / 1000.0), "o/s")),
fmt::arg("bandwidthDownBytes", pow_format(bandwidth_down / interval_.count(), "B/s")), fmt::arg("bandwidthDownBytes",
fmt::arg("bandwidthUpBytes", pow_format(bandwidth_up / interval_.count(), "B/s")), pow_format(bandwidth_down / (interval_.count() / 1000.0), "B/s")),
fmt::arg("bandwidthUpBytes", pow_format(bandwidth_up / (interval_.count() / 1000.0), "B/s")),
fmt::arg("bandwidthTotalBytes", fmt::arg("bandwidthTotalBytes",
pow_format((bandwidth_up + bandwidth_down) / interval_.count(), "B/s"))); pow_format((bandwidth_up + bandwidth_down) / (interval_.count() / 1000.0), "B/s")));
if (text.compare(label_.get_label()) != 0) { if (text.compare(label_.get_label()) != 0) {
label_.set_markup(text); label_.set_markup(text);
if (text.empty()) { if (text.empty()) {

View File

@ -58,6 +58,16 @@ void Language::doUpdate() {
spdlog::debug("niri language update with short description {}", layout.short_description); spdlog::debug("niri language update with short description {}", layout.short_description);
spdlog::debug("niri language update with variant {}", layout.variant); spdlog::debug("niri language update with variant {}", layout.variant);
if (!last_short_name_.empty()) {
label_.get_style_context()->remove_class(last_short_name_);
}
if (!layout.short_name.empty()) {
label_.get_style_context()->add_class(layout.short_name);
last_short_name_ = layout.short_name;
} else {
last_short_name_.clear();
}
std::string layoutName = std::string{}; std::string layoutName = std::string{};
if (config_.isMember("format-" + layout.short_description + "-" + layout.variant)) { if (config_.isMember("format-" + layout.short_description + "-" + layout.variant)) {
const auto propName = "format-" + layout.short_description + "-" + layout.variant; const auto propName = "format-" + layout.short_description + "-" + layout.variant;

View File

@ -150,11 +150,7 @@ Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &con
button.show(); button.show();
} }
struct wl_output *output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()); box_.signal_show().connect(sigc::mem_fun(*this, &Tags::handle_show));
output_status_ = zriver_status_manager_v1_get_river_output_status(status_manager_, output);
zriver_output_status_v1_add_listener(output_status_, &output_status_listener_impl, this);
zriver_status_manager_v1_destroy(status_manager_);
} }
Tags::~Tags() { Tags::~Tags() {
@ -165,6 +161,19 @@ Tags::~Tags() {
if (control_) { if (control_) {
zriver_control_v1_destroy(control_); zriver_control_v1_destroy(control_);
} }
if (status_manager_) {
zriver_status_manager_v1_destroy(status_manager_);
}
}
void Tags::handle_show() {
struct wl_output *output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj());
output_status_ = zriver_status_manager_v1_get_river_output_status(status_manager_, output);
zriver_output_status_v1_add_listener(output_status_, &output_status_listener_impl, this);
zriver_status_manager_v1_destroy(status_manager_);
status_manager_ = nullptr;
} }
void Tags::handle_primary_clicked(uint32_t tag) { void Tags::handle_primary_clicked(uint32_t tag) {

View File

@ -74,6 +74,13 @@ Item::Item(const std::string& bn, const std::string& op, const Json::Value& conf
cancellable_, interface); cancellable_, interface);
} }
Item::~Item() {
if (this->gtk_menu != nullptr) {
this->gtk_menu->popdown();
this->gtk_menu->detach();
}
}
bool Item::handleMouseEnter(GdkEventCrossing* const& e) { bool Item::handleMouseEnter(GdkEventCrossing* const& e) {
event_box.set_state_flags(Gtk::StateFlags::STATE_FLAG_PRELIGHT); event_box.set_state_flags(Gtk::StateFlags::STATE_FLAG_PRELIGHT);
return false; return false;
@ -443,6 +450,9 @@ void Item::makeMenu() {
gtk_menu->attach_to_widget(event_box); gtk_menu->attach_to_widget(event_box);
} }
} }
// Manually reset prelight to make sure the tray item doesn't stay in a hover state even though
// the menu is focused
event_box.unset_state_flags(Gtk::StateFlags::STATE_FLAG_PRELIGHT);
} }
bool Item::handleClick(GdkEventButton* const& ev) { bool Item::handleClick(GdkEventButton* const& ev) {

View File

@ -74,12 +74,14 @@ auto waybar::modules::Temperature::update() -> void {
if (critical) { if (critical) {
format = config_["format-critical"].isString() ? config_["format-critical"].asString() : format; format = config_["format-critical"].isString() ? config_["format-critical"].asString() : format;
label_.get_style_context()->add_class("critical"); label_.get_style_context()->add_class("critical");
} else if (warning) {
format = config_["format-warning"].isString() ? config_["format-warning"].asString() : format;
label_.get_style_context()->add_class("warning");
} else { } else {
label_.get_style_context()->remove_class("critical"); label_.get_style_context()->remove_class("critical");
label_.get_style_context()->remove_class("warning"); if (warning) {
format = config_["format-warning"].isString() ? config_["format-warning"].asString() : format;
label_.get_style_context()->add_class("warning");
} else {
label_.get_style_context()->remove_class("warning");
}
} }
if (format.empty()) { if (format.empty()) {

View File

@ -41,6 +41,7 @@ EnumType EnumParser<EnumType>::parseStringToEnum(const std::string& str,
// Explicit instantiations for specific EnumType types you intend to use // Explicit instantiations for specific EnumType types you intend to use
// Add explicit instantiations for all relevant EnumType types // Add explicit instantiations for all relevant EnumType types
template struct EnumParser<modules::hyprland::Workspaces::SortMethod>; template struct EnumParser<modules::hyprland::Workspaces::SortMethod>;
template struct EnumParser<modules::hyprland::Workspaces::ActiveWindowPosition>;
template struct EnumParser<util::KillSignalAction>; template struct EnumParser<util::KillSignalAction>;
} // namespace waybar::util } // namespace waybar::util

View File

@ -1,13 +1,13 @@
[wrap-file] [wrap-file]
directory = spdlog-1.14.1 directory = spdlog-1.15.2
source_url = https://github.com/gabime/spdlog/archive/refs/tags/v1.14.1.tar.gz source_url = https://github.com/gabime/spdlog/archive/refs/tags/v1.15.2.tar.gz
source_filename = spdlog-1.14.1.tar.gz source_filename = spdlog-1.15.2.tar.gz
source_hash = 1586508029a7d0670dfcb2d97575dcdc242d3868a259742b69f100801ab4e16b source_hash = 7a80896357f3e8e920e85e92633b14ba0f229c506e6f978578bdc35ba09e9a5d
patch_filename = spdlog_1.14.1-1_patch.zip patch_filename = spdlog_1.15.2-3_patch.zip
patch_url = https://wrapdb.mesonbuild.com/v2/spdlog_1.14.1-1/get_patch patch_url = https://wrapdb.mesonbuild.com/v2/spdlog_1.15.2-3/get_patch
patch_hash = ae878e732330ea1048f90d7e117c40c0cd2a6fb8ae5492c7955818ce3aaade6c patch_hash = d5ab078661f571ef5113a8e4bc5c4121e16c044e7772a24b44b1ca8f3ee7c6cb
source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/spdlog_1.14.1-1/spdlog-1.14.1.tar.gz source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/spdlog_1.15.2-3/spdlog-1.15.2.tar.gz
wrapdb_version = 1.14.1-1 wrapdb_version = 1.15.2-3
[provide] [provide]
spdlog = spdlog_dep spdlog = spdlog_dep

View File

@ -84,6 +84,33 @@ TEST_CASE("Load simple config with include", "[config]") {
} }
} }
TEST_CASE("Load simple config with wildcard include", "[config]") {
waybar::Config conf;
conf.load("test/config/include-wildcard.json");
auto& data = conf.getConfig();
SECTION("validate cpu include file") { REQUIRE(data["cpu"]["format"].asString() == "goo"); }
SECTION("validate memory include file") { REQUIRE(data["memory"]["format"].asString() == "foo"); }
}
TEST_CASE("Load config using relative paths and wildcards", "[config]") {
waybar::Config conf;
const char* old_config_path = std::getenv(waybar::Config::CONFIG_PATH_ENV);
setenv(waybar::Config::CONFIG_PATH_ENV, "test/config", 1);
conf.load("test/config/include-relative-path.json");
auto& data = conf.getConfig();
SECTION("validate cpu include file") { REQUIRE(data["cpu"]["format"].asString() == "goo"); }
SECTION("validate memory include file") { REQUIRE(data["memory"]["format"].asString() == "foo"); }
if (old_config_path)
setenv(waybar::Config::CONFIG_PATH_ENV, old_config_path, 1);
else
unsetenv(waybar::Config::CONFIG_PATH_ENV);
}
TEST_CASE("Load multiple bar config with include", "[config]") { TEST_CASE("Load multiple bar config with include", "[config]") {
waybar::Config conf; waybar::Config conf;
conf.load("test/config/include-multi.json"); conf.load("test/config/include-multi.json");

View File

@ -0,0 +1,5 @@
{
"include": ["modules/*.jsonc"],
"position": "top",
"nullOption": null
}

View File

@ -0,0 +1,5 @@
{
"include": ["test/config/modules/*.jsonc"],
"position": "top",
"nullOption": null
}

View File

@ -0,0 +1,6 @@
{
"cpu": {
"interval": 2,
"format": "goo"
}
}

View File

@ -0,0 +1,6 @@
{
"memory": {
"interval": 2,
"format": "foo",
}
}