Merge upstream/master
Some checks failed
clang-format / lint (push) Has been cancelled
freebsd / build (push) Has been cancelled
linux / build (c++20, alpine) (push) Has been cancelled
linux / build (c++20, archlinux) (push) Has been cancelled
linux / build (c++20, debian) (push) Has been cancelled
linux / build (c++20, fedora) (push) Has been cancelled
linux / build (c++20, gentoo) (push) Has been cancelled
linux / build (c++20, opensuse) (push) Has been cancelled
Nix-Tests / nix-flake-check (push) Has been cancelled

This commit is contained in:
2026-02-27 08:36:07 -08:00
85 changed files with 539 additions and 263 deletions

View File

@ -10,7 +10,7 @@ jobs:
lint: lint:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v6
- uses: RafikFarhad/clang-format-github-action@v6 - uses: RafikFarhad/clang-format-github-action@v6
name: clang-format name: clang-format
with: with:

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,13 +12,13 @@ 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
env: env:
CPPFLAGS: '-isystem/usr/local/include' CPPFLAGS: '-isystem/usr/local/include'
LDFLAGS: '-L/usr/local/lib' LDFLAGS: '-L/usr/local/lib'
with: with:
operating_system: freebsd operating_system: freebsd
version: "14.2" version: "14.2"

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,16 +2,19 @@ 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
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
- run: nix flake show - run: nix flake show
- run: nix flake check --print-build-logs - run: nix flake check --print-build-logs
- run: nix build --print-build-logs - run: nix build --print-build-logs

View File

@ -4,19 +4,19 @@ on:
schedule: schedule:
- cron: '0 0 1 * *' # Run monthly - cron: '0 0 1 * *' # Run monthly
push: push:
paths: paths:
- 'flake.nix' - 'flake.nix'
jobs: jobs:
lockfile: lockfile:
runs-on: ubuntu-latest runs-on: ubuntu-latest
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

View File

