diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml index 29b3e23b..25f62c8e 100644 --- a/.github/workflows/clang-format.yml +++ b/.github/workflows/clang-format.yml @@ -10,7 +10,7 @@ jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 - uses: RafikFarhad/clang-format-github-action@v6 name: clang-format with: diff --git a/.github/workflows/clang-tidy.yml.bak b/.github/workflows/clang-tidy.yml.bak index ec67fb7e..9e9da80c 100644 --- a/.github/workflows/clang-tidy.yml.bak +++ b/.github/workflows/clang-tidy.yml.bak @@ -12,7 +12,7 @@ jobs: container: image: alexays/waybar:debian steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 - name: configure run: | meson -Dcpp_std=c++20 build # necessary to generate compile_commands.json diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 0e7e2944..ea8eb08b 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -17,7 +17,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Login to Docker Hub uses: docker/login-action@v3 diff --git a/.github/workflows/freebsd.yml b/.github/workflows/freebsd.yml index b9114c31..f6c96f95 100644 --- a/.github/workflows/freebsd.yml +++ b/.github/workflows/freebsd.yml @@ -12,13 +12,13 @@ jobs: # https://github.com/actions/runner/issues/385 - for FreeBSD runner support runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 - name: Test in FreeBSD VM uses: cross-platform-actions/action@v0.28.0 timeout-minutes: 180 env: CPPFLAGS: '-isystem/usr/local/include' - LDFLAGS: '-L/usr/local/lib' + LDFLAGS: '-L/usr/local/lib' with: operating_system: freebsd version: "14.2" diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index c36f68e2..12a995d7 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -25,7 +25,7 @@ jobs: image: alexays/waybar:${{ matrix.distro }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 - name: configure run: meson setup -Dman-pages=enabled -Dcpp_std=${{matrix.cpp_std}} build - name: build diff --git a/.github/workflows/nix-tests.yml b/.github/workflows/nix-tests.yml index 8859ecb5..c2193b99 100644 --- a/.github/workflows/nix-tests.yml +++ b/.github/workflows/nix-tests.yml @@ -2,16 +2,19 @@ name: "Nix-Tests" on: pull_request: push: +concurrency: + group: ${{ github.workflow }}-nix-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true jobs: nix-flake-check: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: cachix/install-nix-action@v27 - with: - extra_nix_config: | - experimental-features = nix-command flakes - access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} - - run: nix flake show - - run: nix flake check --print-build-logs - - run: nix build --print-build-logs + - uses: actions/checkout@v6 + - uses: cachix/install-nix-action@v31 + with: + extra_nix_config: | + experimental-features = nix-command flakes + access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} + - run: nix flake show + - run: nix flake check --print-build-logs + - run: nix build --print-build-logs diff --git a/.github/workflows/nix-update-flake-lock.yml b/.github/workflows/nix-update-flake-lock.yml index a1679ead..de8a0ea1 100644 --- a/.github/workflows/nix-update-flake-lock.yml +++ b/.github/workflows/nix-update-flake-lock.yml @@ -4,19 +4,19 @@ on: schedule: - cron: '0 0 1 * *' # Run monthly push: - paths: - - 'flake.nix' + paths: + - 'flake.nix' jobs: lockfile: runs-on: ubuntu-latest if: github.event_name != 'schedule' || github.repository == 'Alexays/Waybar' steps: - name: Checkout repository - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Install Nix - uses: cachix/install-nix-action@v27 + uses: cachix/install-nix-action@v31 with: extra_nix_config: | access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} - name: Update flake.lock - uses: DeterminateSystems/update-flake-lock@v21 + uses: DeterminateSystems/update-flake-lock@v28 diff --git a/include/AModule.hpp b/include/AModule.hpp index 6b29045c..a338ffe3 100644 --- a/include/AModule.hpp +++ b/include/AModule.hpp @@ -48,7 +48,7 @@ class AModule : public IModule { virtual bool handleMouseLeave(GdkEventCrossing* const& ev); virtual bool handleScroll(GdkEventScroll*); virtual bool handleRelease(GdkEventButton* const& ev); - GObject* menu_; + GObject* menu_ = nullptr; private: bool handleUserEvent(GdkEventButton* const& ev); diff --git a/include/group.hpp b/include/group.hpp index e6d09e73..dbc886a6 100644 --- a/include/group.hpp +++ b/include/group.hpp @@ -35,6 +35,7 @@ class Group : public AModule { bool handleMouseLeave(GdkEventCrossing* const& ev) override; bool handleToggle(GdkEventButton* const& ev) override; void toggle(); + bool handleScroll(GdkEventScroll* e) override; void show_group(); void hide_group(); void set_visible(bool v) { diff --git a/include/modules/battery.hpp b/include/modules/battery.hpp index ffc82aba..09c172b0 100644 --- a/include/modules/battery.hpp +++ b/include/modules/battery.hpp @@ -6,7 +6,7 @@ #if defined(__linux__) #include #endif -#include +#include #include #include diff --git a/include/modules/hyprland/workspaces.hpp b/include/modules/hyprland/workspaces.hpp index a5d94bbf..8bf88888 100644 --- a/include/modules/hyprland/workspaces.hpp +++ b/include/modules/hyprland/workspaces.hpp @@ -43,6 +43,7 @@ class Workspaces : public AModule, public EventHandler { auto moveToMonitor() const -> bool { return m_moveToMonitor; } auto enableTaskbar() const -> bool { return m_enableTaskbar; } auto taskbarWithIcon() const -> bool { return m_taskbarWithIcon; } + auto barScroll() const -> bool { return m_barScroll; } auto getBarOutput() const -> std::string { return m_bar.output->name; } auto formatBefore() const -> std::string { return m_formatBefore; } @@ -122,6 +123,8 @@ class Workspaces : public AModule, public EventHandler { static std::pair splitDoublePayload(std::string const& payload); static std::tuple splitTriplePayload( std::string const& payload); + // scroll events + bool handleScroll(GdkEventScroll* e) override; // Update methods void doUpdate(); @@ -145,6 +148,7 @@ class Workspaces : public AModule, public EventHandler { bool m_specialVisibleOnly = false; bool m_persistentOnly = false; bool m_moveToMonitor = false; + bool m_barScroll = false; Json::Value m_persistentWorkspaceConfig; // Map for windows stored in workspaces not present in the current bar. diff --git a/include/modules/keyboard_state.hpp b/include/modules/keyboard_state.hpp index be90eee4..db7f2550 100644 --- a/include/modules/keyboard_state.hpp +++ b/include/modules/keyboard_state.hpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -41,6 +42,7 @@ class KeyboardState : public AModule { struct libinput* libinput_; std::unordered_map libinput_devices_; + std::mutex devices_mutex_; // protects libinput_devices_ std::set binding_keys; util::SleeperThread libinput_thread_, hotplug_thread_; diff --git a/include/modules/mpris/mpris.hpp b/include/modules/mpris/mpris.hpp index ad4dac1e..a33db4d2 100644 --- a/include/modules/mpris/mpris.hpp +++ b/include/modules/mpris/mpris.hpp @@ -78,6 +78,7 @@ class Mpris : public ALabel { PlayerctlPlayerManager* manager; PlayerctlPlayer* player; + PlayerctlPlayer* last_active_player_ = nullptr; std::string lastStatus; std::string lastPlayer; diff --git a/include/modules/niri/backend.hpp b/include/modules/niri/backend.hpp index 42b9ff7f..07be039a 100644 --- a/include/modules/niri/backend.hpp +++ b/include/modules/niri/backend.hpp @@ -17,7 +17,7 @@ class EventHandler { class IPC { public: - IPC() { startIPC(); } + IPC(); void registerForIPC(const std::string& ev, EventHandler* ev_handler); void unregisterForIPC(EventHandler* handler); diff --git a/include/modules/sway/ipc/client.hpp b/include/modules/sway/ipc/client.hpp index 9033a688..281df7ab 100644 --- a/include/modules/sway/ipc/client.hpp +++ b/include/modules/sway/ipc/client.hpp @@ -12,6 +12,7 @@ #include #include "ipc.hpp" +#include "util/SafeSignal.hpp" #include "util/sleeper_thread.hpp" namespace waybar::modules::sway { @@ -27,8 +28,8 @@ class Ipc { std::string payload; }; - sigc::signal signal_event; - sigc::signal signal_cmd; + ::waybar::SafeSignal signal_event; + ::waybar::SafeSignal signal_cmd; void sendCmd(uint32_t type, const std::string& payload = ""); void subscribe(const std::string& payload); diff --git a/include/modules/wayfire/backend.hpp b/include/modules/wayfire/backend.hpp index 9d55c820..d3173269 100644 --- a/include/modules/wayfire/backend.hpp +++ b/include/modules/wayfire/backend.hpp @@ -89,7 +89,7 @@ struct Sock { } }; -class IPC { +class IPC : public std::enable_shared_from_this { static std::weak_ptr instance; Json::CharReaderBuilder reader_builder; Json::StreamWriterBuilder writer_builder; @@ -98,7 +98,7 @@ class IPC { State state; std::mutex state_mutex; - IPC() { start(); } + IPC() = default; static auto connect() -> Sock; auto receive(Sock& sock) -> Json::Value; diff --git a/include/util/command.hpp b/include/util/command.hpp index 58c59a96..b1adcd7c 100644 --- a/include/util/command.hpp +++ b/include/util/command.hpp @@ -59,6 +59,7 @@ inline int close(FILE* fp, pid_t pid) { spdlog::debug("Cmd continued"); } else if (ret == -1) { spdlog::debug("waitpid failed: {}", strerror(errno)); + break; } else { break; } @@ -172,8 +173,6 @@ inline int32_t forkExec(const std::string& cmd, const std::string& output_name) return pid; } -inline int32_t forkExec(const std::string& cmd) { - return forkExec(cmd, ""); -} +inline int32_t forkExec(const std::string& cmd) { return forkExec(cmd, ""); } } // namespace waybar::util::command diff --git a/man/waybar-battery.5.scd b/man/waybar-battery.5.scd index 6d98fd4e..42b9e046 100644 --- a/man/waybar-battery.5.scd +++ b/man/waybar-battery.5.scd @@ -182,6 +182,7 @@ Every entry in the *events* object consists of a ** (typeof: *string - *on--* - *on--* +- *on-* Where: @@ -203,7 +204,9 @@ Where: "events": { "on-discharging-warning": "notify-send -u normal 'Low Battery'", "on-discharging-critical": "notify-send -u critical 'Very Low Battery'", - "on-charging-100": "notify-send -u normal 'Battery Full!'" + "on-charging-100": "notify-send -u normal 'Battery Full!'", + "on-discharging": "notify-send -u normal 'Power Switch' Discharging", + "on-charging": "notify-send -u normal 'Power Switch' Charging'" }, "format": "{capacity}% {icon}", "format-icons": ["", "", "", "", ""], diff --git a/man/waybar-hyprland-workspaces.5.scd b/man/waybar-hyprland-workspaces.5.scd index 1d04157b..5284ce99 100644 --- a/man/waybar-hyprland-workspaces.5.scd +++ b/man/waybar-hyprland-workspaces.5.scd @@ -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. 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*: ++ typeof: array ++ default: [] ++ diff --git a/man/waybar-image.5.scd b/man/waybar-image.5.scd index 8c991265..8be182a6 100644 --- a/man/waybar-image.5.scd +++ b/man/waybar-image.5.scd @@ -26,7 +26,8 @@ The *image* module displays an image from a path. *interval*: ++ typeof: integer or float ++ The interval (in seconds) to re-render the image. ++ - Minimum value is 0.001 (1ms). Values smaller than 1ms will be set to 1ms. ++ + If set to a positive value, the minimum is 0.001 (1ms). Values smaller than 1ms will be set to 1ms. ++ + Zero or negative values are treated as "once". ++ This is useful if the contents of *path* changes. ++ If no *interval* is defined, the image will only be rendered once. diff --git a/man/waybar.5.scd.in b/man/waybar.5.scd.in index 2ee63bc4..ad973f4a 100644 --- a/man/waybar.5.scd.in +++ b/man/waybar.5.scd.in @@ -36,7 +36,7 @@ The visual display elements for waybar use a CSS stylesheet, see *waybar-styles( *expand-right* ++ typeof: bool ++ default: false ++ - Enables the modules-left to consume all left over space dynamically. + Enables the modules-right to consume all left over space dynamically. *layer* ++ typeof: string ++ diff --git a/src/AAppIconLabel.cpp b/src/AAppIconLabel.cpp index a309a6e0..b72906c3 100644 --- a/src/AAppIconLabel.cpp +++ b/src/AAppIconLabel.cpp @@ -151,19 +151,24 @@ void AAppIconLabel::updateAppIconName(const std::string& app_identifier, } void AAppIconLabel::updateAppIcon() { - if (update_app_icon_) { + if (update_app_icon_ || (!iconEnabled() && image_.get_visible())) { update_app_icon_ = false; if (app_icon_name_.empty()) { image_.set_visible(false); } else if (app_icon_name_.front() == '/') { - auto pixbuf = Gdk::Pixbuf::create_from_file(app_icon_name_); - 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); + try { + int scaled_icon_size = app_icon_size_ * image_.get_scale_factor(); + auto pixbuf = + Gdk::Pixbuf::create_from_file(app_icon_name_, scaled_icon_size, scaled_icon_size); - auto surface = Gdk::Cairo::create_surface_from_pixbuf(pixbuf, image_.get_scale_factor(), - image_.get_window()); - image_.set(surface); - image_.set_visible(true); + auto surface = Gdk::Cairo::create_surface_from_pixbuf(pixbuf, image_.get_scale_factor(), + image_.get_window()); + image_.set(surface); + image_.set_visible(true); + } catch (const Glib::Exception& e) { + spdlog::warn("Failed to load app icon {}: {}", app_icon_name_, std::string(e.what())); + image_.set_visible(false); + } } else { image_.set_from_icon_name(app_icon_name_, Gtk::ICON_SIZE_INVALID); image_.set_visible(true); diff --git a/src/ALabel.cpp b/src/ALabel.cpp index d251d896..0d92c372 100644 --- a/src/ALabel.cpp +++ b/src/ALabel.cpp @@ -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 if (gtk_builder_add_from_string(builder, fileContent.str().c_str(), -1, nullptr) == 0U) { + g_object_unref(builder); throw std::runtime_error("Error found in the file " + menuFile); } menu_ = gtk_builder_get_object(builder, "menu"); if (menu_ == nullptr) { + g_object_unref(builder); throw std::runtime_error("Failed to get 'menu' object from GtkBuilder"); } submenus_ = std::map(); @@ -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(); it != config_["menu-actions"].end(); ++it) { std::string key = it.key().asString(); - submenus_[key] = GTK_MENU_ITEM(gtk_builder_get_object(builder, key.c_str())); + auto* item = gtk_builder_get_object(builder, key.c_str()); + if (item == nullptr) { + spdlog::warn("Menu item '{}' not found in builder file", key); + continue; + } + submenus_[key] = GTK_MENU_ITEM(item); menuActionsMap_[key] = it->asString(); g_signal_connect(submenus_[key], "activate", G_CALLBACK(handleGtkMenuEvent), (gpointer)menuActionsMap_[key].c_str()); } + g_object_unref(builder); } catch (std::runtime_error& e) { spdlog::warn("Error while creating the menu : {}. Menu popup not activated.", e.what()); } @@ -141,7 +149,8 @@ std::string ALabel::getIcon(uint16_t percentage, const std::string& alt, uint16_ if (format_icons.isArray()) { auto size = format_icons.size(); if (size != 0U) { - auto idx = std::clamp(percentage / ((max == 0 ? 100 : max) / size), 0U, size - 1); + auto divisor = std::max(1U, (max == 0 ? 100U : static_cast(max)) / size); + auto idx = std::clamp(percentage / divisor, 0U, size - 1); format_icons = format_icons[idx]; } } @@ -167,7 +176,8 @@ std::string ALabel::getIcon(uint16_t percentage, const std::vector& if (format_icons.isArray()) { auto size = format_icons.size(); if (size != 0U) { - auto idx = std::clamp(percentage / ((max == 0 ? 100 : max) / size), 0U, size - 1); + auto divisor = std::max(1U, (max == 0 ? 100U : static_cast(max)) / size); + auto idx = std::clamp(percentage / divisor, 0U, size - 1); format_icons = format_icons[idx]; } } diff --git a/src/AModule.cpp b/src/AModule.cpp index c6fdff3e..5f3a187a 100644 --- a/src/AModule.cpp +++ b/src/AModule.cpp @@ -166,9 +166,9 @@ bool AModule::handleUserEvent(GdkEventButton* const& e) { } // Check that a menu has been configured - if (config_["menu"].isString()) { + if (rec != eventMap_.cend() && config_["menu"].isString()) { // Check if the event is the one specified for the "menu" option - if (rec->second == config_["menu"].asString()) { + if (rec->second == config_["menu"].asString() && menu_ != nullptr) { // Popup the menu gtk_widget_show_all(GTK_WIDGET(menu_)); gtk_menu_popup_at_pointer(GTK_MENU(menu_), reinterpret_cast(e)); diff --git a/src/group.cpp b/src/group.cpp index 5a81c254..c9162749 100644 --- a/src/group.cpp +++ b/src/group.cpp @@ -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; } void Group::addWidget(Gtk::Widget& widget) { diff --git a/src/main.cpp b/src/main.cpp index fb96418d..c3e1614a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -33,7 +33,10 @@ static void writeSignalToPipe(int signum) { // to `signal_handler`. static void catchSignals(waybar::SafeSignal& signal_handler) { int fd[2]; - pipe(fd); + if (pipe(fd) != 0) { + spdlog::error("Failed to create signal pipe: {}", strerror(errno)); + return; + } int signal_pipe_read_fd = fd[0]; signal_pipe_write_fd = fd[1]; @@ -138,15 +141,16 @@ static void handleSignalMainThread(int signum, bool& reload) { break; case SIGCHLD: spdlog::debug("Received SIGCHLD in signalThread"); - if (!reap.empty()) { - reap_mtx.lock(); - for (auto it = reap.begin(); it != reap.end(); ++it) { + { + std::lock_guard lock(reap_mtx); + for (auto it = reap.begin(); it != reap.end();) { if (waitpid(*it, nullptr, WNOHANG) == *it) { spdlog::debug("Reaped child with PID: {}", *it); it = reap.erase(it); + } else { + ++it; } } - reap_mtx.unlock(); } break; default: diff --git a/src/modules/backlight.cpp b/src/modules/backlight.cpp index aabfe3f1..8f0d583f 100644 --- a/src/modules/backlight.cpp +++ b/src/modules/backlight.cpp @@ -58,11 +58,11 @@ auto waybar::modules::Backlight::update() -> void { tooltip_format = config_["tooltip-format"].asString(); } if (!tooltip_format.empty()) { - label_.set_tooltip_text(fmt::format(fmt::runtime(tooltip_format), - fmt::arg("percent", percent), - fmt::arg("icon", getIcon(percent)))); + label_.set_tooltip_markup(fmt::format(fmt::runtime(tooltip_format), + fmt::arg("percent", percent), + fmt::arg("icon", getIcon(percent)))); } else { - label_.set_tooltip_text(desc); + label_.set_tooltip_markup(desc); } } } else { diff --git a/src/modules/battery.cpp b/src/modules/battery.cpp index c706eb3c..d39c4920 100644 --- a/src/modules/battery.cpp +++ b/src/modules/battery.cpp @@ -139,7 +139,9 @@ void waybar::modules::Battery::refreshBatteries() { auto event_path = (node.path() / "uevent"); auto wd = inotify_add_watch(battery_watch_fd_, event_path.c_str(), IN_ACCESS); if (wd < 0) { - throw std::runtime_error("Could not watch events for " + node.path().string()); + spdlog::warn("Could not watch events for {} (device may have been removed)", + node.path().string()); + continue; } batteries_[node.path()] = wd; } @@ -686,7 +688,7 @@ auto waybar::modules::Battery::update() -> void { status = getAdapterStatus(capacity); } auto status_pretty = status; - puts(status.c_str()); + // Transform to lowercase and replace space with dash std::ranges::transform(status.begin(), status.end(), status.begin(), [](char ch) { return ch == ' ' ? '-' : std::tolower(ch); }); @@ -790,16 +792,19 @@ void waybar::modules::Battery::processEvents(std::string& state, std::string& st if (!events.isObject() || events.empty()) { return; } - std::string event_name = fmt::format("on-{}-{}", status == "discharging" ? status : "charging", - state.empty() ? std::to_string(capacity) : state); + auto exec = [](Json::Value const& event) { + if (!event.isString()) return; + if (auto command = event.asString(); !command.empty()) { + util::command::exec(command, ""); + } + }; + std::string status_name = status == "discharging" ? "on-discharging" : "on-charging"; + std::string event_name = status_name + '-' + (state.empty() ? std::to_string(capacity) : state); if (last_event_ != event_name) { spdlog::debug("battery: triggering event {}", event_name); - if (events[event_name].isString()) { - std::string exec = events[event_name].asString(); - // Execute the command if it is not empty - if (!exec.empty()) { - util::command::exec(exec, ""); - } + exec(events[event_name]); + if (!last_event_.empty() && last_event_[3] != event_name[3]) { + exec(events[status_name]); } last_event_ = event_name; } diff --git a/src/modules/bluetooth.cpp b/src/modules/bluetooth.cpp index f491f19b..c59af3b5 100644 --- a/src/modules/bluetooth.cpp +++ b/src/modules/bluetooth.cpp @@ -111,13 +111,16 @@ waybar::modules::Bluetooth::Bluetooth(const std::string& id, const Json::Value& findConnectedDevices(cur_controller_->path, connected_devices_); } - g_signal_connect(manager_.get(), "object-added", G_CALLBACK(onObjectAdded), this); - g_signal_connect(manager_.get(), "object-removed", G_CALLBACK(onObjectRemoved), this); - g_signal_connect(manager_.get(), "interface-proxy-properties-changed", - G_CALLBACK(onInterfaceProxyPropertiesChanged), this); - g_signal_connect(manager_.get(), "interface-added", G_CALLBACK(onInterfaceAddedOrRemoved), this); - g_signal_connect(manager_.get(), "interface-removed", G_CALLBACK(onInterfaceAddedOrRemoved), - this); + if (manager_) { + g_signal_connect(manager_.get(), "object-added", G_CALLBACK(onObjectAdded), this); + g_signal_connect(manager_.get(), "object-removed", G_CALLBACK(onObjectRemoved), this); + g_signal_connect(manager_.get(), "interface-proxy-properties-changed", + G_CALLBACK(onInterfaceProxyPropertiesChanged), this); + g_signal_connect(manager_.get(), "interface-added", G_CALLBACK(onInterfaceAddedOrRemoved), + this); + g_signal_connect(manager_.get(), "interface-removed", G_CALLBACK(onInterfaceAddedOrRemoved), + this); + } #ifdef WANT_RFKILL rfkill_.on_update.connect(sigc::hide(sigc::mem_fun(*this, &Bluetooth::update))); @@ -446,6 +449,10 @@ auto waybar::modules::Bluetooth::getControllerProperties(GDBusObject* object, auto waybar::modules::Bluetooth::findCurController() -> std::optional { std::optional controller_info; + if (!manager_) { + return controller_info; + } + GList* objects = g_dbus_object_manager_get_objects(manager_.get()); for (GList* l = objects; l != NULL; l = l->next) { GDBusObject* object = G_DBUS_OBJECT(l->data); @@ -465,6 +472,9 @@ auto waybar::modules::Bluetooth::findCurController() -> std::optional& connected_devices) -> void { + if (!manager_) { + return; + } GList* objects = g_dbus_object_manager_get_objects(manager_.get()); for (GList* l = objects; l != NULL; l = l->next) { GDBusObject* object = G_DBUS_OBJECT(l->data); diff --git a/src/modules/cava/cavaRaw.cpp b/src/modules/cava/cavaRaw.cpp index d6a9e001..2b0aca72 100644 --- a/src/modules/cava/cavaRaw.cpp +++ b/src/modules/cava/cavaRaw.cpp @@ -26,6 +26,7 @@ void waybar::modules::cava::Cava::pause_resume() { backend_->doPauseResume(); } auto waybar::modules::cava::Cava::onUpdate(const std::string& input) -> void { Glib::signal_idle().connect_once([this, input]() { if (silence_) { + silence_ = false; label_.get_style_context()->remove_class("silent"); if (!label_.get_style_context()->has_class("updated")) label_.get_style_context()->add_class("updated"); @@ -38,7 +39,6 @@ auto waybar::modules::cava::Cava::onUpdate(const std::string& input) -> void { label_.show(); ALabel::update(); }); - silence_ = false; } auto waybar::modules::cava::Cava::onSilence() -> void { diff --git a/src/modules/cava/cava_backend.cpp b/src/modules/cava/cava_backend.cpp index c576f0cf..6eb540c9 100644 --- a/src/modules/cava/cava_backend.cpp +++ b/src/modules/cava/cava_backend.cpp @@ -218,7 +218,7 @@ void waybar::modules::cava::CavaBackend::loadConfig() { prm_.input = ::cava::input_method_by_name(config_["method"].asString().c_str()); if (config_["source"].isString()) { if (prm_.audio_source) free(prm_.audio_source); - prm_.audio_source = config_["source"].asString().data(); + prm_.audio_source = strdup(config_["source"].asString().c_str()); } if (config_["sample_rate"].isNumeric()) prm_.samplerate = config_["sample_rate"].asLargestInt(); if (config_["sample_bits"].isInt()) prm_.samplebits = config_["sample_bits"].asInt(); diff --git a/src/modules/cffi.cpp b/src/modules/cffi.cpp index 5c095f46..930c4d47 100644 --- a/src/modules/cffi.cpp +++ b/src/modules/cffi.cpp @@ -35,7 +35,7 @@ CFFI::CFFI(const std::string& name, const std::string& id, const Json::Value& co throw std::runtime_error{std::string{"Missing wbcffi_init function: "} + dlerror()}; } hooks_.deinit = reinterpret_cast(dlsym(handle, "wbcffi_deinit")); - if (!hooks_.init) { + if (!hooks_.deinit) { throw std::runtime_error{std::string{"Missing wbcffi_deinit function: "} + dlerror()}; } // Optional functions diff --git a/src/modules/cpu.cpp b/src/modules/cpu.cpp index 0703eaf7..8cfda2c1 100644 --- a/src/modules/cpu.cpp +++ b/src/modules/cpu.cpp @@ -26,9 +26,7 @@ auto waybar::modules::Cpu::update() -> void { auto [load1, load5, load15] = Load::getLoad(); auto [cpu_usage, tooltip] = CpuUsage::getCpuUsage(prev_times_); auto [max_frequency, min_frequency, avg_frequency] = CpuFrequency::getCpuFrequency(); - if (tooltipEnabled()) { - label_.set_tooltip_text(tooltip); - } + auto format = format_; auto total_usage = cpu_usage.empty() ? 0 : cpu_usage[0]; auto state = getState(total_usage); @@ -48,14 +46,25 @@ auto waybar::modules::Cpu::update() -> void { store.push_back(fmt::arg("max_frequency", max_frequency)); store.push_back(fmt::arg("min_frequency", min_frequency)); store.push_back(fmt::arg("avg_frequency", avg_frequency)); + std::vector arg_names; + arg_names.reserve(cpu_usage.size() * 2); for (size_t i = 1; i < cpu_usage.size(); ++i) { auto core_i = i - 1; - auto core_format = fmt::format("usage{}", core_i); - store.push_back(fmt::arg(core_format.c_str(), cpu_usage[i])); - auto icon_format = fmt::format("icon{}", core_i); - store.push_back(fmt::arg(icon_format.c_str(), getIcon(cpu_usage[i], icons))); + arg_names.push_back(fmt::format("usage{}", core_i)); + store.push_back(fmt::arg(arg_names.back().c_str(), cpu_usage[i])); + arg_names.push_back(fmt::format("icon{}", core_i)); + store.push_back(fmt::arg(arg_names.back().c_str(), getIcon(cpu_usage[i], icons))); } label_.set_markup(fmt::vformat(format, store)); + + if (tooltipEnabled()) { + if (config_["tooltip-format"].isString()) { + tooltip = config_["tooltip-format"].asString(); + label_.set_tooltip_markup(fmt::vformat(tooltip, store)); + } else { + label_.set_tooltip_markup(tooltip); + } + } } // Call parent update diff --git a/src/modules/cpu_frequency/common.cpp b/src/modules/cpu_frequency/common.cpp index e47364ba..05adc2b3 100644 --- a/src/modules/cpu_frequency/common.cpp +++ b/src/modules/cpu_frequency/common.cpp @@ -20,12 +20,7 @@ waybar::modules::CpuFrequency::CpuFrequency(const std::string& id, const Json::V auto waybar::modules::CpuFrequency::update() -> void { // TODO: as creating dynamic fmt::arg arrays is buggy we have to calc both auto [max_frequency, min_frequency, avg_frequency] = CpuFrequency::getCpuFrequency(); - if (tooltipEnabled()) { - auto tooltip = - fmt::format("Minimum frequency: {}\nAverage frequency: {}\nMaximum frequency: {}\n", - min_frequency, avg_frequency, max_frequency); - label_.set_tooltip_text(tooltip); - } + auto format = format_; auto state = getState(avg_frequency); if (!state.empty() && config_["format-" + state].isString()) { @@ -43,6 +38,18 @@ auto waybar::modules::CpuFrequency::update() -> void { store.push_back(fmt::arg("min_frequency", min_frequency)); store.push_back(fmt::arg("avg_frequency", avg_frequency)); label_.set_markup(fmt::vformat(format, store)); + + if (tooltipEnabled()) { + std::string tooltip; + if (config_["tooltip-format"].isString()) { + tooltip = config_["tooltip-format"].asString(); + label_.set_tooltip_markup(fmt::vformat(tooltip, store)); + } else { + tooltip = "Minimum frequency: {}\nAverage frequency: {}\nMaximum frequency: {}\n"; + label_.set_tooltip_markup( + fmt::format(fmt::runtime(tooltip), min_frequency, avg_frequency, max_frequency)); + } + } } // Call parent update diff --git a/src/modules/cpu_usage/common.cpp b/src/modules/cpu_usage/common.cpp index e3947967..1571cb74 100644 --- a/src/modules/cpu_usage/common.cpp +++ b/src/modules/cpu_usage/common.cpp @@ -20,9 +20,7 @@ waybar::modules::CpuUsage::CpuUsage(const std::string& id, const Json::Value& co auto waybar::modules::CpuUsage::update() -> void { // TODO: as creating dynamic fmt::arg arrays is buggy we have to calc both auto [cpu_usage, tooltip] = CpuUsage::getCpuUsage(prev_times_); - if (tooltipEnabled()) { - label_.set_tooltip_text(tooltip); - } + auto format = format_; auto total_usage = cpu_usage.empty() ? 0 : cpu_usage[0]; auto state = getState(total_usage); @@ -38,14 +36,25 @@ auto waybar::modules::CpuUsage::update() -> void { fmt::dynamic_format_arg_store store; store.push_back(fmt::arg("usage", total_usage)); store.push_back(fmt::arg("icon", getIcon(total_usage, icons))); + std::vector arg_names; + arg_names.reserve(cpu_usage.size() * 2); for (size_t i = 1; i < cpu_usage.size(); ++i) { auto core_i = i - 1; - auto core_format = fmt::format("usage{}", core_i); - store.push_back(fmt::arg(core_format.c_str(), cpu_usage[i])); - auto icon_format = fmt::format("icon{}", core_i); - store.push_back(fmt::arg(icon_format.c_str(), getIcon(cpu_usage[i], icons))); + arg_names.push_back(fmt::format("usage{}", core_i)); + store.push_back(fmt::arg(arg_names.back().c_str(), cpu_usage[i])); + arg_names.push_back(fmt::format("icon{}", core_i)); + store.push_back(fmt::arg(arg_names.back().c_str(), getIcon(cpu_usage[i], icons))); } label_.set_markup(fmt::vformat(format, store)); + + if (tooltipEnabled()) { + if (config_["tooltip-format"].isString()) { + tooltip = config_["tooltip-format"].asString(); + label_.set_tooltip_markup(fmt::vformat(tooltip, store)); + } else { + label_.set_tooltip_markup(tooltip); + } + } } // Call parent update @@ -71,7 +80,8 @@ std::tuple, std::string> waybar::modules::CpuUsage::getCpu auto [prev_idle, prev_total] = prev_times[0]; const float delta_idle = curr_idle - prev_idle; const float delta_total = curr_total - prev_total; - uint16_t tmp = 100 * (1 - delta_idle / delta_total); + uint16_t tmp = + (delta_total > 0) ? static_cast(100 * (1 - delta_idle / delta_total)) : 0; tooltip = fmt::format("Total: {}%\nCores: (pending)", tmp); usage.push_back(tmp); } else { @@ -93,7 +103,8 @@ std::tuple, std::string> waybar::modules::CpuUsage::getCpu } const float delta_idle = curr_idle - prev_idle; const float delta_total = curr_total - prev_total; - uint16_t tmp = 100 * (1 - delta_idle / delta_total); + uint16_t tmp = + (delta_total > 0) ? static_cast(100 * (1 - delta_idle / delta_total)) : 0; if (i == 0) { tooltip = fmt::format("Total: {}%", tmp); } else { diff --git a/src/modules/custom.cpp b/src/modules/custom.cpp index 0a8d9cf6..e2b705da 100644 --- a/src/modules/custom.cpp +++ b/src/modules/custom.cpp @@ -182,9 +182,10 @@ auto waybar::modules::Custom::update() -> void { if (tooltipEnabled()) { if (tooltip_format_enabled_) { auto tooltip = config_["tooltip-format"].asString(); - tooltip = fmt::format( - fmt::runtime(tooltip), fmt::arg("text", text_), fmt::arg("alt", alt_), - fmt::arg("icon", getIcon(percentage_, alt_)), fmt::arg("percentage", percentage_)); + tooltip = fmt::format(fmt::runtime(tooltip), fmt::arg("text", text_), + fmt::arg("tooltip", tooltip_), fmt::arg("alt", alt_), + fmt::arg("icon", getIcon(percentage_, alt_)), + fmt::arg("percentage", percentage_)); label_.set_tooltip_markup(tooltip); } else if (text_ == tooltip_) { label_.set_tooltip_markup(str); diff --git a/src/modules/disk.cpp b/src/modules/disk.cpp index ef257b72..fd7ef817 100644 --- a/src/modules/disk.cpp +++ b/src/modules/disk.cpp @@ -41,7 +41,7 @@ auto waybar::modules::Disk::update() -> void { fs_used - File system used space */ - if (err != 0) { + if (err != 0 || stats.f_blocks == 0) { event_box_.hide(); return; } @@ -81,7 +81,7 @@ auto waybar::modules::Disk::update() -> void { if (config_["tooltip-format"].isString()) { tooltip_format = config_["tooltip-format"].asString(); } - label_.set_tooltip_text(fmt::format( + label_.set_tooltip_markup(fmt::format( fmt::runtime(tooltip_format), stats.f_bavail * 100 / stats.f_blocks, fmt::arg("free", free), fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks), fmt::arg("used", used), fmt::arg("percentage_used", percentage_used), fmt::arg("total", total), @@ -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 return 1.0; } -} \ No newline at end of file +} diff --git a/src/modules/dwl/tags.cpp b/src/modules/dwl/tags.cpp index fb065650..399c7d4b 100644 --- a/src/modules/dwl/tags.cpp +++ b/src/modules/dwl/tags.cpp @@ -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) { + if (tag >= buttons_.size()) return; // First clear all occupied state auto& button = buttons_[tag]; if (clients) { diff --git a/src/modules/dwl/window.cpp b/src/modules/dwl/window.cpp index 2b679c9f..56e67895 100644 --- a/src/modules/dwl/window.cpp +++ b/src/modules/dwl/window.cpp @@ -116,7 +116,7 @@ void Window::handle_frame() { updateAppIconName(appid_, ""); updateAppIcon(); if (tooltipEnabled()) { - label_.set_tooltip_text(title_); + label_.set_tooltip_markup(title_); } } diff --git a/src/modules/ext/workspace_manager.cpp b/src/modules/ext/workspace_manager.cpp index 637177d3..b4471c14 100644 --- a/src/modules/ext/workspace_manager.cpp +++ b/src/modules/ext/workspace_manager.cpp @@ -143,7 +143,9 @@ void WorkspaceManager::handle_finished() { ext_manager_ = nullptr; } -void WorkspaceManager::commit() const { ext_workspace_manager_v1_commit(ext_manager_); } +void WorkspaceManager::commit() const { + if (ext_manager_) ext_workspace_manager_v1_commit(ext_manager_); +} void WorkspaceManager::update() { spdlog::debug("[ext/workspaces]: Updating state"); diff --git a/src/modules/gamemode.cpp b/src/modules/gamemode.cpp index 72ef9503..691a2844 100644 --- a/src/modules/gamemode.cpp +++ b/src/modules/gamemode.cpp @@ -128,9 +128,9 @@ void Gamemode::getData() { Glib::VariantContainerBase data = gamemode_proxy->call_sync("Get", parameters); if (data && data.is_of_type(Glib::VariantType("(v)"))) { Glib::VariantBase variant; - g_variant_get(data.gobj_copy(), "(v)", &variant); + g_variant_get(const_cast(data.gobj()), "(v)", &variant); if (variant && variant.is_of_type(Glib::VARIANT_TYPE_INT32)) { - g_variant_get(variant.gobj_copy(), "i", &gameCount); + g_variant_get(const_cast(variant.gobj()), "i", &gameCount); return; } } @@ -158,7 +158,7 @@ void Gamemode::prepareForSleep_cb(const Glib::RefPtr& con const Glib::VariantContainerBase& parameters) { if (parameters.is_of_type(Glib::VariantType("(b)"))) { gboolean sleeping; - g_variant_get(parameters.gobj_copy(), "(b)", &sleeping); + g_variant_get(const_cast(parameters.gobj()), "(b)", &sleeping); if (!sleeping) { getData(); dp.emit(); @@ -212,7 +212,7 @@ auto Gamemode::update() -> void { // Tooltip if (tooltip) { std::string text = fmt::format(fmt::runtime(tooltip_format), fmt::arg("count", gameCount)); - box_.set_tooltip_text(text); + box_.set_tooltip_markup(text); } // Label format diff --git a/src/modules/hyprland/submap.cpp b/src/modules/hyprland/submap.cpp index 5d587d02..ff18e7f3 100644 --- a/src/modules/hyprland/submap.cpp +++ b/src/modules/hyprland/submap.cpp @@ -60,7 +60,7 @@ auto Submap::update() -> void { } else { label_.set_markup(fmt::format(fmt::runtime(format_), submap_)); if (tooltipEnabled()) { - label_.set_tooltip_text(submap_); + label_.set_tooltip_markup(submap_); } event_box_.show(); } diff --git a/src/modules/hyprland/window.cpp b/src/modules/hyprland/window.cpp index e02a7691..1fddb45b 100644 --- a/src/modules/hyprland/window.cpp +++ b/src/modules/hyprland/window.cpp @@ -72,13 +72,13 @@ auto Window::update() -> void { tooltip_format = config_["tooltip-format"].asString(); } if (!tooltip_format.empty()) { - label_.set_tooltip_text( + label_.set_tooltip_markup( fmt::format(fmt::runtime(tooltip_format), fmt::arg("title", windowName), fmt::arg("initialTitle", windowData_.initial_title), fmt::arg("class", windowData_.class_name), fmt::arg("initialClass", windowData_.initial_class_name))); } 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) { - dp.emit(); -} +void Window::onEvent(const std::string& ev) { dp.emit(); } void Window::setClass(const std::string& classname, bool enable) { if (enable) { diff --git a/src/modules/hyprland/windowcount.cpp b/src/modules/hyprland/windowcount.cpp index 487b0083..ab573cca 100644 --- a/src/modules/hyprland/windowcount.cpp +++ b/src/modules/hyprland/windowcount.cpp @@ -38,7 +38,7 @@ WindowCount::~WindowCount() { auto WindowCount::update() -> void { std::lock_guard lg(mutex_); - + queryActiveWorkspace(); std::string format = config_["format"].asString(); @@ -58,7 +58,7 @@ auto WindowCount::update() -> void { } else if (!format.empty()) { label_.set_markup(fmt::format(fmt::runtime(format), workspace_.windows)); } else { - label_.set_text(fmt::format("{}", workspace_.windows)); + label_.set_markup(fmt::format("{}", workspace_.windows)); } label_.show(); @@ -125,9 +125,7 @@ void WindowCount::queryActiveWorkspace() { } } -void WindowCount::onEvent(const std::string& ev) { - dp.emit(); -} +void WindowCount::onEvent(const std::string& ev) { dp.emit(); } void WindowCount::setClass(const std::string& classname, bool enable) { if (enable) { diff --git a/src/modules/hyprland/workspace.cpp b/src/modules/hyprland/workspace.cpp index 4cdd8910..87933ac0 100644 --- a/src/modules/hyprland/workspace.cpp +++ b/src/modules/hyprland/workspace.cpp @@ -300,7 +300,7 @@ void Workspace::updateTaskbar(const std::string& workspace_icon) { } auto window_box = Gtk::make_managed(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"); if (window_repr.isActive) { window_box->get_style_context()->add_class("active"); diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index 6fa38260..88b01223 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -43,6 +43,13 @@ void Workspaces::init() { m_activeWorkspaceId = m_ipc.getSocket1JsonReply("activeworkspace")["id"].asInt(); initializeWorkspaces(); + + if (barScroll()) { + auto& window = const_cast(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(); } @@ -636,6 +643,7 @@ auto Workspaces::parseConfig(const Json::Value& config) -> void { populateBoolConfig(config, "persistent-only", m_persistentOnly); populateBoolConfig(config, "active-only", m_activeOnly); populateBoolConfig(config, "move-to-monitor", m_moveToMonitor); + populateBoolConfig(config, "enable-bar-scroll", m_barScroll); m_persistentWorkspaceConfig = config.get("persistent-workspaces", Json::Value()); populateSortByConfig(config); @@ -1151,4 +1159,31 @@ std::optional 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 diff --git a/src/modules/image.cpp b/src/modules/image.cpp index 189deee6..98bf3c46 100644 --- a/src/modules/image.cpp +++ b/src/modules/image.cpp @@ -14,22 +14,27 @@ waybar::modules::Image::Image(const std::string& id, const Json::Value& config) size_ = config["size"].asInt(); - interval_ = config_["interval"] == "once" - ? std::chrono::milliseconds::max() - : std::chrono::milliseconds(std::max( - 1L, // Minimum 1ms due to millisecond precision - static_cast( - (config_["interval"].isNumeric() ? config_["interval"].asDouble() : 0) * - 1000))); + const auto once = std::chrono::milliseconds::max(); + if (!config_.isMember("interval") || config_["interval"].isNull() || + config_["interval"] == "once") { + interval_ = once; + } else if (config_["interval"].isNumeric()) { + const auto interval_seconds = config_["interval"].asDouble(); + if (interval_seconds <= 0) { + interval_ = once; + } else { + interval_ = + std::chrono::milliseconds(std::max(1L, // Minimum 1ms due to millisecond precision + static_cast(interval_seconds * 1000))); + } + } else { + interval_ = once; + } if (size_ == 0) { size_ = 16; } - if (interval_.count() == 0) { - interval_ = std::chrono::milliseconds::max(); - } - delayWorker(); } diff --git a/src/modules/inhibitor.cpp b/src/modules/inhibitor.cpp index fe2a4be4..170d0508 100644 --- a/src/modules/inhibitor.cpp +++ b/src/modules/inhibitor.cpp @@ -123,7 +123,7 @@ auto Inhibitor::update() -> void { label_.get_style_context()->add_class(status_text); if (tooltipEnabled()) { - label_.set_tooltip_text(status_text); + label_.set_tooltip_markup(status_text); } return ALabel::update(); diff --git a/src/modules/jack.cpp b/src/modules/jack.cpp index 678f986b..578fb4e0 100644 --- a/src/modules/jack.cpp +++ b/src/modules/jack.cpp @@ -53,7 +53,7 @@ std::string JACK::JACKState() { auto JACK::update() -> void { std::string format; 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")) { label_.get_style_context()->remove_class("xrun"); @@ -80,7 +80,7 @@ auto JACK::update() -> void { if (tooltipEnabled()) { std::string tooltip_format = "{bufsize}/{samplerate} {latency}ms"; 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::arg("bufsize", bufsize_), fmt::arg("samplerate", samplerate_), 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) { + std::lock_guard lock(mutex_); bufsize_ = size; return 0; } int JACK::sampleRate(jack_nframes_t rate) { + std::lock_guard lock(mutex_); samplerate_ = rate; return 0; } int JACK::xrun() { + std::lock_guard lock(mutex_); xruns_ += 1; state_ = "xrun"; return 0; diff --git a/src/modules/keyboard_state.cpp b/src/modules/keyboard_state.cpp index 18ce0a7c..a2207fdd 100644 --- a/src/modules/keyboard_state.cpp +++ b/src/modules/keyboard_state.cpp @@ -232,9 +232,12 @@ waybar::modules::KeyboardState::KeyboardState(const std::string& id, const Bar& } tryAddDevice(dev_path); } else if (event->mask & IN_DELETE) { + std::lock_guard lock(devices_mutex_); auto it = libinput_devices_.find(dev_path); if (it != libinput_devices_.end()) { spdlog::info("Keyboard {} has been removed.", dev_path); + libinput_path_remove_device(it->second); + libinput_device_unref(it->second); libinput_devices_.erase(it); } } @@ -245,6 +248,7 @@ waybar::modules::KeyboardState::KeyboardState(const std::string& id, const Bar& } waybar::modules::KeyboardState::~KeyboardState() { + std::lock_guard lock(devices_mutex_); for (const auto& [_, dev_ptr] : libinput_devices_) { libinput_path_remove_device(dev_ptr); } @@ -256,11 +260,17 @@ auto waybar::modules::KeyboardState::update() -> void { try { std::string dev_path; - if (config_["device-path"].isString() && - libinput_devices_.find(config_["device-path"].asString()) != libinput_devices_.end()) { - dev_path = config_["device-path"].asString(); - } else { - dev_path = libinput_devices_.begin()->first; + { + std::lock_guard lock(devices_mutex_); + if (libinput_devices_.empty()) { + return; + } + if (config_["device-path"].isString() && + libinput_devices_.find(config_["device-path"].asString()) != libinput_devices_.end()) { + dev_path = config_["device-path"].asString(); + } else { + dev_path = libinput_devices_.begin()->first; + } } int fd = openFile(dev_path, O_NONBLOCK | O_CLOEXEC | O_RDONLY); auto dev = openDevice(fd); @@ -308,10 +318,15 @@ auto waybar::modules ::KeyboardState::tryAddDevice(const std::string& dev_path) auto dev = openDevice(fd); if (supportsLockStates(dev)) { spdlog::info("Found device {} at '{}'", libevdev_get_name(dev), dev_path); + std::lock_guard lock(devices_mutex_); if (libinput_devices_.find(dev_path) == libinput_devices_.end()) { auto device = libinput_path_add_device(libinput_, dev_path.c_str()); - libinput_device_ref(device); - libinput_devices_[dev_path] = device; + if (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); diff --git a/src/modules/load.cpp b/src/modules/load.cpp index 69a37b4e..72627405 100644 --- a/src/modules/load.cpp +++ b/src/modules/load.cpp @@ -22,7 +22,7 @@ auto waybar::modules::Load::update() -> void { auto [load1, load5, load15] = Load::getLoad(); if (tooltipEnabled()) { 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 state = getState(load1); diff --git a/src/modules/memory/common.cpp b/src/modules/memory/common.cpp index a83f6526..3c486a33 100644 --- a/src/modules/memory/common.cpp +++ b/src/modules/memory/common.cpp @@ -69,7 +69,7 @@ auto waybar::modules::Memory::update() -> void { if (tooltipEnabled()) { if (config_["tooltip-format"].isString()) { auto tooltip_format = config_["tooltip-format"].asString(); - label_.set_tooltip_text(fmt::format( + label_.set_tooltip_markup(fmt::format( fmt::runtime(tooltip_format), used_ram_percentage, fmt::arg("total", total_ram_gigabytes), fmt::arg("swapTotal", total_swap_gigabytes), 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("swapAvail", available_swap_gigabytes))); } 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 { diff --git a/src/modules/mpd/mpd.cpp b/src/modules/mpd/mpd.cpp index 8e6bbb25..1e28a58a 100644 --- a/src/modules/mpd/mpd.cpp +++ b/src/modules/mpd/mpd.cpp @@ -110,7 +110,7 @@ void waybar::modules::MPD::setLabel() { ? config_["tooltip-format-disconnected"].asString() : "MPD (disconnected)"; // Nothing to format - label_.set_tooltip_text(tooltip_format); + label_.set_tooltip_markup(tooltip_format); } return; } @@ -210,7 +210,7 @@ void waybar::modules::MPD::setLabel() { fmt::arg("stateIcon", stateIcon), fmt::arg("consumeIcon", consumeIcon), fmt::arg("randomIcon", randomIcon), fmt::arg("repeatIcon", repeatIcon), 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) { spdlog::warn("mpd: format error (tooltip): {}", e.what()); } @@ -323,6 +323,7 @@ void waybar::modules::MPD::checkErrors(mpd_connection* conn) { case MPD_ERROR_SYSTEM: if (auto ec = mpd_connection_get_system_error(conn); ec != 0) { mpd_connection_clear_error(conn); + connection_.reset(); throw std::system_error(ec, std::system_category()); } G_GNUC_FALLTHROUGH; diff --git a/src/modules/mpris/mpris.cpp b/src/modules/mpris/mpris.cpp index 2b345fc5..1bdd7df6 100644 --- a/src/modules/mpris/mpris.cpp +++ b/src/modules/mpris/mpris.cpp @@ -117,7 +117,7 @@ Mpris::Mpris(const std::string& id, const Json::Value& config) } GError* error = nullptr; - waybar::util::ScopeGuard error_deleter([error]() { + waybar::util::ScopeGuard error_deleter([&error]() { if (error) { g_error_free(error); } @@ -178,6 +178,7 @@ Mpris::Mpris(const std::string& id, const Json::Value& config) } Mpris::~Mpris() { + if (last_active_player_ && last_active_player_ != player) g_object_unref(last_active_player_); if (manager != nullptr) g_object_unref(manager); if (player != nullptr) g_object_unref(player); } @@ -458,10 +459,7 @@ auto Mpris::onPlayerStop(PlayerctlPlayer* player, gpointer data) -> void { if (!mpris) return; spdlog::debug("mpris: player-stop callback"); - - // hide widget - mpris->event_box_.set_visible(false); - // update widget + // update widget (update() handles visibility) mpris->dp.emit(); } @@ -480,7 +478,7 @@ auto Mpris::getPlayerInfo() -> std::optional { } GError* error = nullptr; - waybar::util::ScopeGuard error_deleter([error]() { + waybar::util::ScopeGuard error_deleter([&error]() { if (error) { g_error_free(error); } @@ -488,7 +486,12 @@ auto Mpris::getPlayerInfo() -> std::optional { char* player_status = nullptr; 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_; if (player_name == "playerctld") { @@ -498,19 +501,52 @@ auto Mpris::getPlayerInfo() -> std::optional { } // > get the list of players [..] in order of activity // https://github.com/altdesktop/playerctl/blob/b19a71cb9dba635df68d271bd2b3f6a99336a223/playerctl/playerctl-common.c#L248-L249 - players = g_list_first(players); - if (players) - player_name = static_cast(players->data)->name; - else - return std::nullopt; // no players found, hide the widget - } - - if (std::any_of(ignored_players_.begin(), ignored_players_.end(), - [&](const std::string& pn) { return player_name == pn; })) { + PlayerctlPlayer* first_valid_player = nullptr; + std::string first_valid_name; + for (auto* p = g_list_first(players); p != nullptr; p = p->next) { + auto* pn = static_cast(p->data); + std::string name = pn->name; + if (std::any_of(ignored_players_.begin(), ignored_players_.end(), + [&](const std::string& ignored) { return name == ignored; })) { + spdlog::warn("mpris[{}]: ignoring player update", name); + 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); 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 player_status[0] = std::tolower(player_status[0]); @@ -524,28 +560,29 @@ auto Mpris::getPlayerInfo() -> std::optional { .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_); info.artist = artist_; g_free(artist_); } 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_); info.album = album_; g_free(album_); } 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_); info.title = title_; g_free(title_); } 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_); auto len = std::chrono::microseconds(std::strtol(length_, nullptr, 10)); auto len_h = std::chrono::duration_cast(len); @@ -557,7 +594,7 @@ auto Mpris::getPlayerInfo() -> std::optional { if (error) goto errorexit; { - auto position_ = playerctl_player_get_position(player, &error); + auto position_ = playerctl_player_get_position(last_active_player_, &error); if (error) { // it's fine to have an error here because not all players report a position g_error_free(error); @@ -609,12 +646,13 @@ bool Mpris::handleToggle(GdkEventButton* const& e) { }); // Command pattern: encapsulate each button's action + auto* target = last_active_player_ ? last_active_player_ : player; const ButtonAction actions[] = { - {1, "on-click", [&]() { playerctl_player_play_pause(player, &error); }}, - {2, "on-click-middle", [&]() { playerctl_player_previous(player, &error); }}, - {3, "on-click-right", [&]() { playerctl_player_next(player, &error); }}, - {8, "on-click-backward", [&]() { playerctl_player_previous(player, &error); }}, - {9, "on-click-forward", [&]() { playerctl_player_next(player, &error); }}, + {1, "on-click", [&]() { playerctl_player_play_pause(target, &error); }}, + {2, "on-click-middle", [&]() { playerctl_player_previous(target, &error); }}, + {3, "on-click-right", [&]() { playerctl_player_next(target, &error); }}, + {8, "on-click-backward", [&]() { playerctl_player_previous(target, &error); }}, + {9, "on-click-forward", [&]() { playerctl_player_next(target, &error); }}, }; 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("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) { spdlog::warn("mpris: format error (tooltip): {}", e.what()); } diff --git a/src/modules/network.cpp b/src/modules/network.cpp index c33e750d..34dcc03c 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -45,7 +45,8 @@ waybar::modules::Network::readBandwidthUsage() { std::istringstream iss(line); std::string ifacename; - iss >> ifacename; // ifacename contains "eth0:" + iss >> ifacename; // ifacename contains "eth0:" + if (ifacename.empty()) continue; ifacename.pop_back(); // remove trailing ':' if (ifacename != ifname_) { continue; diff --git a/src/modules/niri/backend.cpp b/src/modules/niri/backend.cpp index 1ee1bf3f..68bb1724 100644 --- a/src/modules/niri/backend.cpp +++ b/src/modules/niri/backend.cpp @@ -20,12 +20,13 @@ namespace waybar::modules::niri { +IPC::IPC() { startIPC(); } + int IPC::connectToSocket() { const char* socket_path = getenv("NIRI_SOCKET"); if (socket_path == nullptr) { - spdlog::warn("Niri is not running, niri IPC will not be available."); - return -1; + throw std::runtime_error("Niri IPC: NIRI_SOCKET was not set! (Is Niri running?)"); } struct sockaddr_un addr; @@ -54,16 +55,9 @@ int IPC::connectToSocket() { void IPC::startIPC() { // will start IPC and relay events to parseIPC - std::thread([&]() { - int socketfd; - try { - socketfd = connectToSocket(); - } catch (std::exception& e) { - spdlog::error("Niri IPC: failed to start, reason: {}", e.what()); - return; - } - if (socketfd == -1) return; + int socketfd = connectToSocket(); + std::thread([this, socketfd]() { spdlog::info("Niri IPC starting"); 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) { int socketfd = connectToSocket(); - if (socketfd == -1) throw std::runtime_error("Niri is not running"); auto unix_istream = Gio::UnixInputStream::create(socketfd, true); auto unix_ostream = Gio::UnixOutputStream::create(socketfd, false); diff --git a/src/modules/niri/window.cpp b/src/modules/niri/window.cpp index c3537769..61b3a7dd 100644 --- a/src/modules/niri/window.cpp +++ b/src/modules/niri/window.cpp @@ -67,7 +67,7 @@ void Window::doUpdate() { updateAppIconName(appId, ""); - if (tooltipEnabled()) label_.set_tooltip_text(title); + if (tooltipEnabled()) label_.set_tooltip_markup(title); const auto id = window["id"].asUInt64(); const auto workspaceId = window["workspace_id"].asUInt64(); diff --git a/src/modules/niri/workspaces.cpp b/src/modules/niri/workspaces.cpp index 8eb912bc..3e8a432e 100644 --- a/src/modules/niri/workspaces.cpp +++ b/src/modules/niri/workspaces.cpp @@ -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["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_active"].asBool() && icons["active"]) return icons["active"].asString(); + if (ws["active_window_id"].isNull() && icons["empty"]) return icons["empty"].asString(); if (ws["name"]) { const auto& name = ws["name"].asString(); diff --git a/src/modules/power_profiles_daemon.cpp b/src/modules/power_profiles_daemon.cpp index abad763d..566787f3 100644 --- a/src/modules/power_profiles_daemon.cpp +++ b/src/modules/power_profiles_daemon.cpp @@ -157,7 +157,7 @@ auto PowerProfilesDaemon::update() -> void { store.push_back(fmt::arg("icon", getIcon(0, profile.name))); label_.set_markup(fmt::vformat(format_, store)); if (tooltipEnabled()) { - label_.set_tooltip_text(fmt::vformat(tooltipFormat_, store)); + label_.set_tooltip_markup(fmt::vformat(tooltipFormat_, store)); } // Set CSS class @@ -176,6 +176,10 @@ auto PowerProfilesDaemon::update() -> void { bool PowerProfilesDaemon::handleToggle(GdkEventButton* const& e) { 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 */ { activeProfile_++; if (activeProfile_ == availableProfiles_.end()) { diff --git a/src/modules/pulseaudio.cpp b/src/modules/pulseaudio.cpp index f60c39ff..4d63ff3c 100644 --- a/src/modules/pulseaudio.cpp +++ b/src/modules/pulseaudio.cpp @@ -138,7 +138,7 @@ auto waybar::modules::Pulseaudio::update() -> void { fmt::arg("source_volume", source_volume), fmt::arg("source_desc", source_desc), fmt::arg("icon", getIcon(sink_volume, getPulseIcon())))); } else { - label_.set_tooltip_text(sink_desc); + label_.set_tooltip_markup(sink_desc); } } diff --git a/src/modules/river/layout.cpp b/src/modules/river/layout.cpp index 2d76835a..3fb536e6 100644 --- a/src/modules/river/layout.cpp +++ b/src/modules/river/layout.cpp @@ -118,6 +118,7 @@ Layout::Layout(const std::string& id, const waybar::Bar& bar, const Json::Value& if (!seat_) { spdlog::error("wl_seat not advertised"); + return; } label_.hide(); diff --git a/src/modules/river/mode.cpp b/src/modules/river/mode.cpp index 3d378776..53038c96 100644 --- a/src/modules/river/mode.cpp +++ b/src/modules/river/mode.cpp @@ -77,6 +77,7 @@ Mode::Mode(const std::string& id, const waybar::Bar& bar, const Json::Value& con if (!seat_) { spdlog::error("wl_seat not advertised"); + return; } if (config_["hidden-modes"].isArray()) { diff --git a/src/modules/river/tags.cpp b/src/modules/river/tags.cpp index 618474a6..7957015b 100644 --- a/src/modules/river/tags.cpp +++ b/src/modules/river/tags.cpp @@ -141,10 +141,12 @@ Tags::Tags(const std::string& id, const waybar::Bar& bar, const Json::Value& con if (!control_) { spdlog::error("river_control_v1 not advertised"); + return; } if (!seat_) { spdlog::error("wl_seat not advertised"); + return; } seat_status_ = zriver_status_manager_v1_get_river_seat_status(status_manager_, seat_); @@ -217,6 +219,7 @@ Tags::~Tags() { } void Tags::handle_show() { + if (!status_manager_) return; struct wl_output* output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()); output_status_ = zriver_status_manager_v1_get_river_output_status(status_manager_, output); zriver_output_status_v1_add_listener(output_status_, &output_status_listener_impl, this); diff --git a/src/modules/river/window.cpp b/src/modules/river/window.cpp index cb0dbebd..131f36d9 100644 --- a/src/modules/river/window.cpp +++ b/src/modules/river/window.cpp @@ -82,6 +82,7 @@ Window::Window(const std::string& id, const waybar::Bar& bar, const Json::Value& if (!seat_) { spdlog::error("wl_seat not advertised"); + return; } label_.hide(); // hide the label until populated diff --git a/src/modules/simpleclock.cpp b/src/modules/simpleclock.cpp index b6a96ecc..59a2b1c1 100644 --- a/src/modules/simpleclock.cpp +++ b/src/modules/simpleclock.cpp @@ -25,9 +25,9 @@ auto waybar::modules::Clock::update() -> void { if (config_["tooltip-format"].isString()) { auto tooltip_format = config_["tooltip-format"].asString(); auto tooltip_text = fmt::format(fmt::runtime(tooltip_format), localtime); - label_.set_tooltip_text(tooltip_text); + label_.set_tooltip_markup(tooltip_text); } else { - label_.set_tooltip_text(text); + label_.set_tooltip_markup(text); } } // Call parent update diff --git a/src/modules/sndio.cpp b/src/modules/sndio.cpp index 9779cd36..d878c4a4 100644 --- a/src/modules/sndio.cpp +++ b/src/modules/sndio.cpp @@ -102,7 +102,9 @@ Sndio::~Sndio() { sioctl_close(hdl_); } auto Sndio::update() -> void { auto format = format_; - unsigned int vol = 100. * static_cast(volume_) / static_cast(maxval_); + unsigned int vol = (maxval_ > 0) ? static_cast(100. * static_cast(volume_) / + static_cast(maxval_)) + : 0; if (volume_ == 0) { label_.get_style_context()->add_class("muted"); diff --git a/src/modules/sni/host.cpp b/src/modules/sni/host.cpp index 54faa16c..75501207 100644 --- a/src/modules/sni/host.cpp +++ b/src/modules/sni/host.cpp @@ -59,7 +59,7 @@ void Host::nameVanished(const Glib::RefPtr& conn, const G void Host::proxyReady(GObject* src, GAsyncResult* res, gpointer data) { GError* error = nullptr; - waybar::util::ScopeGuard error_deleter([error]() { + waybar::util::ScopeGuard error_deleter([&error]() { if (error != nullptr) { 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) { GError* error = nullptr; - waybar::util::ScopeGuard error_deleter([error]() { + waybar::util::ScopeGuard error_deleter([&error]() { if (error != nullptr) { g_error_free(error); } diff --git a/src/modules/sni/item.cpp b/src/modules/sni/item.cpp index ef2543b5..d33765d2 100644 --- a/src/modules/sni/item.cpp +++ b/src/modules/sni/item.cpp @@ -365,13 +365,14 @@ Glib::RefPtr Item::extractPixBuf(GVariant* variant) { void Item::updateImage() { auto pixbuf = getIconPixbuf(); + if (!pixbuf) return; auto scaled_icon_size = getScaledIconSize(); // 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 // 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. - 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(); pixbuf = pixbuf->scale_simple(width, scaled_icon_size, Gdk::InterpType::INTERP_BILINEAR); } diff --git a/src/modules/sni/watcher.cpp b/src/modules/sni/watcher.cpp index 324bd9f5..1534d924 100644 --- a/src/modules/sni/watcher.cpp +++ b/src/modules/sni/watcher.cpp @@ -31,7 +31,7 @@ Watcher::~Watcher() { void Watcher::busAcquired(const Glib::RefPtr& conn, Glib::ustring name) { GError* error = nullptr; - waybar::util::ScopeGuard error_deleter([error]() { + waybar::util::ScopeGuard error_deleter([&error]() { if (error) { g_error_free(error); } diff --git a/src/modules/sway/mode.cpp b/src/modules/sway/mode.cpp index b81735e5..1f5f071a 100644 --- a/src/modules/sway/mode.cpp +++ b/src/modules/sway/mode.cpp @@ -44,7 +44,7 @@ auto Mode::update() -> void { } else { label_.set_markup(fmt::format(fmt::runtime(format_), mode_)); if (tooltipEnabled()) { - label_.set_tooltip_text(mode_); + label_.set_tooltip_markup(mode_); } event_box_.show(); } diff --git a/src/modules/sway/window.cpp b/src/modules/sway/window.cpp index 68655a76..830a4120 100644 --- a/src/modules/sway/window.cpp +++ b/src/modules/sway/window.cpp @@ -99,7 +99,7 @@ auto Window::update() -> void { fmt::arg("shell", shell_), fmt::arg("marks", marks_)), config_["rewrite"])); if (tooltipEnabled()) { - label_.set_tooltip_text(window_); + label_.set_tooltip_markup(window_); } updateAppIcon(); diff --git a/src/modules/systemd_failed_units.cpp b/src/modules/systemd_failed_units.cpp index 90f33be7..68e61fe9 100644 --- a/src/modules/systemd_failed_units.cpp +++ b/src/modules/systemd_failed_units.cpp @@ -77,9 +77,9 @@ void SystemdFailedUnits::RequestSystemState() { Glib::VariantContainerBase data = proxy->call_sync("Get", parameters); if (data && data.is_of_type(Glib::VariantType("(v)"))) { Glib::VariantBase variant; - g_variant_get(data.gobj_copy(), "(v)", &variant); + g_variant_get(const_cast(data.gobj()), "(v)", &variant); 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(variant.gobj()), NULL); } } } catch (Glib::Error& e) { @@ -105,9 +105,9 @@ void SystemdFailedUnits::RequestFailedUnits() { Glib::VariantContainerBase data = proxy->call_sync("Get", parameters); if (data && data.is_of_type(Glib::VariantType("(v)"))) { Glib::VariantBase variant; - g_variant_get(data.gobj_copy(), "(v)", &variant); + g_variant_get(const_cast(data.gobj()), "(v)", &variant); 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(variant.gobj())); } } } catch (Glib::Error& e) { diff --git a/src/modules/temperature.cpp b/src/modules/temperature.cpp index fa23ef56..bb5ff02c 100644 --- a/src/modules/temperature.cpp +++ b/src/modules/temperature.cpp @@ -101,7 +101,7 @@ auto waybar::modules::Temperature::update() -> void { if (config_["tooltip-format"].isString()) { tooltip_format = config_["tooltip-format"].asString(); } - label_.set_tooltip_text(fmt::format( + label_.set_tooltip_markup(fmt::format( fmt::runtime(tooltip_format), fmt::arg("temperatureC", temperature_c), fmt::arg("temperatureF", temperature_f), fmt::arg("temperatureK", temperature_k))); } diff --git a/src/modules/upower.cpp b/src/modules/upower.cpp index 7530890c..8202b718 100644 --- a/src/modules/upower.cpp +++ b/src/modules/upower.cpp @@ -59,10 +59,13 @@ UPower::UPower(const std::string& id, const Json::Value& config) sigc::mem_fun(*this, &UPower::getConn_cb)); // Make UPower client - GError** gErr = NULL; - upClient_ = up_client_new_full(NULL, gErr); - if (upClient_ == NULL) - spdlog::error("Upower. UPower client connection error. {}", (*gErr)->message); + GError* gErr = NULL; + upClient_ = up_client_new_full(NULL, &gErr); + if (upClient_ == NULL) { + spdlog::error("Upower. UPower client connection error. {}", + gErr ? gErr->message : "unknown error"); + if (gErr) g_error_free(gErr); + } // Subscribe UPower events g_signal_connect(upClient_, "device-added", G_CALLBACK(deviceAdded_cb), this); diff --git a/src/modules/wayfire/backend.cpp b/src/modules/wayfire/backend.cpp index 5a9c0c1a..545aaa89 100644 --- a/src/modules/wayfire/backend.cpp +++ b/src/modules/wayfire/backend.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -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 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(r); + } 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& { + if (!output.has_value()) { + throw std::runtime_error("Wayfire IPC: wset has no output assigned"); + } const auto& out = output.value().get(); auto [qx, rx] = std::div(geo["x"].asInt(), out.w); 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 { auto p = instance.lock(); - if (!p) instance = p = std::shared_ptr(new IPC); + if (!p) { + instance = p = std::shared_ptr(new IPC); + p->start(); + } return p; } @@ -116,7 +129,9 @@ auto IPC::connect() -> Sock { } auto IPC::receive(Sock& sock) -> Json::Value { - auto len = *reinterpret_cast(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); 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-output", {}); - std::thread([&] { + std::thread([self = shared_from_this()] { auto sock = connect(); { Json::Value json; json["method"] = "window-rules/events/watch"; - pack_and_write(sock, Json::writeString(writer_builder, json)); - if (receive(sock)["result"] != "ok") { + pack_and_write(sock, Json::writeString(self->writer_builder, json)); + if (self->receive(sock)["result"] != "ok") { spdlog::error( "Wayfire IPC: method \"window-rules/events/watch\"" " 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(); spdlog::debug("Wayfire IPC: received event \"{}\"", ev); - root_event_handler(ev, json); + self->root_event_handler(ev, json); } }).detach(); } diff --git a/src/modules/wayfire/window.cpp b/src/modules/wayfire/window.cpp index fbcde6ec..8634f090 100644 --- a/src/modules/wayfire/window.cpp +++ b/src/modules/wayfire/window.cpp @@ -34,8 +34,12 @@ auto Window::update() -> void { auto Window::update_icon_label() -> void { auto _ = ipc->lock_state(); - const auto& output = ipc->get_outputs().at(bar_.output->name); - const auto& wset = ipc->get_wsets().at(output.wset_idx); + auto out_it = ipc->get_outputs().find(bar_.output->name); + 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(); auto ctx = bar_.window.get_style_context(); diff --git a/src/modules/wayfire/workspaces.cpp b/src/modules/wayfire/workspaces.cpp index 4c4cd6c1..724a19f8 100644 --- a/src/modules/wayfire/workspaces.cpp +++ b/src/modules/wayfire/workspaces.cpp @@ -70,8 +70,12 @@ auto Workspaces::handleScroll(GdkEventScroll* e) -> bool { Json::Value data; { auto _ = ipc->lock_state(); - const auto& output = ipc->get_outputs().at(bar_.output->name); - const auto& wset = ipc->get_wsets().at(output.wset_idx); + auto out_it = ipc->get_outputs().find(bar_.output->name); + 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 i = (wset.ws_idx() + delta + n) % n; data["x"] = Json::Value((uint64_t)i % wset.ws_w); @@ -92,8 +96,12 @@ auto Workspaces::update_box() -> void { auto _ = ipc->lock_state(); const auto& output_name = bar_.output->name; - const auto& output = ipc->get_outputs().at(output_name); - const auto& wset = ipc->get_wsets().at(output.wset_idx); + auto out_it = ipc->get_outputs().find(output_name); + 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 ws_w = wset.ws_w; diff --git a/src/modules/wireplumber.cpp b/src/modules/wireplumber.cpp index bb3005bd..e9cb7206 100644 --- a/src/modules/wireplumber.cpp +++ b/src/modules/wireplumber.cpp @@ -81,7 +81,7 @@ void waybar::modules::Wireplumber::updateNodeName(waybar::modules::Wireplumber* if (proxy == nullptr) { auto err = fmt::format("Object '{}' not found\n", id); spdlog::error("[{}]: {}", self->name_, err); - throw std::runtime_error(err); + return; } g_autoptr(WpProperties) properties = @@ -153,7 +153,7 @@ void waybar::modules::Wireplumber::updateVolume(waybar::modules::Wireplumber* se if (variant == nullptr) { auto err = fmt::format("Node {} does not support volume\n", id); spdlog::error("[{}]: {}", self->name_, err); - throw std::runtime_error(err); + return; } 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) { 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"); if (self->mixer_api_ == nullptr) { spdlog::error("[{}]: mixer api is not loaded.", self->name_); - throw std::runtime_error("Mixer api is not loaded\n"); + return; } // Get default sink @@ -336,7 +336,7 @@ void waybar::modules::Wireplumber::onPluginActivated(WpObject* p, GAsyncResult* if (wp_object_activate_finish(p, res, &error) == 0) { spdlog::error("[{}]: error activating plugin: {}", self->name_, error->message); - throw std::runtime_error(error->message); + return; } if (--self->pending_plugins_ == 0) { @@ -373,7 +373,7 @@ void waybar::modules::Wireplumber::onDefaultNodesApiLoaded(WpObject* p, GAsyncRe if (success == FALSE) { spdlog::error("[{}]: default nodes API load failed", self->name_); - throw std::runtime_error(error->message); + return; } spdlog::debug("[{}]: loaded default nodes api", self->name_); 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) { spdlog::error("[{}]: mixer API load failed", self->name_); - throw std::runtime_error(error->message); + return; } spdlog::debug("[{}]: loaded mixer API", self->name_); @@ -479,12 +479,12 @@ auto waybar::modules::Wireplumber::update() -> void { } 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::arg("icon", getIcon(vol)), fmt::arg("format_source", formatted_source), fmt::arg("source_volume", source_vol), fmt::arg("source_desc", source_name_))); } 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 (mixer_api_ == nullptr) return true; GVariant* variant = g_variant_new_double(newVol); gboolean ret; g_signal_emit_by_name(mixer_api_, "set-volume", node_id_, variant, &ret); diff --git a/src/modules/wlr/taskbar.cpp b/src/modules/wlr/taskbar.cpp index d58303da..2aef0ae3 100644 --- a/src/modules/wlr/taskbar.cpp +++ b/src/modules/wlr/taskbar.cpp @@ -437,7 +437,9 @@ void Task::handle_drag_data_get(const Glib::RefPtr& context, void Task::handle_drag_data_received(const Glib::RefPtr& context, int x, int y, Gtk::SelectionData selection_data, guint info, guint time) { spdlog::debug("drag_data_received"); - gpointer handle = *(gpointer*)selection_data.get_data(); + auto* raw = selection_data.get_data(); + if (!raw || selection_data.get_length() < static_cast(sizeof(gpointer))) return; + gpointer handle = *(gpointer*)raw; auto dragged_button = (Gtk::Button*)handle; if (dragged_button == &this->button) return; @@ -509,7 +511,7 @@ void Task::update() { if (markup) button.set_tooltip_markup(txt); else - button.set_tooltip_text(txt); + button.set_tooltip_markup(txt); } } diff --git a/src/util/audio_backend.cpp b/src/util/audio_backend.cpp index 342d40b2..f61ee945 100644 --- a/src/util/audio_backend.cpp +++ b/src/util/audio_backend.cpp @@ -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) { auto* backend = static_cast(data); - backend->current_sink_name_ = i->default_sink_name; - backend->default_sink_name = i->default_sink_name; - backend->default_source_name_ = i->default_source_name; + if (i == nullptr) return; + backend->current_sink_name_ = i->default_sink_name ? i->default_sink_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_source_info_list(context, sourceInfoCb, data); @@ -355,6 +356,7 @@ void AudioBackend::changeVolume(ChangeType change_type, double step, uint16_t ma } void AudioBackend::toggleSinkMute() { + if (context_ == nullptr || pa_context_get_state(context_) != PA_CONTEXT_READY) return; muted_ = !muted_; pa_threaded_mainloop_lock(mainloop_); pa_context_set_sink_mute_by_index(context_, sink_idx_, static_cast(muted_), nullptr, @@ -363,6 +365,7 @@ void AudioBackend::toggleSinkMute() { } void AudioBackend::toggleSinkMute(bool mute) { + if (context_ == nullptr || pa_context_get_state(context_) != PA_CONTEXT_READY) return; muted_ = mute; pa_threaded_mainloop_lock(mainloop_); pa_context_set_sink_mute_by_index(context_, sink_idx_, static_cast(muted_), nullptr, @@ -371,7 +374,8 @@ void AudioBackend::toggleSinkMute(bool mute) { } 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_context_set_source_mute_by_index(context_, source_idx_, static_cast(source_muted_), nullptr, nullptr); @@ -379,6 +383,7 @@ void AudioBackend::toggleSourceMute() { } void AudioBackend::toggleSourceMute(bool mute) { + if (context_ == nullptr || pa_context_get_state(context_) != PA_CONTEXT_READY) return; source_muted_ = mute; pa_threaded_mainloop_lock(mainloop_); pa_context_set_source_mute_by_index(context_, source_idx_, static_cast(source_muted_), diff --git a/src/util/backlight_backend.cpp b/src/util/backlight_backend.cpp index 48473dd2..61eb9b43 100644 --- a/src/util/backlight_backend.cpp +++ b/src/util/backlight_backend.cpp @@ -79,18 +79,38 @@ static void upsert_device(std::vector& devices, udev_device* de }); if (found != devices.end()) { if (actual != nullptr) { - found->set_actual(std::stoi(actual)); + try { + found->set_actual(std::stoi(actual)); + } catch (const std::exception&) { + } } if (max != nullptr) { - found->set_max(std::stoi(max)); + try { + found->set_max(std::stoi(max)); + } catch (const std::exception&) { + } } if (power != nullptr) { - found->set_powered(std::stoi(power) == 0); + try { + found->set_powered(std::stoi(power) == 0); + } catch (const std::exception&) { + } } } else { - const int actual_int = actual == nullptr ? 0 : std::stoi(actual); - const int max_int = max == nullptr ? 0 : std::stoi(max); - const bool power_bool = power == nullptr ? true : std::stoi(power) == 0; + int actual_int = 0, max_int = 0; + bool power_bool = true; + 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); } } @@ -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, int max_brightness) { + if (!login_proxy_) { + spdlog::error("Login proxy not available, cannot set brightness"); + return; + } + brightness = std::clamp(brightness, 0, max_brightness); 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); if (best != nullptr) { + if (best->get_max() == 0) return 0; return static_cast(std::round(best->get_actual() * 100.0F / best->get_max())); } diff --git a/src/util/css_reload_helper.cpp b/src/util/css_reload_helper.cpp index 06381d60..be8c0a68 100644 --- a/src/util/css_reload_helper.cpp +++ b/src/util/css_reload_helper.cpp @@ -115,11 +115,15 @@ std::vector waybar::CssReloadHelper::parseImports(const std::string auto maxIterations = 100U; do { previousSize = imports.size(); + std::vector to_parse; for (const auto& [file, parsed] : imports) { 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); diff --git a/src/util/icon_loader.cpp b/src/util/icon_loader.cpp index fe534f69..1d0bd067 100644 --- a/src/util/icon_loader.cpp +++ b/src/util/icon_loader.cpp @@ -5,8 +5,11 @@ std::vector IconLoader::search_prefix() { std::vector prefixes = {""}; - std::string home_dir = std::getenv("HOME"); - prefixes.push_back(home_dir + "/.local/share/"); + const char* home_env = std::getenv("HOME"); + 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"); if (!xdg_data_dirs) { @@ -139,7 +142,7 @@ bool IconLoader::image_load_icon(Gtk::Image& image, const Glib::RefPtrget_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(); pixbuf = pixbuf->scale_simple(width, scaled_icon_size, Gdk::InterpType::INTERP_BILINEAR); } diff --git a/src/util/pipewire/pipewire_backend.cpp b/src/util/pipewire/pipewire_backend.cpp index a88a3fbe..82ec0c0f 100644 --- a/src/util/pipewire/pipewire_backend.cpp +++ b/src/util/pipewire/pipewire_backend.cpp @@ -54,11 +54,17 @@ PipewireBackend::PipewireBackend(PrivateConstructorTag tag) context_ = pw_context_new(pw_thread_loop_get_loop(mainloop_), nullptr, 0); if (context_ == nullptr) { pw_thread_loop_unlock(mainloop_); + pw_thread_loop_destroy(mainloop_); + mainloop_ = nullptr; throw std::runtime_error("pa_context_new() failed."); } core_ = pw_context_connect(context_, nullptr, 0); if (core_ == nullptr) { 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"); } 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); - privacy_nodes.insert_or_assign(id, pNodeInfo); + { + std::lock_guard lock(mutex_); + privacy_nodes.insert_or_assign(id, pNodeInfo); + } } void PipewireBackend::handleRegistryEventGlobalRemove(uint32_t id) { diff --git a/src/util/portal.cpp b/src/util/portal.cpp index 6df2a6b6..e2be97c7 100644 --- a/src/util/portal.cpp +++ b/src/util/portal.cpp @@ -58,21 +58,27 @@ void waybar::Portal::refreshAppearance() { // xdg-desktop-portal 1.17 will fix this issue with a new `ReadOne` method, // but this version is not yet released. // TODO(xdg-desktop-portal v1.17): switch to ReadOne - auto container = Glib::VariantBase::cast_dynamic(response); - Glib::VariantBase modev; - container.get_child(modev, 0); - auto mode = - Glib::VariantBase::cast_dynamic>>>(modev) - .get() - .get() - .get(); - auto newMode = Appearance(mode); - if (newMode == currentMode) { + try { + auto container = Glib::VariantBase::cast_dynamic(response); + Glib::VariantBase modev; + container.get_child(modev, 0); + auto mode = + Glib::VariantBase::cast_dynamic>>>( + modev) + .get() + .get() + .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; } - spdlog::info("Discovered appearance '{}'", newMode); - currentMode = newMode; - m_signal_appearance_changed.emit(currentMode); } waybar::Appearance waybar::Portal::getAppearance() { return currentMode; };