@ -48,7 +48,7 @@ class AModule : public IModule {
virtual bool handleMouseLeave(GdkEventCrossing* const& ev); virtual bool handleMouseLeave(GdkEventCrossing* const& ev);
virtual bool handleScroll(GdkEventScroll*); virtual bool handleScroll(GdkEventScroll*);
virtual bool handleRelease(GdkEventButton* const& ev); virtual bool handleRelease(GdkEventButton* const& ev);
GObject* menu_; GObject* menu_ = nullptr;
private: private:
bool handleUserEvent(GdkEventButton* const& ev); bool handleUserEvent(GdkEventButton* const& ev);

View File

@ -35,6 +35,7 @@ class Group : public AModule {
bool handleMouseLeave(GdkEventCrossing* const& ev) override; bool handleMouseLeave(GdkEventCrossing* const& ev) override;
bool handleToggle(GdkEventButton* const& ev) override; bool handleToggle(GdkEventButton* const& ev) override;
void toggle(); void toggle();
bool handleScroll(GdkEventScroll* e) override;
void show_group(); void show_group();
void hide_group(); void hide_group();
void set_visible(bool v) { void set_visible(bool v) {

View File

@ -6,7 +6,7 @@
#if defined(__linux__) #if defined(__linux__)
#include <sys/inotify.h> #include <sys/inotify.h>
#endif #endif
#include <sys/poll.h> #include <poll.h>
#include <algorithm> #include <algorithm>
#include <fstream> #include <fstream>

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

@ -78,6 +78,7 @@ class Mpris : public ALabel {
PlayerctlPlayerManager* manager; PlayerctlPlayerManager* manager;
PlayerctlPlayer* player; PlayerctlPlayer* player;
PlayerctlPlayer* last_active_player_ = nullptr;
std::string lastStatus; std::string lastStatus;
std::string lastPlayer; std::string lastPlayer;

View File

@ -17,7 +17,7 @@ class EventHandler {
class IPC { class IPC {
public: public:
IPC() { startIPC(); } IPC();
void registerForIPC(const std::string& ev, EventHandler* ev_handler); void registerForIPC(const std::string& ev, EventHandler* ev_handler);
void unregisterForIPC(EventHandler* handler); void unregisterForIPC(EventHandler* handler);

View File

@ -12,6 +12,7 @@
#include <string> #include <string>
#include "ipc.hpp" #include "ipc.hpp"
#include "util/SafeSignal.hpp"
#include "util/sleeper_thread.hpp" #include "util/sleeper_thread.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

@ -89,7 +89,7 @@ struct Sock {
} }
}; };
class IPC { class IPC : public std::enable_shared_from_this<IPC> {
static std::weak_ptr<IPC> instance; static std::weak_ptr<IPC> instance;
Json::CharReaderBuilder reader_builder; Json::CharReaderBuilder reader_builder;
Json::StreamWriterBuilder writer_builder; Json::StreamWriterBuilder writer_builder;
@ -98,7 +98,7 @@ class IPC {
State state; State state;
std::mutex state_mutex; std::mutex state_mutex;
IPC() { start(); } IPC() = default;
static auto connect() -> Sock; static auto connect() -> Sock;
auto receive(Sock& sock) -> Json::Value; auto receive(Sock& sock) -> Json::Value;

View File

@ -59,6 +59,7 @@ inline int close(FILE* fp, pid_t pid) {
spdlog::debug("Cmd continued"); spdlog::debug("Cmd continued");
} else if (ret == -1) { } else if (ret == -1) {
spdlog::debug("waitpid failed: {}", strerror(errno)); spdlog::debug("waitpid failed: {}", strerror(errno));
break;
} else { } else {
break; break;
} }
@ -172,8 +173,6 @@ inline int32_t forkExec(const std::string& cmd, const std::string& output_name)
return pid; return pid;
} }
inline int32_t forkExec(const std::string& cmd) { inline int32_t forkExec(const std::string& cmd) { return forkExec(cmd, ""); }
return forkExec(cmd, "");
}
} // namespace waybar::util::command } // namespace waybar::util::command

View File

@ -182,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:
@ -203,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

@ -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

@ -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

@ -151,19 +151,24 @@ 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);
} else if (app_icon_name_.front() == '/') { } else if (app_icon_name_.front() == '/') {
auto pixbuf = Gdk::Pixbuf::create_from_file(app_icon_name_); try {
int scaled_icon_size = app_icon_size_ * image_.get_scale_factor(); int scaled_icon_size = app_icon_size_ * image_.get_scale_factor();
pixbuf = Gdk::Pixbuf::create_from_file(app_icon_name_, scaled_icon_size, scaled_icon_size); auto pixbuf =
Gdk::Pixbuf::create_from_file(app_icon_name_, scaled_icon_size, scaled_icon_size);
auto surface = Gdk::Cairo::create_surface_from_pixbuf(pixbuf, image_.get_scale_factor(), auto surface = Gdk::Cairo::create_surface_from_pixbuf(pixbuf, image_.get_scale_factor(),
image_.get_window()); image_.get_window());
image_.set(surface); image_.set(surface);
image_.set_visible(true); image_.set_visible(true);
} catch (const Glib::Exception& e) {
spdlog::warn("Failed to load app icon {}: {}", app_icon_name_, std::string(e.what()));
image_.set_visible(false);
}
} else { } else {
image_.set_from_icon_name(app_icon_name_, Gtk::ICON_SIZE_INVALID); image_.set_from_icon_name(app_icon_name_, Gtk::ICON_SIZE_INVALID);
image_.set_visible(true); image_.set_visible(true);

View File

@ -91,11 +91,13 @@ ALabel::ALabel(const Json::Value& config, const std::string& name, const std::st
// Make the GtkBuilder and check for errors in his parsing // Make the GtkBuilder and check for errors in his parsing
if (gtk_builder_add_from_string(builder, fileContent.str().c_str(), -1, nullptr) == 0U) { if (gtk_builder_add_from_string(builder, fileContent.str().c_str(), -1, nullptr) == 0U) {
g_object_unref(builder);
throw std::runtime_error("Error found in the file " + menuFile); throw std::runtime_error("Error found in the file " + menuFile);
} }
menu_ = gtk_builder_get_object(builder, "menu"); menu_ = gtk_builder_get_object(builder, "menu");
if (menu_ == nullptr) { if (menu_ == nullptr) {
g_object_unref(builder);
throw std::runtime_error("Failed to get 'menu' object from GtkBuilder"); throw std::runtime_error("Failed to get 'menu' object from GtkBuilder");
} }
submenus_ = std::map<std::string, GtkMenuItem*>(); submenus_ = std::map<std::string, GtkMenuItem*>();
@ -105,11 +107,17 @@ ALabel::ALabel(const Json::Value& config, const std::string& name, const std::st
for (Json::Value::const_iterator it = config_["menu-actions"].begin(); for (Json::Value::const_iterator it = config_["menu-actions"].begin();
it != config_["menu-actions"].end(); ++it) { it != config_["menu-actions"].end(); ++it) {
std::string key = it.key().asString(); std::string key = it.key().asString();
submenus_[key] = GTK_MENU_ITEM(gtk_builder_get_object(builder, key.c_str())); auto* item = gtk_builder_get_object(builder, key.c_str());
if (item == nullptr) {
spdlog::warn("Menu item '{}' not found in builder file", key);
continue;
}
submenus_[key] = GTK_MENU_ITEM(item);
menuActionsMap_[key] = it->asString(); menuActionsMap_[key] = it->asString();
g_signal_connect(submenus_[key], "activate", G_CALLBACK(handleGtkMenuEvent), g_signal_connect(submenus_[key], "activate", G_CALLBACK(handleGtkMenuEvent),
(gpointer)menuActionsMap_[key].c_str()); (gpointer)menuActionsMap_[key].c_str());
} }
g_object_unref(builder);
} catch (std::runtime_error& e) { } catch (std::runtime_error& e) {
spdlog::warn("Error while creating the menu : {}. Menu popup not activated.", e.what()); spdlog::warn("Error while creating the menu : {}. Menu popup not activated.", e.what());
} }
@ -141,7 +149,8 @@ std::string ALabel::getIcon(uint16_t percentage, const std::string& alt, uint16_
if (format_icons.isArray()) { if (format_icons.isArray()) {
auto size = format_icons.size(); auto size = format_icons.size();
if (size != 0U) { if (size != 0U) {
auto idx = std::clamp(percentage / ((max == 0 ? 100 : max) / size), 0U, size - 1); auto divisor = std::max(1U, (max == 0 ? 100U : static_cast<unsigned>(max)) / size);
auto idx = std::clamp(percentage / divisor, 0U, size - 1);
format_icons = format_icons[idx]; format_icons = format_icons[idx];
} }
} }
@ -167,7 +176,8 @@ std::string ALabel::getIcon(uint16_t percentage, const std::vector<std::string>&
if (format_icons.isArray()) { if (format_icons.isArray()) {
auto size = format_icons.size(); auto size = format_icons.size();
if (size != 0U) { if (size != 0U) {
auto idx = std::clamp(percentage / ((max == 0 ? 100 : max) / size), 0U, size - 1); auto divisor = std::max(1U, (max == 0 ? 100U : static_cast<unsigned>(max)) / size);
auto idx = std::clamp(percentage / divisor, 0U, size - 1);
format_icons = format_icons[idx]; format_icons = format_icons[idx];
} }
} }

View File

@ -166,9 +166,9 @@ bool AModule::handleUserEvent(GdkEventButton* const& e) {
} }
// Check that a menu has been configured // Check that a menu has been configured
if (config_["menu"].isString()) { if (rec != eventMap_.cend() && config_["menu"].isString()) {
// Check if the event is the one specified for the "menu" option // Check if the event is the one specified for the "menu" option
if (rec->second == config_["menu"].asString()) { if (rec->second == config_["menu"].asString() && menu_ != nullptr) {
// Popup the menu // 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));

View File

@ -143,6 +143,11 @@ void Group::refresh(int sig) {
} }
} }
bool Group::handleScroll(GdkEventScroll* e) {
// no scroll.
return true;
}
Gtk::Box& Group::getBox() { return is_drawer ? (is_first_widget ? box : revealer_box) : box; } Gtk::Box& Group::getBox() { return is_drawer ? (is_first_widget ? box : revealer_box) : box; }
void Group::addWidget(Gtk::Widget& widget) { void Group::addWidget(Gtk::Widget& widget) {

View File

@ -33,7 +33,10 @@ static void writeSignalToPipe(int signum) {
// to `signal_handler`. // to `signal_handler`.
static void catchSignals(waybar::SafeSignal<int>& signal_handler) { static void catchSignals(waybar::SafeSignal<int>& signal_handler) {
int fd[2]; int fd[2];
pipe(fd); if (pipe(fd) != 0) {
spdlog::error("Failed to create signal pipe: {}", strerror(errno));
return;
}
int signal_pipe_read_fd = fd[0]; int signal_pipe_read_fd = fd[0];
signal_pipe_write_fd = fd[1]; signal_pipe_write_fd = fd[1];
@ -138,15 +141,16 @@ static void handleSignalMainThread(int signum, bool& reload) {
break; break;
case SIGCHLD: case SIGCHLD:
spdlog::debug("Received SIGCHLD in signalThread"); spdlog::debug("Received SIGCHLD in signalThread");
if (!reap.empty()) { {
reap_mtx.lock(); std::lock_guard<std::mutex> lock(reap_mtx);
for (auto it = reap.begin(); it != reap.end(); ++it) { for (auto it = reap.begin(); it != reap.end();) {
if (waitpid(*it, nullptr, WNOHANG) == *it) { if (waitpid(*it, nullptr, WNOHANG) == *it) {
spdlog::debug("Reaped child with PID: {}", *it); spdlog::debug("Reaped child with PID: {}", *it);
it = reap.erase(it); it = reap.erase(it);
} else {
++it;
} }
} }
reap_mtx.unlock();
} }
break; break;
default: default:

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

@ -139,7 +139,9 @@ void waybar::modules::Battery::refreshBatteries() {
auto event_path = (node.path() / "uevent"); auto event_path = (node.path() / "uevent");
auto wd = inotify_add_watch(battery_watch_fd_, event_path.c_str(), IN_ACCESS); auto wd = inotify_add_watch(battery_watch_fd_, event_path.c_str(), IN_ACCESS);
if (wd < 0) { if (wd < 0) {
throw std::runtime_error("Could not watch events for " + node.path().string()); spdlog::warn("Could not watch events for {} (device may have been removed)",
node.path().string());
continue;
} }
batteries_[node.path()] = wd; batteries_[node.path()] = wd;
} }
@ -686,7 +688,7 @@ auto waybar::modules::Battery::update() -> void {
status = getAdapterStatus(capacity); status = getAdapterStatus(capacity);
} }
auto status_pretty = status; auto status_pretty = status;
puts(status.c_str());
// 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); });
@ -790,16 +792,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

@ -111,13 +111,16 @@ waybar::modules::Bluetooth::Bluetooth(const std::string& id, const Json::Value&
findConnectedDevices(cur_controller_->path, connected_devices_); findConnectedDevices(cur_controller_->path, connected_devices_);
} }
g_signal_connect(manager_.get(), "object-added", G_CALLBACK(onObjectAdded), this); if (manager_) {
g_signal_connect(manager_.get(), "object-removed", G_CALLBACK(onObjectRemoved), this); g_signal_connect(manager_.get(), "object-added", G_CALLBACK(onObjectAdded), this);
g_signal_connect(manager_.get(), "interface-proxy-properties-changed", g_signal_connect(manager_.get(), "object-removed", G_CALLBACK(onObjectRemoved), this);
G_CALLBACK(onInterfaceProxyPropertiesChanged), this); g_signal_connect(manager_.get(), "interface-proxy-properties-changed",
g_signal_connect(manager_.get(), "interface-added", G_CALLBACK(onInterfaceAddedOrRemoved), this); G_CALLBACK(onInterfaceProxyPropertiesChanged), this);
g_signal_connect(manager_.get(), "interface-removed", G_CALLBACK(onInterfaceAddedOrRemoved), g_signal_connect(manager_.get(), "interface-added", G_CALLBACK(onInterfaceAddedOrRemoved),
this); this);
g_signal_connect(manager_.get(), "interface-removed", G_CALLBACK(onInterfaceAddedOrRemoved),
this);
}
#ifdef WANT_RFKILL #ifdef WANT_RFKILL
rfkill_.on_update.connect(sigc::hide(sigc::mem_fun(*this, &Bluetooth::update))); rfkill_.on_update.connect(sigc::hide(sigc::mem_fun(*this, &Bluetooth::update)));
@ -446,6 +449,10 @@ auto waybar::modules::Bluetooth::getControllerProperties(GDBusObject* object,
auto waybar::modules::Bluetooth::findCurController() -> std::optional<ControllerInfo> { auto waybar::modules::Bluetooth::findCurController() -> std::optional<ControllerInfo> {
std::optional<ControllerInfo> controller_info; std::optional<ControllerInfo> controller_info;
if (!manager_) {
return controller_info;
}
GList* objects = g_dbus_object_manager_get_objects(manager_.get()); GList* objects = g_dbus_object_manager_get_objects(manager_.get());
for (GList* l = objects; l != NULL; l = l->next) { for (GList* l = objects; l != NULL; l = l->next) {
GDBusObject* object = G_DBUS_OBJECT(l->data); GDBusObject* object = G_DBUS_OBJECT(l->data);
@ -465,6 +472,9 @@ auto waybar::modules::Bluetooth::findCurController() -> std::optional<Controller
auto waybar::modules::Bluetooth::findConnectedDevices(const std::string& cur_controller_path, auto waybar::modules::Bluetooth::findConnectedDevices(const std::string& cur_controller_path,
std::vector<DeviceInfo>& connected_devices) std::vector<DeviceInfo>& connected_devices)
-> void { -> void {
if (!manager_) {
return;
}
GList* objects = g_dbus_object_manager_get_objects(manager_.get()); GList* objects = g_dbus_object_manager_get_objects(manager_.get());
for (GList* l = objects; l != NULL; l = l->next) { for (GList* l = objects; l != NULL; l = l->next) {
GDBusObject* object = G_DBUS_OBJECT(l->data); GDBusObject* object = G_DBUS_OBJECT(l->data);

View File

@ -26,6 +26,7 @@ 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]() { 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")) if (!label_.get_style_context()->has_class("updated"))
label_.get_style_context()->add_class("updated"); label_.get_style_context()->add_class("updated");
@ -38,7 +39,6 @@ auto waybar::modules::cava::Cava::onUpdate(const std::string& input) -> void {
label_.show(); label_.show();
ALabel::update(); ALabel::update();
}); });
silence_ = false;
} }
auto waybar::modules::cava::Cava::onSilence() -> void { auto waybar::modules::cava::Cava::onSilence() -> void {

View File

@ -218,7 +218,7 @@ void waybar::modules::cava::CavaBackend::loadConfig() {
prm_.input = ::cava::input_method_by_name(config_["method"].asString().c_str()); prm_.input = ::cava::input_method_by_name(config_["method"].asString().c_str());
if (config_["source"].isString()) { if (config_["source"].isString()) {
if (prm_.audio_source) free(prm_.audio_source); if (prm_.audio_source) free(prm_.audio_source);
prm_.audio_source = config_["source"].asString().data(); prm_.audio_source = strdup(config_["source"].asString().c_str());
} }
if (config_["sample_rate"].isNumeric()) prm_.samplerate = config_["sample_rate"].asLargestInt(); if (config_["sample_rate"].isNumeric()) prm_.samplerate = config_["sample_rate"].asLargestInt();
if (config_["sample_bits"].isInt()) prm_.samplebits = config_["sample_bits"].asInt(); if (config_["sample_bits"].isInt()) prm_.samplebits = config_["sample_bits"].asInt();

View File

@ -35,7 +35,7 @@ CFFI::CFFI(const std::string& name, const std::string& id, const Json::Value& co
throw std::runtime_error{std::string{"Missing wbcffi_init function: "} + dlerror()}; throw std::runtime_error{std::string{"Missing wbcffi_init function: "} + dlerror()};
} }
hooks_.deinit = reinterpret_cast<DenitFn*>(dlsym(handle, "wbcffi_deinit")); hooks_.deinit = reinterpret_cast<DenitFn*>(dlsym(handle, "wbcffi_deinit"));
if (!hooks_.init) { if (!hooks_.deinit) {
throw std::runtime_error{std::string{"Missing wbcffi_deinit function: "} + dlerror()}; throw std::runtime_error{std::string{"Missing wbcffi_deinit function: "} + dlerror()};
} }
// Optional functions // Optional functions

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);
@ -48,14 +46,25 @@ auto waybar::modules::Cpu::update() -> void {
store.push_back(fmt::arg("max_frequency", max_frequency)); store.push_back(fmt::arg("max_frequency", max_frequency));
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));
std::vector<std::string> arg_names;
arg_names.reserve(cpu_usage.size() * 2);
for (size_t i = 1; i < cpu_usage.size(); ++i) { for (size_t i = 1; i < cpu_usage.size(); ++i) {
auto core_i = i - 1; auto core_i = i - 1;
auto core_format = fmt::format("usage{}", core_i); arg_names.push_back(fmt::format("usage{}", core_i));
store.push_back(fmt::arg(core_format.c_str(), cpu_usage[i])); store.push_back(fmt::arg(arg_names.back().c_str(), cpu_usage[i]));
auto icon_format = fmt::format("icon{}", core_i); arg_names.push_back(fmt::format("icon{}", core_i));
store.push_back(fmt::arg(icon_format.c_str(), getIcon(cpu_usage[i], icons))); store.push_back(fmt::arg(arg_names.back().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);
@ -38,14 +36,25 @@ auto waybar::modules::CpuUsage::update() -> void {
fmt::dynamic_format_arg_store<fmt::format_context> store; fmt::dynamic_format_arg_store<fmt::format_context> store;
store.push_back(fmt::arg("usage", total_usage)); store.push_back(fmt::arg("usage", total_usage));
store.push_back(fmt::arg("icon", getIcon(total_usage, icons))); store.push_back(fmt::arg("icon", getIcon(total_usage, icons)));
std::vector<std::string> arg_names;
arg_names.reserve(cpu_usage.size() * 2);
for (size_t i = 1; i < cpu_usage.size(); ++i) { for (size_t i = 1; i < cpu_usage.size(); ++i) {
auto core_i = i - 1; auto core_i = i - 1;
auto core_format = fmt::format("usage{}", core_i); arg_names.push_back(fmt::format("usage{}", core_i));
store.push_back(fmt::arg(core_format.c_str(), cpu_usage[i])); store.push_back(fmt::arg(arg_names.back().c_str(), cpu_usage[i]));
auto icon_format = fmt::format("icon{}", core_i); arg_names.push_back(fmt::format("icon{}", core_i));
store.push_back(fmt::arg(icon_format.c_str(), getIcon(cpu_usage[i], icons))); store.push_back(fmt::arg(arg_names.back().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
@ -71,7 +80,8 @@ std::tuple<std::vector<uint16_t>, std::string> waybar::modules::CpuUsage::getCpu
auto [prev_idle, prev_total] = prev_times[0]; auto [prev_idle, prev_total] = prev_times[0];
const float delta_idle = curr_idle - prev_idle; const float delta_idle = curr_idle - prev_idle;
const float delta_total = curr_total - prev_total; const float delta_total = curr_total - prev_total;
uint16_t tmp = 100 * (1 - delta_idle / delta_total); uint16_t tmp =
(delta_total > 0) ? static_cast<uint16_t>(100 * (1 - delta_idle / delta_total)) : 0;
tooltip = fmt::format("Total: {}%\nCores: (pending)", tmp); tooltip = fmt::format("Total: {}%\nCores: (pending)", tmp);
usage.push_back(tmp); usage.push_back(tmp);
} else { } else {
@ -93,7 +103,8 @@ std::tuple<std::vector<uint16_t>, std::string> waybar::modules::CpuUsage::getCpu
} }
const float delta_idle = curr_idle - prev_idle; const float delta_idle = curr_idle - prev_idle;
const float delta_total = curr_total - prev_total; const float delta_total = curr_total - prev_total;
uint16_t tmp = 100 * (1 - delta_idle / delta_total); uint16_t tmp =
(delta_total > 0) ? static_cast<uint16_t>(100 * (1 - delta_idle / delta_total)) : 0;
if (i == 0) { if (i == 0) {
tooltip = fmt::format("Total: {}%", tmp); tooltip = fmt::format("Total: {}%", tmp);
} else { } else {

View File

@ -182,9 +182,10 @@ auto waybar::modules::Custom::update() -> void {
if (tooltipEnabled()) { if (tooltipEnabled()) {
if (tooltip_format_enabled_) { if (tooltip_format_enabled_) {
auto tooltip = config_["tooltip-format"].asString(); auto tooltip = config_["tooltip-format"].asString();
tooltip = fmt::format( tooltip = fmt::format(fmt::runtime(tooltip), fmt::arg("text", text_),
fmt::runtime(tooltip), fmt::arg("text", text_), fmt::arg("alt", alt_), fmt::arg("tooltip", tooltip_), fmt::arg("alt", alt_),
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_) {
label_.set_tooltip_markup(str); label_.set_tooltip_markup(str);

View File

@ -41,7 +41,7 @@ auto waybar::modules::Disk::update() -> void {
fs_used - File system used space fs_used - File system used space
*/ */
if (err != 0) { if (err != 0 || stats.f_blocks == 0) {
event_box_.hide(); event_box_.hide();
return; return;
} }
@ -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),
@ -112,4 +112,4 @@ float waybar::modules::Disk::calc_specific_divisor(std::string divisor) {
} else { // default to Bytes if it is anything that we don't recongnise } else { // default to Bytes if it is anything that we don't recongnise
return 1.0; return 1.0;
} }
} }

View File

@ -179,6 +179,7 @@ bool Tags::handle_button_press(GdkEventButton* event_button, uint32_t tag) {
} }
void Tags::handle_view_tags(uint32_t tag, uint32_t state, uint32_t clients, uint32_t focused) { void Tags::handle_view_tags(uint32_t tag, uint32_t state, uint32_t clients, uint32_t focused) {
if (tag >= buttons_.size()) return;
// First clear all occupied state // First clear all occupied state
auto& button = buttons_[tag]; auto& button = buttons_[tag];
if (clients) { if (clients) {

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

@ -143,7 +143,9 @@ void WorkspaceManager::handle_finished() {
ext_manager_ = nullptr; ext_manager_ = nullptr;
} }
void WorkspaceManager::commit() const { ext_workspace_manager_v1_commit(ext_manager_); } void WorkspaceManager::commit() const {
if (ext_manager_) ext_workspace_manager_v1_commit(ext_manager_);
}
void WorkspaceManager::update() { void WorkspaceManager::update() {
spdlog::debug("[ext/workspaces]: Updating state"); spdlog::debug("[ext/workspaces]: Updating state");

View File

@ -128,9 +128,9 @@ void Gamemode::getData() {
Glib::VariantContainerBase data = gamemode_proxy->call_sync("Get", parameters); Glib::VariantContainerBase data = gamemode_proxy->call_sync("Get", parameters);
if (data && data.is_of_type(Glib::VariantType("(v)"))) { if (data && data.is_of_type(Glib::VariantType("(v)"))) {
Glib::VariantBase variant; Glib::VariantBase variant;
g_variant_get(data.gobj_copy(), "(v)", &variant); g_variant_get(const_cast<GVariant*>(data.gobj()), "(v)", &variant);
if (variant && variant.is_of_type(Glib::VARIANT_TYPE_INT32)) { if (variant && variant.is_of_type(Glib::VARIANT_TYPE_INT32)) {
g_variant_get(variant.gobj_copy(), "i", &gameCount); g_variant_get(const_cast<GVariant*>(variant.gobj()), "i", &gameCount);
return; return;
} }
} }
@ -158,7 +158,7 @@ void Gamemode::prepareForSleep_cb(const Glib::RefPtr<Gio::DBus::Connection>& con
const Glib::VariantContainerBase& parameters) { const Glib::VariantContainerBase& parameters) {
if (parameters.is_of_type(Glib::VariantType("(b)"))) { if (parameters.is_of_type(Glib::VariantType("(b)"))) {
gboolean sleeping; gboolean sleeping;
g_variant_get(parameters.gobj_copy(), "(b)", &sleeping); g_variant_get(const_cast<GVariant*>(parameters.gobj()), "(b)", &sleeping);
if (!sleeping) { if (!sleeping) {
getData(); getData();
dp.emit(); dp.emit();
@ -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

@ -60,7 +60,7 @@ auto Submap::update() -> void {
} 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();
} }

View File

@ -72,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);
} }
} }
@ -237,9 +237,7 @@ void Window::queryActiveWorkspace() {
} }
} }
void Window::onEvent(const std::string& ev) { void Window::onEvent(const std::string& ev) { dp.emit(); }
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

@ -38,7 +38,7 @@ 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(); queryActiveWorkspace();
std::string format = config_["format"].asString(); std::string format = config_["format"].asString();
@ -58,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();
@ -125,9 +125,7 @@ void WindowCount::queryActiveWorkspace() {
} }
} }
void WindowCount::onEvent(const std::string& ev) { void WindowCount::onEvent(const std::string& ev) { dp.emit(); }
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

@ -300,7 +300,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();
} }
@ -636,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);
@ -1151,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();
} }

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

@ -53,7 +53,7 @@ std::string JACK::JACKState() {
auto JACK::update() -> void { auto JACK::update() -> void {
std::string format; std::string format;
std::string state = JACKState(); std::string state = JACKState();
float latency = 1000 * (float)bufsize_ / (float)samplerate_; float latency = samplerate_ > 0 ? 1000.0f * (float)bufsize_ / (float)samplerate_ : 0.0f;
if (label_.get_style_context()->has_class("xrun")) { if (label_.get_style_context()->has_class("xrun")) {
label_.get_style_context()->remove_class("xrun"); label_.get_style_context()->remove_class("xrun");
@ -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_)));
@ -91,16 +91,19 @@ auto JACK::update() -> void {
} }
int JACK::bufSize(jack_nframes_t size) { int JACK::bufSize(jack_nframes_t size) {
std::lock_guard<std::mutex> lock(mutex_);
bufsize_ = size; bufsize_ = size;
return 0; return 0;
} }
int JACK::sampleRate(jack_nframes_t rate) { int JACK::sampleRate(jack_nframes_t rate) {
std::lock_guard<std::mutex> lock(mutex_);
samplerate_ = rate; samplerate_ = rate;
return 0; return 0;
} }
int JACK::xrun() { int JACK::xrun() {
std::lock_guard<std::mutex> lock(mutex_);
xruns_ += 1; xruns_ += 1;
state_ = "xrun"; state_ = "xrun";
return 0; return 0;

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,11 +260,17 @@ auto waybar::modules::KeyboardState::update() -> void {
try { try {
std::string dev_path; std::string dev_path;
if (config_["device-path"].isString() && {
libinput_devices_.find(config_["device-path"].asString()) != libinput_devices_.end()) { std::lock_guard<std::mutex> lock(devices_mutex_);
dev_path = config_["device-path"].asString(); if (libinput_devices_.empty()) {
} else { return;
dev_path = libinput_devices_.begin()->first; }
if (config_["device-path"].isString() &&
libinput_devices_.find(config_["device-path"].asString()) != libinput_devices_.end()) {
dev_path = config_["device-path"].asString();
} else {
dev_path = libinput_devices_.begin()->first;
}
} }
int fd = openFile(dev_path, O_NONBLOCK | O_CLOEXEC | O_RDONLY); int fd = openFile(dev_path, O_NONBLOCK | O_CLOEXEC | O_RDONLY);
auto dev = openDevice(fd); auto dev = openDevice(fd);
@ -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());
libinput_device_ref(device); if (device) {
libinput_devices_[dev_path] = device; libinput_device_ref(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

@ -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

@ -110,7 +110,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;
} }
@ -210,7 +210,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());
} }
@ -323,6 +323,7 @@ void waybar::modules::MPD::checkErrors(mpd_connection* conn) {
case MPD_ERROR_SYSTEM: case MPD_ERROR_SYSTEM:
if (auto ec = mpd_connection_get_system_error(conn); ec != 0) { if (auto ec = mpd_connection_get_system_error(conn); ec != 0) {
mpd_connection_clear_error(conn); mpd_connection_clear_error(conn);
connection_.reset();
throw std::system_error(ec, std::system_category()); throw std::system_error(ec, std::system_category());
} }
G_GNUC_FALLTHROUGH; G_GNUC_FALLTHROUGH;

View File

@ -117,7 +117,7 @@ Mpris::Mpris(const std::string& id, const Json::Value& config)
} }
GError* error = nullptr; GError* error = nullptr;
waybar::util::ScopeGuard error_deleter([error]() { waybar::util::ScopeGuard error_deleter([&error]() {
if (error) { if (error) {
g_error_free(error); g_error_free(error);
} }
@ -178,6 +178,7 @@ Mpris::Mpris(const std::string& id, const Json::Value& config)
} }
Mpris::~Mpris() { Mpris::~Mpris() {
if (last_active_player_ && last_active_player_ != player) g_object_unref(last_active_player_);
if (manager != nullptr) g_object_unref(manager); if (manager != nullptr) g_object_unref(manager);
if (player != nullptr) g_object_unref(player); if (player != nullptr) g_object_unref(player);
} }
@ -458,10 +459,7 @@ auto Mpris::onPlayerStop(PlayerctlPlayer* player, gpointer data) -> void {
if (!mpris) return; if (!mpris) return;
spdlog::debug("mpris: player-stop callback"); spdlog::debug("mpris: player-stop callback");
// update widget (update() handles visibility)
// hide widget
mpris->event_box_.set_visible(false);
// update widget
mpris->dp.emit(); mpris->dp.emit();
} }
@ -480,7 +478,7 @@ auto Mpris::getPlayerInfo() -> std::optional<PlayerInfo> {
} }
GError* error = nullptr; GError* error = nullptr;
waybar::util::ScopeGuard error_deleter([error]() { waybar::util::ScopeGuard error_deleter([&error]() {
if (error) { if (error) {
g_error_free(error); g_error_free(error);
} }
@ -488,7 +486,12 @@ auto Mpris::getPlayerInfo() -> std::optional<PlayerInfo> {
char* player_status = nullptr; char* player_status = nullptr;
auto player_playback_status = PLAYERCTL_PLAYBACK_STATUS_STOPPED; auto player_playback_status = PLAYERCTL_PLAYBACK_STATUS_STOPPED;
g_object_get(player, "status", &player_status, "playback-status", &player_playback_status, NULL);
// Clean up previous fallback player
if (last_active_player_ && last_active_player_ != player) {
g_object_unref(last_active_player_);
last_active_player_ = nullptr;
}
std::string player_name = player_; std::string player_name = player_;
if (player_name == "playerctld") { if (player_name == "playerctld") {
@ -498,19 +501,52 @@ auto Mpris::getPlayerInfo() -> std::optional<PlayerInfo> {
} }
// > get the list of players [..] in order of activity // > get the list of players [..] in order of activity
// https://github.com/altdesktop/playerctl/blob/b19a71cb9dba635df68d271bd2b3f6a99336a223/playerctl/playerctl-common.c#L248-L249 // https://github.com/altdesktop/playerctl/blob/b19a71cb9dba635df68d271bd2b3f6a99336a223/playerctl/playerctl-common.c#L248-L249
players = g_list_first(players); PlayerctlPlayer* first_valid_player = nullptr;
if (players) std::string first_valid_name;
player_name = static_cast<PlayerctlPlayerName*>(players->data)->name; for (auto* p = g_list_first(players); p != nullptr; p = p->next) {
else auto* pn = static_cast<PlayerctlPlayerName*>(p->data);
return std::nullopt; // no players found, hide the widget std::string name = pn->name;
} if (std::any_of(ignored_players_.begin(), ignored_players_.end(),
[&](const std::string& ignored) { return name == ignored; })) {
if (std::any_of(ignored_players_.begin(), ignored_players_.end(), spdlog::warn("mpris[{}]: ignoring player update", name);
[&](const std::string& pn) { return player_name == pn; })) { continue;
}
auto* tmp = playerctl_player_new_from_name(pn, &error);
if (error || !tmp) continue;
if (!first_valid_player) {
first_valid_player = tmp;
first_valid_name = name;
}
PlayerctlPlaybackStatus status;
g_object_get(tmp, "playback-status", &status, NULL);
if (status == PLAYERCTL_PLAYBACK_STATUS_PLAYING) {
if (tmp != first_valid_player) g_object_unref(first_valid_player);
last_active_player_ = tmp;
player_name = name;
break;
}
if (tmp != first_valid_player) g_object_unref(tmp);
}
if (!last_active_player_) {
if (!first_valid_player) return std::nullopt;
last_active_player_ = first_valid_player;
player_name = first_valid_name;
}
} else if (std::any_of(ignored_players_.begin(), ignored_players_.end(),
[&](const std::string& pn) { return player_name == pn; })) {
spdlog::warn("mpris[{}]: ignoring player update", player_name); spdlog::warn("mpris[{}]: ignoring player update", player_name);
return std::nullopt; return std::nullopt;
} else {
last_active_player_ = player;
} }
g_object_get(last_active_player_, "status", &player_status, "playback-status",
&player_playback_status, NULL);
if (!player_status) {
spdlog::error("mpris: failed to get player status");
return std::nullopt;
}
// make status lowercase // make status lowercase
player_status[0] = std::tolower(player_status[0]); player_status[0] = std::tolower(player_status[0]);
@ -524,28 +560,29 @@ auto Mpris::getPlayerInfo() -> std::optional<PlayerInfo> {
.length = std::nullopt, .length = std::nullopt,
}; };
if (auto* artist_ = playerctl_player_get_artist(player, &error)) { if (auto* artist_ = playerctl_player_get_artist(last_active_player_, &error)) {
spdlog::debug("mpris[{}]: artist = {}", info.name, artist_); spdlog::debug("mpris[{}]: artist = {}", info.name, artist_);
info.artist = artist_; info.artist = artist_;
g_free(artist_); g_free(artist_);
} }
if (error) goto errorexit; if (error) goto errorexit;
if (auto* album_ = playerctl_player_get_album(player, &error)) { if (auto* album_ = playerctl_player_get_album(last_active_player_, &error)) {
spdlog::debug("mpris[{}]: album = {}", info.name, album_); spdlog::debug("mpris[{}]: album = {}", info.name, album_);
info.album = album_; info.album = album_;
g_free(album_); g_free(album_);
} }
if (error) goto errorexit; if (error) goto errorexit;
if (auto* title_ = playerctl_player_get_title(player, &error)) { if (auto* title_ = playerctl_player_get_title(last_active_player_, &error)) {
spdlog::debug("mpris[{}]: title = {}", info.name, title_); spdlog::debug("mpris[{}]: title = {}", info.name, title_);
info.title = title_; info.title = title_;
g_free(title_); g_free(title_);
} }
if (error) goto errorexit; if (error) goto errorexit;
if (auto* length_ = playerctl_player_print_metadata_prop(player, "mpris:length", &error)) { if (auto* length_ =
playerctl_player_print_metadata_prop(last_active_player_, "mpris:length", &error)) {
spdlog::debug("mpris[{}]: mpris:length = {}", info.name, length_); spdlog::debug("mpris[{}]: mpris:length = {}", info.name, length_);
auto len = std::chrono::microseconds(std::strtol(length_, nullptr, 10)); auto len = std::chrono::microseconds(std::strtol(length_, nullptr, 10));
auto len_h = std::chrono::duration_cast<std::chrono::hours>(len); auto len_h = std::chrono::duration_cast<std::chrono::hours>(len);
@ -557,7 +594,7 @@ auto Mpris::getPlayerInfo() -> std::optional<PlayerInfo> {
if (error) goto errorexit; if (error) goto errorexit;
{ {
auto position_ = playerctl_player_get_position(player, &error); auto position_ = playerctl_player_get_position(last_active_player_, &error);
if (error) { if (error) {
// it's fine to have an error here because not all players report a position // it's fine to have an error here because not all players report a position
g_error_free(error); g_error_free(error);
@ -609,12 +646,13 @@ bool Mpris::handleToggle(GdkEventButton* const& e) {
}); });
// Command pattern: encapsulate each button's action // Command pattern: encapsulate each button's action
auto* target = last_active_player_ ? last_active_player_ : player;
const ButtonAction actions[] = { const ButtonAction actions[] = {
{1, "on-click", [&]() { playerctl_player_play_pause(player, &error); }}, {1, "on-click", [&]() { playerctl_player_play_pause(target, &error); }},
{2, "on-click-middle", [&]() { playerctl_player_previous(player, &error); }}, {2, "on-click-middle", [&]() { playerctl_player_previous(target, &error); }},
{3, "on-click-right", [&]() { playerctl_player_next(player, &error); }}, {3, "on-click-right", [&]() { playerctl_player_next(target, &error); }},
{8, "on-click-backward", [&]() { playerctl_player_previous(player, &error); }}, {8, "on-click-backward", [&]() { playerctl_player_previous(target, &error); }},
{9, "on-click-forward", [&]() { playerctl_player_next(player, &error); }}, {9, "on-click-forward", [&]() { playerctl_player_next(target, &error); }},
}; };
for (const auto& action : actions) { for (const auto& action : actions) {
@ -734,7 +772,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

@ -45,7 +45,8 @@ waybar::modules::Network::readBandwidthUsage() {
std::istringstream iss(line); std::istringstream iss(line);
std::string ifacename; std::string ifacename;
iss >> ifacename; // ifacename contains "eth0:" iss >> ifacename; // ifacename contains "eth0:"
if (ifacename.empty()) continue;
ifacename.pop_back(); // remove trailing ':' ifacename.pop_back(); // remove trailing ':'
if (ifacename != ifname_) { if (ifacename != ifname_) {
continue; continue;

View File

@ -20,12 +20,13 @@
namespace waybar::modules::niri { namespace waybar::modules::niri {
IPC::IPC() { startIPC(); }
int IPC::connectToSocket() { int IPC::connectToSocket() {
const char* socket_path = getenv("NIRI_SOCKET"); const char* socket_path = getenv("NIRI_SOCKET");
if (socket_path == nullptr) { if (socket_path == nullptr) {
spdlog::warn("Niri is not running, niri IPC will not be available."); throw std::runtime_error("Niri IPC: NIRI_SOCKET was not set! (Is Niri running?)");
return -1;
} }
struct sockaddr_un addr; struct sockaddr_un addr;
@ -54,16 +55,9 @@ int IPC::connectToSocket() {
void IPC::startIPC() { void IPC::startIPC() {
// will start IPC and relay events to parseIPC // will start IPC and relay events to parseIPC
std::thread([&]() { int socketfd = connectToSocket();
int socketfd;
try {
socketfd = connectToSocket();
} catch (std::exception& e) {
spdlog::error("Niri IPC: failed to start, reason: {}", e.what());
return;
}
if (socketfd == -1) return;
std::thread([this, socketfd]() {
spdlog::info("Niri IPC starting"); spdlog::info("Niri IPC starting");
auto unix_istream = Gio::UnixInputStream::create(socketfd, true); auto unix_istream = Gio::UnixInputStream::create(socketfd, true);
@ -242,7 +236,6 @@ void IPC::unregisterForIPC(EventHandler* ev_handler) {
Json::Value IPC::send(const Json::Value& request) { Json::Value IPC::send(const Json::Value& request) {
int socketfd = connectToSocket(); int socketfd = connectToSocket();
if (socketfd == -1) throw std::runtime_error("Niri is not running");
auto unix_istream = Gio::UnixInputStream::create(socketfd, true); auto unix_istream = Gio::UnixInputStream::create(socketfd, true);
auto unix_ostream = Gio::UnixOutputStream::create(socketfd, false); auto unix_ostream = Gio::UnixOutputStream::create(socketfd, false);

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
@ -176,6 +176,10 @@ auto PowerProfilesDaemon::update() -> void {
bool PowerProfilesDaemon::handleToggle(GdkEventButton* const& e) { bool PowerProfilesDaemon::handleToggle(GdkEventButton* const& e) {
if (e->type == GdkEventType::GDK_BUTTON_PRESS && connected_) { if (e->type == GdkEventType::GDK_BUTTON_PRESS && connected_) {
if (availableProfiles_.empty()) return true;
if (activeProfile_ == availableProfiles_.end()) {
activeProfile_ = availableProfiles_.begin();
}
if (e->button == 1) /* left click */ { if (e->button == 1) /* left click */ {
activeProfile_++; activeProfile_++;
if (activeProfile_ == availableProfiles_.end()) { if (activeProfile_ == availableProfiles_.end()) {

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

@ -118,6 +118,7 @@ Layout::Layout(const std::string& id, const waybar::Bar& bar, const Json::Value&
if (!seat_) { if (!seat_) {
spdlog::error("wl_seat not advertised"); spdlog::error("wl_seat not advertised");
return;
} }
label_.hide(); label_.hide();

View File

@ -77,6 +77,7 @@ Mode::Mode(const std::string& id, const waybar::Bar& bar, const Json::Value& con
if (!seat_) { if (!seat_) {
spdlog::error("wl_seat not advertised"); spdlog::error("wl_seat not advertised");
return;
} }
if (config_["hidden-modes"].isArray()) { if (config_["hidden-modes"].isArray()) {

View File

@ -141,10 +141,12 @@ Tags::Tags(const std::string& id, const waybar::Bar& bar, const Json::Value& con
if (!control_) { if (!control_) {
spdlog::error("river_control_v1 not advertised"); spdlog::error("river_control_v1 not advertised");
return;
} }
if (!seat_) { if (!seat_) {
spdlog::error("wl_seat not advertised"); spdlog::error("wl_seat not advertised");
return;
} }
seat_status_ = zriver_status_manager_v1_get_river_seat_status(status_manager_, seat_); seat_status_ = zriver_status_manager_v1_get_river_seat_status(status_manager_, seat_);
@ -217,6 +219,7 @@ Tags::~Tags() {
} }
void Tags::handle_show() { void Tags::handle_show() {
if (!status_manager_) return;
struct wl_output* output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()); 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); 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_output_status_v1_add_listener(output_status_, &output_status_listener_impl, this);

View File

@ -82,6 +82,7 @@ Window::Window(const std::string& id, const waybar::Bar& bar, const Json::Value&
if (!seat_) { if (!seat_) {
spdlog::error("wl_seat not advertised"); spdlog::error("wl_seat not advertised");
return;
} }
label_.hide(); // hide the label until populated label_.hide(); // hide the label until populated

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

@ -102,7 +102,9 @@ Sndio::~Sndio() { sioctl_close(hdl_); }
auto Sndio::update() -> void { auto Sndio::update() -> void {
auto format = format_; auto format = format_;
unsigned int vol = 100. * static_cast<double>(volume_) / static_cast<double>(maxval_); unsigned int vol = (maxval_ > 0) ? static_cast<unsigned int>(100. * static_cast<double>(volume_) /
static_cast<double>(maxval_))
: 0;
if (volume_ == 0) { if (volume_ == 0) {
label_.get_style_context()->add_class("muted"); label_.get_style_context()->add_class("muted");

View File

@ -59,7 +59,7 @@ void Host::nameVanished(const Glib::RefPtr<Gio::DBus::Connection>& conn, const G
void Host::proxyReady(GObject* src, GAsyncResult* res, gpointer data) { void Host::proxyReady(GObject* src, GAsyncResult* res, gpointer data) {
GError* error = nullptr; GError* error = nullptr;
waybar::util::ScopeGuard error_deleter([error]() { waybar::util::ScopeGuard error_deleter([&error]() {
if (error != nullptr) { if (error != nullptr) {
g_error_free(error); g_error_free(error);
} }
@ -81,7 +81,7 @@ void Host::proxyReady(GObject* src, GAsyncResult* res, gpointer data) {
void Host::registerHost(GObject* src, GAsyncResult* res, gpointer data) { void Host::registerHost(GObject* src, GAsyncResult* res, gpointer data) {
GError* error = nullptr; GError* error = nullptr;
waybar::util::ScopeGuard error_deleter([error]() { waybar::util::ScopeGuard error_deleter([&error]() {
if (error != nullptr) { if (error != nullptr) {
g_error_free(error); g_error_free(error);
} }

View File

@ -365,13 +365,14 @@ Glib::RefPtr<Gdk::Pixbuf> Item::extractPixBuf(GVariant* variant) {
void Item::updateImage() { void Item::updateImage() {
auto pixbuf = getIconPixbuf(); auto pixbuf = getIconPixbuf();
if (!pixbuf) return;
auto scaled_icon_size = getScaledIconSize(); auto scaled_icon_size = getScaledIconSize();
// If the loaded icon is not square, assume that the icon height should match the // If the loaded icon is not square, assume that the icon height should match the
// requested icon size, but the width is allowed to be different. As such, if the // requested icon size, but the width is allowed to be different. As such, if the
// height of the image does not match the requested icon size, resize the icon such that // height of the image does not match the requested icon size, resize the icon such that
// the aspect ratio is maintained, but the height matches the requested icon size. // the aspect ratio is maintained, but the height matches the requested icon size.
if (pixbuf->get_height() != scaled_icon_size) { if (pixbuf->get_height() > 0 && pixbuf->get_height() != scaled_icon_size) {
int width = scaled_icon_size * pixbuf->get_width() / pixbuf->get_height(); int width = scaled_icon_size * pixbuf->get_width() / pixbuf->get_height();
pixbuf = pixbuf->scale_simple(width, scaled_icon_size, Gdk::InterpType::INTERP_BILINEAR); pixbuf = pixbuf->scale_simple(width, scaled_icon_size, Gdk::InterpType::INTERP_BILINEAR);
} }

View File

@ -31,7 +31,7 @@ Watcher::~Watcher() {
void Watcher::busAcquired(const Glib::RefPtr<Gio::DBus::Connection>& conn, Glib::ustring name) { void Watcher::busAcquired(const Glib::RefPtr<Gio::DBus::Connection>& conn, Glib::ustring name) {
GError* error = nullptr; GError* error = nullptr;
waybar::util::ScopeGuard error_deleter([error]() { waybar::util::ScopeGuard error_deleter([&error]() {
if (error) { if (error) {
g_error_free(error); g_error_free(error);
} }

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

@ -77,9 +77,9 @@ void SystemdFailedUnits::RequestSystemState() {
Glib::VariantContainerBase data = proxy->call_sync("Get", parameters); Glib::VariantContainerBase data = proxy->call_sync("Get", parameters);
if (data && data.is_of_type(Glib::VariantType("(v)"))) { if (data && data.is_of_type(Glib::VariantType("(v)"))) {
Glib::VariantBase variant; Glib::VariantBase variant;
g_variant_get(data.gobj_copy(), "(v)", &variant); g_variant_get(const_cast<GVariant*>(data.gobj()), "(v)", &variant);
if (variant && variant.is_of_type(Glib::VARIANT_TYPE_STRING)) { if (variant && variant.is_of_type(Glib::VARIANT_TYPE_STRING)) {
return g_variant_get_string(variant.gobj_copy(), NULL); return g_variant_get_string(const_cast<GVariant*>(variant.gobj()), NULL);
} }
} }
} catch (Glib::Error& e) { } catch (Glib::Error& e) {
@ -105,9 +105,9 @@ void SystemdFailedUnits::RequestFailedUnits() {
Glib::VariantContainerBase data = proxy->call_sync("Get", parameters); Glib::VariantContainerBase data = proxy->call_sync("Get", parameters);
if (data && data.is_of_type(Glib::VariantType("(v)"))) { if (data && data.is_of_type(Glib::VariantType("(v)"))) {
Glib::VariantBase variant; Glib::VariantBase variant;
g_variant_get(data.gobj_copy(), "(v)", &variant); g_variant_get(const_cast<GVariant*>(data.gobj()), "(v)", &variant);
if (variant && variant.is_of_type(Glib::VARIANT_TYPE_UINT32)) { if (variant && variant.is_of_type(Glib::VARIANT_TYPE_UINT32)) {
return g_variant_get_uint32(variant.gobj_copy()); return g_variant_get_uint32(const_cast<GVariant*>(variant.gobj()));
} }
} }
} catch (Glib::Error& e) { } catch (Glib::Error& e) {

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

@ -59,10 +59,13 @@ UPower::UPower(const std::string& id, const Json::Value& config)
sigc::mem_fun(*this, &UPower::getConn_cb)); sigc::mem_fun(*this, &UPower::getConn_cb));
// Make UPower client // Make UPower client
GError** gErr = NULL; GError* gErr = NULL;
upClient_ = up_client_new_full(NULL, gErr); upClient_ = up_client_new_full(NULL, &gErr);
if (upClient_ == NULL) if (upClient_ == NULL) {
spdlog::error("Upower. UPower client connection error. {}", (*gErr)->message); spdlog::error("Upower. UPower client connection error. {}",
gErr ? gErr->message : "unknown error");
if (gErr) g_error_free(gErr);
}
// Subscribe UPower events // Subscribe UPower events
g_signal_connect(upClient_, "device-added", G_CALLBACK(deviceAdded_cb), this); g_signal_connect(upClient_, "device-added", G_CALLBACK(deviceAdded_cb), this);

View File

@ -9,6 +9,7 @@
#include <bit> #include <bit>
#include <cstdint> #include <cstdint>
#include <cstdlib> #include <cstdlib>
#include <cstring>
#include <exception> #include <exception>
#include <ranges> #include <ranges>
#include <thread> #include <thread>
@ -32,7 +33,13 @@ auto pack_and_write(Sock& sock, std::string&& buf) -> void {
auto read_exact(Sock& sock, size_t n) -> std::string { auto read_exact(Sock& sock, size_t n) -> std::string {
auto buf = std::string(n, 0); auto buf = std::string(n, 0);
for (size_t i = 0; i < n;) i += read(sock.fd, &buf[i], n - i); for (size_t i = 0; i < n;) {
auto r = read(sock.fd, &buf[i], n - i);
if (r <= 0) {
throw std::runtime_error("Wayfire IPC: read failed");
}
i += static_cast<size_t>(r);
}
return buf; return buf;
} }
@ -53,6 +60,9 @@ auto State::Wset::locate_ws(const Json::Value& geo) -> Workspace& {
} }
auto State::Wset::locate_ws(const Json::Value& geo) const -> const Workspace& { auto State::Wset::locate_ws(const Json::Value& geo) const -> const Workspace& {
if (!output.has_value()) {
throw std::runtime_error("Wayfire IPC: wset has no output assigned");
}
const auto& out = output.value().get(); const auto& out = output.value().get();
auto [qx, rx] = std::div(geo["x"].asInt(), out.w); auto [qx, rx] = std::div(geo["x"].asInt(), out.w);
auto [qy, ry] = std::div(geo["y"].asInt(), out.h); auto [qy, ry] = std::div(geo["y"].asInt(), out.h);
@ -88,7 +98,10 @@ auto State::update_view(const Json::Value& view) -> void {
auto IPC::get_instance() -> std::shared_ptr<IPC> { auto IPC::get_instance() -> std::shared_ptr<IPC> {
auto p = instance.lock(); auto p = instance.lock();
if (!p) instance = p = std::shared_ptr<IPC>(new IPC); if (!p) {
instance = p = std::shared_ptr<IPC>(new IPC);
p->start();
}
return p; return p;
} }
@ -116,7 +129,9 @@ auto IPC::connect() -> Sock {
} }
auto IPC::receive(Sock& sock) -> Json::Value { auto IPC::receive(Sock& sock) -> Json::Value {
auto len = *reinterpret_cast<uint32_t*>(read_exact(sock, 4).data()); auto len_buf = read_exact(sock, 4);
uint32_t len;
std::memcpy(&len, len_buf.data(), sizeof(len));
if constexpr (std::endian::native != std::endian::little) len = byteswap(len); if constexpr (std::endian::native != std::endian::little) len = byteswap(len);
auto buf = read_exact(sock, len); auto buf = read_exact(sock, len);
@ -153,15 +168,15 @@ auto IPC::start() -> void {
send("window-rules/get-focused-view", {}); send("window-rules/get-focused-view", {});
send("window-rules/get-focused-output", {}); send("window-rules/get-focused-output", {});
std::thread([&] { std::thread([self = shared_from_this()] {
auto sock = connect(); auto sock = connect();
{ {
Json::Value json; Json::Value json;
json["method"] = "window-rules/events/watch"; json["method"] = "window-rules/events/watch";
pack_and_write(sock, Json::writeString(writer_builder, json)); pack_and_write(sock, Json::writeString(self->writer_builder, json));
if (receive(sock)["result"] != "ok") { if (self->receive(sock)["result"] != "ok") {
spdlog::error( spdlog::error(
"Wayfire IPC: method \"window-rules/events/watch\"" "Wayfire IPC: method \"window-rules/events/watch\""
" have failed"); " have failed");
@ -169,10 +184,10 @@ auto IPC::start() -> void {
} }
} }
while (auto json = receive(sock)) { while (auto json = self->receive(sock)) {
auto ev = json["event"].asString(); auto ev = json["event"].asString();
spdlog::debug("Wayfire IPC: received event \"{}\"", ev); spdlog::debug("Wayfire IPC: received event \"{}\"", ev);
root_event_handler(ev, json); self->root_event_handler(ev, json);
} }
}).detach(); }).detach();
} }

View File

@ -34,8 +34,12 @@ auto Window::update() -> void {
auto Window::update_icon_label() -> void { auto Window::update_icon_label() -> void {
auto _ = ipc->lock_state(); auto _ = ipc->lock_state();
const auto& output = ipc->get_outputs().at(bar_.output->name); auto out_it = ipc->get_outputs().find(bar_.output->name);
const auto& wset = ipc->get_wsets().at(output.wset_idx); if (out_it == ipc->get_outputs().end()) return;
const auto& output = out_it->second;
auto wset_it = ipc->get_wsets().find(output.wset_idx);
if (wset_it == ipc->get_wsets().end()) return;
const auto& wset = wset_it->second;
const auto& views = ipc->get_views(); const auto& views = ipc->get_views();
auto ctx = bar_.window.get_style_context(); auto ctx = bar_.window.get_style_context();

View File

@ -70,8 +70,12 @@ auto Workspaces::handleScroll(GdkEventScroll* e) -> bool {
Json::Value data; Json::Value data;
{ {
auto _ = ipc->lock_state(); auto _ = ipc->lock_state();
const auto& output = ipc->get_outputs().at(bar_.output->name); auto out_it = ipc->get_outputs().find(bar_.output->name);
const auto& wset = ipc->get_wsets().at(output.wset_idx); if (out_it == ipc->get_outputs().end()) return true;
const auto& output = out_it->second;
auto wset_it = ipc->get_wsets().find(output.wset_idx);
if (wset_it == ipc->get_wsets().end()) return true;
const auto& wset = wset_it->second;
auto n = wset.ws_w * wset.ws_h; auto n = wset.ws_w * wset.ws_h;
auto i = (wset.ws_idx() + delta + n) % n; auto i = (wset.ws_idx() + delta + n) % n;
data["x"] = Json::Value((uint64_t)i % wset.ws_w); data["x"] = Json::Value((uint64_t)i % wset.ws_w);
@ -92,8 +96,12 @@ auto Workspaces::update_box() -> void {
auto _ = ipc->lock_state(); auto _ = ipc->lock_state();
const auto& output_name = bar_.output->name; const auto& output_name = bar_.output->name;
const auto& output = ipc->get_outputs().at(output_name); auto out_it = ipc->get_outputs().find(output_name);
const auto& wset = ipc->get_wsets().at(output.wset_idx); if (out_it == ipc->get_outputs().end()) return;
const auto& output = out_it->second;
auto wset_it = ipc->get_wsets().find(output.wset_idx);
if (wset_it == ipc->get_wsets().end()) return;
const auto& wset = wset_it->second;
auto output_focused = ipc->get_focused_output_name() == output_name; auto output_focused = ipc->get_focused_output_name() == output_name;
auto ws_w = wset.ws_w; auto ws_w = wset.ws_w;

View File

@ -81,7 +81,7 @@ void waybar::modules::Wireplumber::updateNodeName(waybar::modules::Wireplumber*
if (proxy == nullptr) { if (proxy == nullptr) {
auto err = fmt::format("Object '{}' not found\n", id); auto err = fmt::format("Object '{}' not found\n", id);
spdlog::error("[{}]: {}", self->name_, err); spdlog::error("[{}]: {}", self->name_, err);
throw std::runtime_error(err); return;
} }
g_autoptr(WpProperties) properties = g_autoptr(WpProperties) properties =
@ -153,7 +153,7 @@ void waybar::modules::Wireplumber::updateVolume(waybar::modules::Wireplumber* se
if (variant == nullptr) { if (variant == nullptr) {
auto err = fmt::format("Node {} does not support volume\n", id); auto err = fmt::format("Node {} does not support volume\n", id);
spdlog::error("[{}]: {}", self->name_, err); spdlog::error("[{}]: {}", self->name_, err);
throw std::runtime_error(err); return;
} }
g_variant_lookup(variant, "volume", "d", &self->volume_); g_variant_lookup(variant, "volume", "d", &self->volume_);
@ -287,14 +287,14 @@ void waybar::modules::Wireplumber::onObjectManagerInstalled(waybar::modules::Wir
if (self->def_nodes_api_ == nullptr) { if (self->def_nodes_api_ == nullptr) {
spdlog::error("[{}]: default nodes api is not loaded.", self->name_); spdlog::error("[{}]: default nodes api is not loaded.", self->name_);
throw std::runtime_error("Default nodes API is not loaded\n"); return;
} }
self->mixer_api_ = wp_plugin_find(self->wp_core_, "mixer-api"); self->mixer_api_ = wp_plugin_find(self->wp_core_, "mixer-api");
if (self->mixer_api_ == nullptr) { if (self->mixer_api_ == nullptr) {
spdlog::error("[{}]: mixer api is not loaded.", self->name_); spdlog::error("[{}]: mixer api is not loaded.", self->name_);
throw std::runtime_error("Mixer api is not loaded\n"); return;
} }
// Get default sink // Get default sink
@ -336,7 +336,7 @@ void waybar::modules::Wireplumber::onPluginActivated(WpObject* p, GAsyncResult*
if (wp_object_activate_finish(p, res, &error) == 0) { if (wp_object_activate_finish(p, res, &error) == 0) {
spdlog::error("[{}]: error activating plugin: {}", self->name_, error->message); spdlog::error("[{}]: error activating plugin: {}", self->name_, error->message);
throw std::runtime_error(error->message); return;
} }
if (--self->pending_plugins_ == 0) { if (--self->pending_plugins_ == 0) {
@ -373,7 +373,7 @@ void waybar::modules::Wireplumber::onDefaultNodesApiLoaded(WpObject* p, GAsyncRe
if (success == FALSE) { if (success == FALSE) {
spdlog::error("[{}]: default nodes API load failed", self->name_); spdlog::error("[{}]: default nodes API load failed", self->name_);
throw std::runtime_error(error->message); return;
} }
spdlog::debug("[{}]: loaded default nodes api", self->name_); spdlog::debug("[{}]: loaded default nodes api", self->name_);
g_ptr_array_add(self->apis_, wp_plugin_find(self->wp_core_, "default-nodes-api")); g_ptr_array_add(self->apis_, wp_plugin_find(self->wp_core_, "default-nodes-api"));
@ -392,7 +392,7 @@ void waybar::modules::Wireplumber::onMixerApiLoaded(WpObject* p, GAsyncResult* r
if (success == FALSE) { if (success == FALSE) {
spdlog::error("[{}]: mixer API load failed", self->name_); spdlog::error("[{}]: mixer API load failed", self->name_);
throw std::runtime_error(error->message); return;
} }
spdlog::debug("[{}]: loaded mixer API", self->name_); spdlog::debug("[{}]: loaded mixer API", self->name_);
@ -479,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_);
} }
} }
@ -524,6 +524,7 @@ bool waybar::modules::Wireplumber::handleScroll(GdkEventScroll* e) {
} }
} }
if (newVol != volume_) { if (newVol != volume_) {
if (mixer_api_ == nullptr) return true;
GVariant* variant = g_variant_new_double(newVol); GVariant* variant = g_variant_new_double(newVol);
gboolean ret; gboolean ret;
g_signal_emit_by_name(mixer_api_, "set-volume", node_id_, variant, &ret); g_signal_emit_by_name(mixer_api_, "set-volume", node_id_, variant, &ret);

View File

@ -437,7 +437,9 @@ void Task::handle_drag_data_get(const Glib::RefPtr<Gdk::DragContext>& context,
void Task::handle_drag_data_received(const Glib::RefPtr<Gdk::DragContext>& context, int x, int y, void Task::handle_drag_data_received(const Glib::RefPtr<Gdk::DragContext>& context, int x, int y,
Gtk::SelectionData selection_data, guint info, guint time) { Gtk::SelectionData selection_data, guint info, guint time) {
spdlog::debug("drag_data_received"); spdlog::debug("drag_data_received");
gpointer handle = *(gpointer*)selection_data.get_data(); auto* raw = selection_data.get_data();
if (!raw || selection_data.get_length() < static_cast<int>(sizeof(gpointer))) return;
gpointer handle = *(gpointer*)raw;
auto dragged_button = (Gtk::Button*)handle; auto dragged_button = (Gtk::Button*)handle;
if (dragged_button == &this->button) return; if (dragged_button == &this->button) return;
@ -509,7 +511,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

@ -237,9 +237,10 @@ void AudioBackend::sourceInfoCb(pa_context* /*context*/, const pa_source_info* i
*/ */
void AudioBackend::serverInfoCb(pa_context* context, const pa_server_info* i, void* data) { void AudioBackend::serverInfoCb(pa_context* context, const pa_server_info* i, void* data) {
auto* backend = static_cast<AudioBackend*>(data); auto* backend = static_cast<AudioBackend*>(data);
backend->current_sink_name_ = i->default_sink_name; if (i == nullptr) return;
backend->default_sink_name = i->default_sink_name; backend->current_sink_name_ = i->default_sink_name ? i->default_sink_name : "";
backend->default_source_name_ = i->default_source_name; backend->default_sink_name = i->default_sink_name ? i->default_sink_name : "";
backend->default_source_name_ = i->default_source_name ? i->default_source_name : "";
pa_context_get_sink_info_list(context, sinkInfoCb, data); pa_context_get_sink_info_list(context, sinkInfoCb, data);
pa_context_get_source_info_list(context, sourceInfoCb, data); pa_context_get_source_info_list(context, sourceInfoCb, data);
@ -355,6 +356,7 @@ void AudioBackend::changeVolume(ChangeType change_type, double step, uint16_t ma
} }
void AudioBackend::toggleSinkMute() { void AudioBackend::toggleSinkMute() {
if (context_ == nullptr || pa_context_get_state(context_) != PA_CONTEXT_READY) return;
muted_ = !muted_; muted_ = !muted_;
pa_threaded_mainloop_lock(mainloop_); pa_threaded_mainloop_lock(mainloop_);
pa_context_set_sink_mute_by_index(context_, sink_idx_, static_cast<int>(muted_), nullptr, pa_context_set_sink_mute_by_index(context_, sink_idx_, static_cast<int>(muted_), nullptr,
@ -363,6 +365,7 @@ void AudioBackend::toggleSinkMute() {
} }
void AudioBackend::toggleSinkMute(bool mute) { void AudioBackend::toggleSinkMute(bool mute) {
if (context_ == nullptr || pa_context_get_state(context_) != PA_CONTEXT_READY) return;
muted_ = mute; muted_ = mute;
pa_threaded_mainloop_lock(mainloop_); pa_threaded_mainloop_lock(mainloop_);
pa_context_set_sink_mute_by_index(context_, sink_idx_, static_cast<int>(muted_), nullptr, pa_context_set_sink_mute_by_index(context_, sink_idx_, static_cast<int>(muted_), nullptr,
@ -371,7 +374,8 @@ void AudioBackend::toggleSinkMute(bool mute) {
} }
void AudioBackend::toggleSourceMute() { void AudioBackend::toggleSourceMute() {
source_muted_ = !muted_; if (context_ == nullptr || pa_context_get_state(context_) != PA_CONTEXT_READY) return;
source_muted_ = !source_muted_;
pa_threaded_mainloop_lock(mainloop_); pa_threaded_mainloop_lock(mainloop_);
pa_context_set_source_mute_by_index(context_, source_idx_, static_cast<int>(source_muted_), pa_context_set_source_mute_by_index(context_, source_idx_, static_cast<int>(source_muted_),
nullptr, nullptr); nullptr, nullptr);
@ -379,6 +383,7 @@ void AudioBackend::toggleSourceMute() {
} }
void AudioBackend::toggleSourceMute(bool mute) { void AudioBackend::toggleSourceMute(bool mute) {
if (context_ == nullptr || pa_context_get_state(context_) != PA_CONTEXT_READY) return;
source_muted_ = mute; source_muted_ = mute;
pa_threaded_mainloop_lock(mainloop_); pa_threaded_mainloop_lock(mainloop_);
pa_context_set_source_mute_by_index(context_, source_idx_, static_cast<int>(source_muted_), pa_context_set_source_mute_by_index(context_, source_idx_, static_cast<int>(source_muted_),

View File

@ -79,18 +79,38 @@ static void upsert_device(std::vector<BacklightDevice>& devices, udev_device* de
}); });
if (found != devices.end()) { if (found != devices.end()) {
if (actual != nullptr) { if (actual != nullptr) {
found->set_actual(std::stoi(actual)); try {
found->set_actual(std::stoi(actual));
} catch (const std::exception&) {
}
} }
if (max != nullptr) { if (max != nullptr) {
found->set_max(std::stoi(max)); try {
found->set_max(std::stoi(max));
} catch (const std::exception&) {
}
} }
if (power != nullptr) { if (power != nullptr) {
found->set_powered(std::stoi(power) == 0); try {
found->set_powered(std::stoi(power) == 0);
} catch (const std::exception&) {
}
} }
} else { } else {
const int actual_int = actual == nullptr ? 0 : std::stoi(actual); int actual_int = 0, max_int = 0;
const int max_int = max == nullptr ? 0 : std::stoi(max); bool power_bool = true;
const bool power_bool = power == nullptr ? true : std::stoi(power) == 0; try {
if (actual != nullptr) actual_int = std::stoi(actual);
} catch (const std::exception&) {
}
try {
if (max != nullptr) max_int = std::stoi(max);
} catch (const std::exception&) {
}
try {
if (power != nullptr) power_bool = std::stoi(power) == 0;
} catch (const std::exception&) {
}
devices.emplace_back(name, actual_int, max_int, power_bool); devices.emplace_back(name, actual_int, max_int, power_bool);
} }
} }
@ -261,6 +281,11 @@ void BacklightBackend::set_brightness(const std::string& preferred_device, Chang
void BacklightBackend::set_brightness_internal(const std::string& device_name, int brightness, void BacklightBackend::set_brightness_internal(const std::string& device_name, int brightness,
int max_brightness) { int max_brightness) {
if (!login_proxy_) {
spdlog::error("Login proxy not available, cannot set brightness");
return;
}
brightness = std::clamp(brightness, 0, max_brightness); brightness = std::clamp(brightness, 0, max_brightness);
auto call_args = Glib::VariantContainerBase( auto call_args = Glib::VariantContainerBase(
@ -273,6 +298,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) {
if (best->get_max() == 0) return 0;
return static_cast<int>(std::round(best->get_actual() * 100.0F / best->get_max())); return static_cast<int>(std::round(best->get_actual() * 100.0F / best->get_max()));
} }

View File

@ -115,11 +115,15 @@ std::vector<std::string> waybar::CssReloadHelper::parseImports(const std::string
auto maxIterations = 100U; auto maxIterations = 100U;
do { do {
previousSize = imports.size(); previousSize = imports.size();
std::vector<std::string> to_parse;
for (const auto& [file, parsed] : imports) { for (const auto& [file, parsed] : imports) {
if (!parsed) { if (!parsed) {
parseImports(file, imports); to_parse.push_back(file);
} }
} }
for (const auto& file : to_parse) {
parseImports(file, imports);
}
} while (imports.size() > previousSize && maxIterations-- > 0); } while (imports.size() > previousSize && maxIterations-- > 0);

View File

@ -5,8 +5,11 @@
std::vector<std::string> IconLoader::search_prefix() { std::vector<std::string> IconLoader::search_prefix() {
std::vector<std::string> prefixes = {""}; std::vector<std::string> prefixes = {""};
std::string home_dir = std::getenv("HOME"); const char* home_env = std::getenv("HOME");
prefixes.push_back(home_dir + "/.local/share/"); std::string home_dir = home_env ? home_env : "";
if (!home_dir.empty()) {
prefixes.push_back(home_dir + "/.local/share/");
}
auto xdg_data_dirs = std::getenv("XDG_DATA_DIRS"); auto xdg_data_dirs = std::getenv("XDG_DATA_DIRS");
if (!xdg_data_dirs) { if (!xdg_data_dirs) {
@ -139,7 +142,7 @@ bool IconLoader::image_load_icon(Gtk::Image& image, const Glib::RefPtr<Gtk::Icon
} }
if (pixbuf) { if (pixbuf) {
if (pixbuf->get_width() != scaled_icon_size) { if (pixbuf->get_width() != scaled_icon_size && pixbuf->get_height() > 0) {
int width = scaled_icon_size * pixbuf->get_width() / pixbuf->get_height(); int width = scaled_icon_size * pixbuf->get_width() / pixbuf->get_height();
pixbuf = pixbuf->scale_simple(width, scaled_icon_size, Gdk::InterpType::INTERP_BILINEAR); pixbuf = pixbuf->scale_simple(width, scaled_icon_size, Gdk::InterpType::INTERP_BILINEAR);
} }

View File

@ -54,11 +54,17 @@ PipewireBackend::PipewireBackend(PrivateConstructorTag tag)
context_ = pw_context_new(pw_thread_loop_get_loop(mainloop_), nullptr, 0); context_ = pw_context_new(pw_thread_loop_get_loop(mainloop_), nullptr, 0);
if (context_ == nullptr) { if (context_ == nullptr) {
pw_thread_loop_unlock(mainloop_); pw_thread_loop_unlock(mainloop_);
pw_thread_loop_destroy(mainloop_);
mainloop_ = nullptr;
throw std::runtime_error("pa_context_new() failed."); throw std::runtime_error("pa_context_new() failed.");
} }
core_ = pw_context_connect(context_, nullptr, 0); core_ = pw_context_connect(context_, nullptr, 0);
if (core_ == nullptr) { if (core_ == nullptr) {
pw_thread_loop_unlock(mainloop_); pw_thread_loop_unlock(mainloop_);
pw_context_destroy(context_);
context_ = nullptr;
pw_thread_loop_destroy(mainloop_);
mainloop_ = nullptr;
throw std::runtime_error("pw_context_connect() failed"); throw std::runtime_error("pw_context_connect() failed");
} }
registry_ = pw_core_get_registry(core_, PW_VERSION_REGISTRY, 0); registry_ = pw_core_get_registry(core_, PW_VERSION_REGISTRY, 0);
@ -136,7 +142,10 @@ void PipewireBackend::handleRegistryEventGlobal(uint32_t id, uint32_t permission
pw_proxy_add_object_listener(proxy, &pNodeInfo->object_listener, &NODE_EVENTS, pNodeInfo); pw_proxy_add_object_listener(proxy, &pNodeInfo->object_listener, &NODE_EVENTS, pNodeInfo);
privacy_nodes.insert_or_assign(id, pNodeInfo); {
std::lock_guard<std::mutex> lock(mutex_);
privacy_nodes.insert_or_assign(id, pNodeInfo);
}
} }
void PipewireBackend::handleRegistryEventGlobalRemove(uint32_t id) { void PipewireBackend::handleRegistryEventGlobalRemove(uint32_t id) {

View File

@ -58,21 +58,27 @@ void waybar::Portal::refreshAppearance() {
// xdg-desktop-portal 1.17 will fix this issue with a new `ReadOne` method, // xdg-desktop-portal 1.17 will fix this issue with a new `ReadOne` method,
// but this version is not yet released. // but this version is not yet released.
// TODO(xdg-desktop-portal v1.17): switch to ReadOne // TODO(xdg-desktop-portal v1.17): switch to ReadOne
auto container = Glib::VariantBase::cast_dynamic<Glib::VariantContainerBase>(response); try {
Glib::VariantBase modev; auto container = Glib::VariantBase::cast_dynamic<Glib::VariantContainerBase>(response);
container.get_child(modev, 0); Glib::VariantBase modev;
auto mode = container.get_child(modev, 0);
Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::Variant<Glib::Variant<uint32_t>>>>(modev) auto mode =
.get() Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::Variant<Glib::Variant<uint32_t>>>>(
.get() modev)
.get(); .get()
auto newMode = Appearance(mode); .get()
if (newMode == currentMode) { .get();
auto newMode = Appearance(mode);
if (newMode == currentMode) {
return;
}
spdlog::info("Discovered appearance '{}'", newMode);
currentMode = newMode;
m_signal_appearance_changed.emit(currentMode);
} catch (const std::bad_cast& e) {
spdlog::error("Unexpected appearance variant format: {}", e.what());
return; return;
} }
spdlog::info("Discovered appearance '{}'", newMode);
currentMode = newMode;
m_signal_appearance_changed.emit(currentMode);
} }
waybar::Appearance waybar::Portal::getAppearance() { return currentMode; }; waybar::Appearance waybar::Portal::getAppearance() { return currentMode; };