diff --git a/.clang-tidy b/.clang-tidy index b7a33451..f74eae65 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -22,7 +22,7 @@ CheckOptions: - { key: readability-identifier-naming.FunctionCase, value: camelBack } - { key: readability-identifier-naming.VariableCase, value: camelBack } - { key: readability-identifier-naming.PrivateMemberCase, value: camelBack } - - { key: readability-identifier-naming.PrivateMemberPrefix, value: m_ } + - { key: readability-identifier-naming.PrivateMemberSuffix, value: _ } - { key: readability-identifier-naming.EnumCase, value: CamelCase } - { key: readability-identifier-naming.EnumConstantCase, value: UPPER_CASE } - { key: readability-identifier-naming.GlobalConstantCase, value: UPPER_CASE } diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 00000000..a89e734f --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,65 @@ +bug: + - "(crash|bug|error|coredump|freeze|segfault|issue|problem)" + +enhancement: + - "(feature|enhancement|improvement|request|suggestion)" + +hyprland: + - "(hyprland)" + +network: + - "(network|wifi|ethernet)" + +bluetooth: + - "(bluetooth|bluez)" + +sway: + - "(sway)" + +cpu: + - "(cpu)" + +memory: + - "(memory|ram)" + +disk: + - "(disk|storage)" + +battery: + - "(upower|battery)" + +sni: + - "(sni|tray)" + +dwl: + - "(dwl)" + +custom: + - "(custom|module|extension|plugin|script)" + +mpd: + - "(mpd|music)" + +audio: + - "(pulseaudio|alsa|jack|audio|pirewire|wireplumber)" + +temperature: + - "(temperature|thermal|hwmon)" + +clock: + - "(clock|time|date)" + +gamemode: + - "(gamemode|game|gaming)" + +inhibitor: + - "(inhibitor|idle|lock|suspend|hibernate|logout)" + +cava: + - "(cava|audio-visualizer)" + +backlight: + - "(backlight|brightness)" + +keyboard: + - "(keyboard|keymap|layout|shortcut)" diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml index 46e338ef..40fd3126 100644 --- a/.github/workflows/clang-format.yml +++ b/.github/workflows/clang-format.yml @@ -2,14 +2,18 @@ name: clang-format on: [push, pull_request] +concurrency: + group: ${{ github.workflow }}-format-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: DoozyX/clang-format-lint-action@v0.16.2 - name: clang-format - with: - source: '.' - extensions: 'hpp,h,cpp,c' - clangFormatVersion: 16 + - uses: actions/checkout@v3 + - uses: DoozyX/clang-format-lint-action@v0.16.2 + name: clang-format + with: + source: "." + extensions: "hpp,h,cpp,c" + clangFormatVersion: 16 diff --git a/.github/workflows/clang-tidy.yml b/.github/workflows/clang-tidy.yml index 86dd1f73..a39bd23d 100644 --- a/.github/workflows/clang-tidy.yml +++ b/.github/workflows/clang-tidy.yml @@ -2,6 +2,10 @@ name: clang-tidy on: [push, pull_request] +concurrency: + group: ${{ github.workflow }}-tidy-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: build: runs-on: ubuntu-latest @@ -13,7 +17,11 @@ jobs: run: | meson -Dcpp_std=c++20 build # necessary to generate compile_commands.json ninja -C build # necessary to find certain .h files (xdg, wayland, etc.) - - uses: cpp-linter/cpp-linter-action@v2.7.5 + - uses: actions/setup-python@v5 + with: + python-version: '3.10' # to be kept in sync with cpp-linter-action + update-environment: true # the python dist installed by the action needs LD_LIBRARY_PATH to work + - uses: cpp-linter/cpp-linter-action@v2.9.1 name: clang-tidy id: clang-tidy-check env: diff --git a/.github/workflows/freebsd.yml b/.github/workflows/freebsd.yml index 46419c1d..f0b8f69c 100644 --- a/.github/workflows/freebsd.yml +++ b/.github/workflows/freebsd.yml @@ -2,6 +2,10 @@ name: freebsd on: [push, pull_request] +concurrency: + group: ${{ github.workflow }}-freebsd-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: clang: # Run actions in a FreeBSD VM on the macos-12 runner diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml new file mode 100644 index 00000000..94dc42d2 --- /dev/null +++ b/.github/workflows/labeler.yml @@ -0,0 +1,19 @@ +name: "Issue Labeler" +on: + issues: + types: [opened, edited] + +permissions: + issues: write + contents: read + +jobs: + triage: + runs-on: ubuntu-latest + steps: + - uses: github/issue-labeler@v3.4 + with: + configuration-path: .github/labeler.yml + enable-versioned-regex: 0 + include-title: 1 + repo-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index d97612d5..dc6b7ede 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -2,6 +2,10 @@ name: linux on: [push, pull_request] +concurrency: + group: ${{ github.workflow }}-linux-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: build: strategy: @@ -14,9 +18,6 @@ jobs: - opensuse - gentoo cpp_std: [c++20] - include: - - distro: fedora - cpp_std: c++20 runs-on: ubuntu-latest container: diff --git a/Dockerfiles/debian b/Dockerfiles/debian index 7536fdfd..0745935e 100644 --- a/Dockerfiles/debian +++ b/Dockerfiles/debian @@ -3,11 +3,47 @@ FROM debian:sid RUN apt update && \ - apt install -y \ - build-essential meson ninja-build git pkg-config libinput10 libpugixml-dev libinput-dev \ - wayland-protocols libwayland-client0 libwayland-cursor0 libwayland-dev \ - libegl1-mesa-dev libgles2-mesa-dev libgbm-dev libxkbcommon-dev libudev-dev libpixman-1-dev \ - libgtkmm-3.0-dev libjsoncpp-dev scdoc libdbusmenu-gtk3-dev libnl-3-dev libnl-genl-3-dev \ - libpulse-dev libmpdclient-dev gobject-introspection libgirepository1.0-dev libxkbcommon-dev \ - libxkbregistry-dev libxkbregistry0 libplayerctl-dev sudo python3-venv python3-pip && \ - apt clean + apt install --no-install-recommends --no-install-suggests -y \ + build-essential \ + catch2 \ + cmake \ + git \ + gobject-introspection \ + libdbusmenu-gtk3-dev \ + libegl1-mesa-dev \ + libfmt-dev \ + libgbm-dev \ + libgirepository1.0-dev \ + libgles2-mesa-dev \ + libgtk-layer-shell-dev \ + libgtkmm-3.0-dev \ + libhowardhinnant-date-dev \ + libiniparser-dev \ + libinput-dev \ + libjack-jackd2-dev \ + libjsoncpp-dev \ + libmpdclient-dev \ + libnl-3-dev \ + libnl-genl-3-dev \ + libpixman-1-dev \ + libplayerctl-dev \ + libpugixml-dev \ + libpulse-dev \ + libsndio-dev \ + libspdlog-dev \ + libudev-dev \ + libupower-glib-dev \ + libwayland-dev \ + libwireplumber-0.4-dev \ + libxkbcommon-dev \ + libxkbregistry-dev \ + locales \ + meson \ + ninja-build \ + pkg-config \ + python3-pip \ + python3-venv \ + scdoc \ + sudo \ + wayland-protocols \ + && apt clean diff --git a/include/AModule.hpp b/include/AModule.hpp index df0165cf..c15efb00 100644 --- a/include/AModule.hpp +++ b/include/AModule.hpp @@ -11,6 +11,8 @@ namespace waybar { class AModule : public IModule { public: + static constexpr const char *MODULE_CLASS = "module"; + virtual ~AModule(); auto update() -> void override; virtual auto refresh(int) -> void{}; diff --git a/include/bar.hpp b/include/bar.hpp index d2cbffa0..6dc3c03d 100644 --- a/include/bar.hpp +++ b/include/bar.hpp @@ -53,34 +53,18 @@ class BarIpcClient; } #endif // HAVE_SWAY -class BarSurface { - protected: - BarSurface() = default; - - public: - virtual void setExclusiveZone(bool enable) = 0; - virtual void setLayer(bar_layer layer) = 0; - virtual void setMargins(const struct bar_margins &margins) = 0; - virtual void setPassThrough(bool enable) = 0; - virtual void setPosition(const std::string_view &position) = 0; - virtual void setSize(uint32_t width, uint32_t height) = 0; - virtual void commit(){}; - - virtual ~BarSurface() = default; -}; - class Bar { public: - using bar_mode_map = std::map; + using bar_mode_map = std::map; static const bar_mode_map PRESET_MODES; - static const std::string_view MODE_DEFAULT; - static const std::string_view MODE_INVISIBLE; + static const std::string MODE_DEFAULT; + static const std::string MODE_INVISIBLE; Bar(struct waybar_output *w_output, const Json::Value &); Bar(const Bar &) = delete; ~Bar(); - void setMode(const std::string_view &); + void setMode(const std::string &mode); void setVisible(bool visible); void toggle(); void handleSignal(int); @@ -89,8 +73,9 @@ class Bar { Json::Value config; struct wl_surface *surface; bool visible = true; - bool vertical = false; Gtk::Window window; + Gtk::Orientation orientation = Gtk::ORIENTATION_HORIZONTAL; + Gtk::PositionType position = Gtk::POS_TOP; int x_global; int y_global; @@ -106,6 +91,8 @@ class Bar { void setupAltFormatKeyForModule(const std::string &module_name); void setupAltFormatKeyForModuleList(const char *module_list_name); void setMode(const bar_mode &); + void setPassThrough(bool passthrough); + void setPosition(Gtk::PositionType position); void onConfigure(GdkEventConfigure *ev); void configureGlobalOffset(int width, int height); void onOutputGeometryChanged(); @@ -115,8 +102,9 @@ class Bar { std::string last_mode_{MODE_DEFAULT}; struct bar_margins margins_; + uint32_t width_, height_; + bool passthrough_; - std::unique_ptr surface_impl_; Gtk::Box left_; Gtk::Box center_; Gtk::Box right_; diff --git a/include/client.hpp b/include/client.hpp index 641ee6a7..0e68f002 100644 --- a/include/client.hpp +++ b/include/client.hpp @@ -10,7 +10,6 @@ #include "util/css_reload_helper.hpp" #include "util/portal.hpp" -struct zwlr_layer_shell_v1; struct zwp_idle_inhibitor_v1; struct zwp_idle_inhibit_manager_v1; @@ -26,7 +25,6 @@ class Client { Glib::RefPtr gdk_display; struct wl_display *wl_display = nullptr; struct wl_registry *registry = nullptr; - struct zwlr_layer_shell_v1 *layer_shell = nullptr; struct zxdg_output_manager_v1 *xdg_output_manager = nullptr; struct zwp_idle_inhibit_manager_v1 *idle_inhibit_manager = nullptr; std::vector> bars; diff --git a/include/factory.hpp b/include/factory.hpp index 9ce680d7..f805aab5 100644 --- a/include/factory.hpp +++ b/include/factory.hpp @@ -1,110 +1,13 @@ #pragma once #include -#if defined(HAVE_CHRONO_TIMEZONES) || defined(HAVE_LIBDATE) -#include "modules/clock.hpp" -#else -#include "modules/simpleclock.hpp" -#endif -#ifdef HAVE_SWAY -#include "modules/sway/language.hpp" -#include "modules/sway/mode.hpp" -#include "modules/sway/scratchpad.hpp" -#include "modules/sway/window.hpp" -#include "modules/sway/workspaces.hpp" -#endif -#ifdef HAVE_WLR -#include "modules/wlr/taskbar.hpp" -#include "modules/wlr/workspace_manager.hpp" -#endif -#ifdef HAVE_RIVER -#include "modules/river/layout.hpp" -#include "modules/river/mode.hpp" -#include "modules/river/tags.hpp" -#include "modules/river/window.hpp" -#endif -#ifdef HAVE_DWL -#include "modules/dwl/tags.hpp" -#endif -#ifdef HAVE_HYPRLAND -#include "modules/hyprland/backend.hpp" -#include "modules/hyprland/language.hpp" -#include "modules/hyprland/submap.hpp" -#include "modules/hyprland/window.hpp" -#include "modules/hyprland/workspaces.hpp" -#endif -#if defined(__FreeBSD__) || (defined(__linux__) && !defined(NO_FILESYSTEM)) -#include "modules/battery.hpp" -#endif -#if defined(HAVE_CPU_LINUX) || defined(HAVE_CPU_BSD) -#include "modules/cpu.hpp" -#include "modules/cpu_frequency.hpp" -#include "modules/cpu_usage.hpp" -#include "modules/load.hpp" -#endif -#include "modules/idle_inhibitor.hpp" -#if defined(HAVE_MEMORY_LINUX) || defined(HAVE_MEMORY_BSD) -#include "modules/memory.hpp" -#endif -#include "modules/disk.hpp" -#ifdef HAVE_DBUSMENU -#include "modules/sni/tray.hpp" -#endif -#ifdef HAVE_MPRIS -#include "modules/mpris/mpris.hpp" -#endif -#ifdef HAVE_LIBNL -#include "modules/network.hpp" -#endif -#ifdef HAVE_LIBUDEV -#include "modules/backlight.hpp" -#endif -#ifdef HAVE_LIBEVDEV -#include "modules/keyboard_state.hpp" -#endif -#ifdef HAVE_GAMEMODE -#include "modules/gamemode.hpp" -#endif -#ifdef HAVE_UPOWER -#include "modules/upower/upower.hpp" -#endif -#ifdef HAVE_PIPEWIRE -#include "modules/privacy/privacy.hpp" -#endif -#ifdef HAVE_LIBPULSE -#include "modules/pulseaudio.hpp" -#endif -#ifdef HAVE_LIBMPDCLIENT -#include "modules/mpd/mpd.hpp" -#endif -#ifdef HAVE_LIBSNDIO -#include "modules/sndio.hpp" -#endif -#ifdef HAVE_GIO_UNIX -#include "modules/bluetooth.hpp" -#include "modules/inhibitor.hpp" -#endif -#ifdef HAVE_LIBJACK -#include "modules/jack.hpp" -#endif -#ifdef HAVE_LIBWIREPLUMBER -#include "modules/wireplumber.hpp" -#endif -#ifdef HAVE_LIBCAVA -#include "modules/cava.hpp" -#endif -#ifdef HAVE_SYSTEMD_MONITOR -#include "modules/systemd_failed_units.hpp" -#endif -#include "bar.hpp" -#include "modules/cffi.hpp" -#include "modules/custom.hpp" -#include "modules/image.hpp" -#include "modules/temperature.hpp" -#include "modules/user.hpp" + +#include namespace waybar { +class Bar; + class Factory { public: Factory(const Bar& bar, const Json::Value& config); diff --git a/include/modules/battery.hpp b/include/modules/battery.hpp index bbdd0eed..7955e598 100644 --- a/include/modules/battery.hpp +++ b/include/modules/battery.hpp @@ -1,11 +1,8 @@ #pragma once -#ifdef FILESYSTEM_EXPERIMENTAL -#include -#else -#include -#endif #include + +#include #if defined(__linux__) #include #endif @@ -21,11 +18,7 @@ namespace waybar::modules { -#ifdef FILESYSTEM_EXPERIMENTAL -namespace fs = std::experimental::filesystem; -#else namespace fs = std::filesystem; -#endif class Battery : public ALabel { public: diff --git a/include/modules/clock.hpp b/include/modules/clock.hpp index d6aabaa0..8b597c4e 100644 --- a/include/modules/clock.hpp +++ b/include/modules/clock.hpp @@ -8,6 +8,7 @@ namespace waybar::modules { const std::string kCldPlaceholder{"calendar"}; const std::string kTZPlaceholder{"tz_list"}; +const std::string kOrdPlaceholder{"ordinal_date"}; enum class CldMode { MONTH, YEAR }; enum class WS { LEFT, RIGHT, HIDDEN }; @@ -57,6 +58,11 @@ class Clock final : public ALabel { std::string tzText_{""}; // time zones text to print util::SleeperThread thread_; + // ordinal date in tooltip + const bool ordInTooltip_; + std::string ordText_{""}; + auto get_ordinal_date(const year_month_day& today) -> std::string; + auto getTZtext(sys_seconds now) -> std::string; auto first_day_of_week() -> weekday; // Module actions diff --git a/include/modules/hyprland/backend.hpp b/include/modules/hyprland/backend.hpp index d197df3a..9ce0ec33 100644 --- a/include/modules/hyprland/backend.hpp +++ b/include/modules/hyprland/backend.hpp @@ -20,8 +20,8 @@ class IPC { public: IPC() { startIPC(); } - void registerForIPC(const std::string&, EventHandler*); - void unregisterForIPC(EventHandler*); + void registerForIPC(const std::string& ev, EventHandler* ev_handler); + void unregisterForIPC(EventHandler* handler); static std::string getSocket1Reply(const std::string& rq); Json::Value getSocket1JsonReply(const std::string& rq); @@ -30,9 +30,9 @@ class IPC { void startIPC(); void parseIPC(const std::string&); - std::mutex m_callbackMutex; - util::JsonParser m_parser; - std::list> m_callbacks; + std::mutex callbackMutex_; + util::JsonParser parser_; + std::list> callbacks_; }; inline std::unique_ptr gIPC; diff --git a/include/modules/hyprland/language.hpp b/include/modules/hyprland/language.hpp index eb220609..47a4d69c 100644 --- a/include/modules/hyprland/language.hpp +++ b/include/modules/hyprland/language.hpp @@ -30,7 +30,7 @@ class Language : public waybar::ALabel, public EventHandler { std::string short_description; }; - auto getLayout(const std::string&) -> Layout; + static auto getLayout(const std::string&) -> Layout; std::mutex mutex_; const Bar& bar_; diff --git a/include/modules/hyprland/submap.hpp b/include/modules/hyprland/submap.hpp index b70d236a..ce980f36 100644 --- a/include/modules/hyprland/submap.hpp +++ b/include/modules/hyprland/submap.hpp @@ -14,13 +14,13 @@ namespace waybar::modules::hyprland { class Submap : public waybar::ALabel, public EventHandler { public: Submap(const std::string&, const waybar::Bar&, const Json::Value&); - virtual ~Submap(); + ~Submap() override; auto update() -> void override; private: auto parseConfig(const Json::Value&) -> void; - void onEvent(const std::string&) override; + void onEvent(const std::string& ev) override; std::mutex mutex_; const Bar& bar_; diff --git a/include/modules/hyprland/window.hpp b/include/modules/hyprland/window.hpp index ea4d83b2..593e3422 100644 --- a/include/modules/hyprland/window.hpp +++ b/include/modules/hyprland/window.hpp @@ -25,7 +25,7 @@ class Window : public waybar::AAppIconLabel, public EventHandler { std::string last_window; std::string last_window_title; - static auto parse(const Json::Value&) -> Workspace; + static auto parse(const Json::Value& value) -> Workspace; }; struct WindowData { @@ -41,22 +41,22 @@ class Window : public waybar::AAppIconLabel, public EventHandler { static auto parse(const Json::Value&) -> WindowData; }; - auto getActiveWorkspace(const std::string&) -> Workspace; - auto getActiveWorkspace() -> Workspace; - void onEvent(const std::string&) override; + static auto getActiveWorkspace(const std::string&) -> Workspace; + static auto getActiveWorkspace() -> Workspace; + void onEvent(const std::string& ev) override; void queryActiveWorkspace(); void setClass(const std::string&, bool enable); - bool separate_outputs; + bool separateOutputs_; std::mutex mutex_; const Bar& bar_; util::JsonParser parser_; - WindowData window_data_; + WindowData windowData_; Workspace workspace_; - std::string solo_class_; - std::string last_solo_class_; + std::string soloClass_; + std::string lastSoloClass_; bool solo_; - bool all_floating_; + bool allFloating_; bool swallowing_; bool fullscreen_; }; diff --git a/include/modules/hyprland/workspaces.hpp b/include/modules/hyprland/workspaces.hpp index d2006fcc..8d46b1a1 100644 --- a/include/modules/hyprland/workspaces.hpp +++ b/include/modules/hyprland/workspaces.hpp @@ -68,7 +68,7 @@ class Workspace { int id() const { return m_id; }; std::string name() const { return m_name; }; std::string output() const { return m_output; }; - bool isActive() const { return m_active; }; + bool isActive() const { return m_isActive; }; bool isSpecial() const { return m_isSpecial; }; bool isPersistent() const { return m_isPersistent; }; bool isVisible() const { return m_isVisible; }; @@ -76,7 +76,7 @@ class Workspace { bool isUrgent() const { return m_isUrgent; }; bool handleClicked(GdkEventButton* bt) const; - void setActive(bool value = true) { m_active = value; }; + void setActive(bool value = true) { m_isActive = value; }; void setPersistent(bool value = true) { m_isPersistent = value; }; void setUrgent(bool value = true) { m_isUrgent = value; }; void setVisible(bool value = true) { m_isVisible = value; }; @@ -99,7 +99,7 @@ class Workspace { std::string m_name; std::string m_output; uint m_windows; - bool m_active = false; + bool m_isActive = false; bool m_isSpecial = false; bool m_isPersistent = false; bool m_isUrgent = false; @@ -135,8 +135,8 @@ class Workspaces : public AModule, public EventHandler { void onEvent(const std::string& e) override; void updateWindowCount(); void sortWorkspaces(); - void createWorkspace(Json::Value const& workspace_data, - Json::Value const& clients_data = Json::Value::nullRef); + void createWorkspace(Json::Value const& workspaceData, + Json::Value const& clientsData = Json::Value::nullRef); void removeWorkspace(std::string const& name); void setUrgentWorkspace(std::string const& windowaddress); void parseConfig(const Json::Value& config); @@ -144,6 +144,7 @@ class Workspaces : public AModule, public EventHandler { // workspace events void onWorkspaceActivated(std::string const& payload); + void onSpecialWorkspaceActivated(std::string const& payload); void onWorkspaceDestroyed(std::string const& payload); void onWorkspaceCreated(std::string const& workspaceName, Json::Value const& clientsData = Json::Value::nullRef); @@ -160,16 +161,24 @@ class Workspaces : public AModule, public EventHandler { void onWindowTitleEvent(std::string const& payload); + void onConfigReloaded(); + int windowRewritePriorityFunction(std::string const& window_rule); void doUpdate(); void extendOrphans(int workspaceId, Json::Value const& clientsJson); - void registerOrphanWindow(WindowCreationPayload create_window_paylod); + void registerOrphanWindow(WindowCreationPayload create_window_payload); + + void initializeWorkspaces(); + void setCurrentMonitorId(); + void loadPersistentWorkspacesFromConfig(Json::Value const& clientsJson); + void loadPersistentWorkspacesFromWorkspaceRules(const Json::Value& clientsJson); bool m_allOutputs = false; bool m_showSpecial = false; bool m_activeOnly = false; + Json::Value m_persistentWorkspaceConfig; // Map for windows stored in workspaces not present in the current bar. // This happens when the user has multiple monitors (hence, multiple bars) @@ -184,11 +193,6 @@ class Workspaces : public AModule, public EventHandler { {"NUMBER", SortMethod::NUMBER}, {"DEFAULT", SortMethod::DEFAULT}}; - void fillPersistentWorkspaces(); - void createPersistentWorkspaces(); - std::vector m_persistentWorkspacesToCreate; - bool m_persistentCreated = false; - std::string m_format; std::map m_iconsMap; @@ -199,6 +203,7 @@ class Workspaces : public AModule, public EventHandler { bool m_withIcon; uint64_t m_monitorId; std::string m_activeWorkspaceName; + std::string m_activeSpecialWorkspaceName; std::vector> m_workspaces; std::vector> m_workspacesToCreate; std::vector m_workspacesToRemove; diff --git a/include/modules/sway/language.hpp b/include/modules/sway/language.hpp index 3e9519f5..ea58c4f0 100644 --- a/include/modules/sway/language.hpp +++ b/include/modules/sway/language.hpp @@ -56,6 +56,7 @@ class Language : public ALabel, public sigc::trackable { Layout layout_; std::string tooltip_format_ = ""; std::map layouts_map_; + bool hide_single_; bool is_variant_displayed; std::byte displayed_short_flag = static_cast(DispayedShortFlag::None); diff --git a/include/modules/sway/workspaces.hpp b/include/modules/sway/workspaces.hpp index 0efffe64..4258252a 100644 --- a/include/modules/sway/workspaces.hpp +++ b/include/modules/sway/workspaces.hpp @@ -12,6 +12,7 @@ #include "client.hpp" #include "modules/sway/ipc/client.hpp" #include "util/json.hpp" +#include "util/regex_collection.hpp" namespace waybar::modules::sway { @@ -27,10 +28,13 @@ class Workspaces : public AModule, public sigc::trackable { R"(workspace {} "{}"; move workspace to output "{}"; workspace {} "{}")"; static int convertWorkspaceNameToNum(std::string name); + static int windowRewritePriorityFunction(std::string const& window_rule); void onCmd(const struct Ipc::ipc_response&); void onEvent(const struct Ipc::ipc_response&); bool filterButtons(); + static bool hasFlag(const Json::Value&, const std::string&); + void updateWindows(const Json::Value&, std::string&); Gtk::Button& addButton(const Json::Value&); void onButtonReady(const Json::Value&, Gtk::Button&); std::string getIcon(const std::string&, const Json::Value&); @@ -44,6 +48,9 @@ class Workspaces : public AModule, public sigc::trackable { std::vector high_priority_named_; std::vector workspaces_order_; Gtk::Box box_; + std::string m_formatWindowSeperator; + std::string m_windowRewriteDefault; + util::RegexCollection m_windowRewriteRules; util::JsonParser parser_; std::unordered_map buttons_; std::mutex mutex_; diff --git a/include/util/css_reload_helper.hpp b/include/util/css_reload_helper.hpp index 4826fc31..032b2382 100644 --- a/include/util/css_reload_helper.hpp +++ b/include/util/css_reload_helper.hpp @@ -16,6 +16,8 @@ class CssReloadHelper { public: CssReloadHelper(std::string cssFile, std::function callback); + virtual ~CssReloadHelper() = default; + virtual void monitorChanges(); protected: diff --git a/include/util/date.hpp b/include/util/date.hpp index 962c810b..2431b766 100644 --- a/include/util/date.hpp +++ b/include/util/date.hpp @@ -30,10 +30,6 @@ template inline auto format(const std::locale& loc, const char* spec, const T& arg) { return date::format(loc, std::regex_replace(spec, std::regex("\\{:L|\\}"), ""), arg); } - -constexpr decltype(auto) operator""d(unsigned long long d) noexcept { - return date::operator""_d(d); // very verbose, but it works -} #endif } // namespace date diff --git a/man/waybar-clock.5.scd b/man/waybar-clock.5.scd index fc079338..e8ef7bed 100644 --- a/man/waybar-clock.5.scd +++ b/man/waybar-clock.5.scd @@ -157,6 +157,7 @@ View all valid format options in *strftime(3)* or have a look https://en.cpprefe - *{calendar}*: Current month calendar - *{tz_list}*: List of time in the rest timezones, if more than one timezone is set in the config +- *{ordinal_date}*: The current day in (English) ordinal form, e.g. 21st # EXAMPLES diff --git a/man/waybar-custom.5.scd b/man/waybar-custom.5.scd index 67e4c89c..4c219031 100644 --- a/man/waybar-custom.5.scd +++ b/man/waybar-custom.5.scd @@ -107,6 +107,11 @@ Addressed by *custom/* default: true ++ Option to disable tooltip on hover. +*tooltip-format*: ++ + typeof: string ++ + The tooltip format. If specified, overrides any tooltip output from the script in *exec*. ++ + Uses the same format replacements as *format*. + *escape*: ++ typeof: bool ++ default: false ++ diff --git a/man/waybar-hyprland-workspaces.5.scd b/man/waybar-hyprland-workspaces.5.scd index fb7b6e4d..12c1fe39 100644 --- a/man/waybar-hyprland-workspaces.5.scd +++ b/man/waybar-hyprland-workspaces.5.scd @@ -139,6 +139,7 @@ Additional to workspace name matching, the following *format-icons* can be set. } ``` +``` "hyprland/workspaces": { // Formatting omitted for brevity "ignore-workspaces": [ diff --git a/man/waybar-sway-language.5.scd b/man/waybar-sway-language.5.scd index c257ed75..1c62fd95 100644 --- a/man/waybar-sway-language.5.scd +++ b/man/waybar-sway-language.5.scd @@ -17,6 +17,11 @@ Addressed by *sway/language* default: {} ++ The format, how layout should be displayed. +*hide-single-layout*: ++ + typeof: bool ++ + default: false ++ + Defines visibility of the module if a single layout is configured + *tooltip-format*: ++ typeof: string ++ default: {} ++ diff --git a/man/waybar-sway-workspaces.5.scd b/man/waybar-sway-workspaces.5.scd index cdb653f9..3343b8d5 100644 --- a/man/waybar-sway-workspaces.5.scd +++ b/man/waybar-sway-workspaces.5.scd @@ -82,6 +82,23 @@ warp-on-scroll: ++ default: true ++ If set to false, you can scroll to cycle through workspaces without mouse warping being enabled. If set to true this behaviour is disabled. +*window-rewrite*: ++ + typeof: object ++ + Regex rules to map window class to an icon or preferred method of representation for a workspace's window. + Keys are the rules, while the values are the methods of representation. + Rules may specify `class<...>`, `title<...>`, or both in order to fine-tune the matching. + +*window-rewrite-default*: + typeof: string ++ + default: "?" ++ + The default method of representation for a workspace's window. This will be used for windows whose classes do not match any of the rules in *window-rewrite*. + +*format-window-separator*: ++ + typeof: string ++ + default: " " ++ + The separator to be used between windows in a workspace. + + # FORMAT REPLACEMENTS *{value}*: Name of the workspace, as defined by sway. @@ -94,6 +111,8 @@ warp-on-scroll: ++ *{output}*: Output where the workspace is located. +*{windows}*: Result from window-rewrite + # ICONS Additional to workspace name matching, the following *format-icons* can be set. @@ -143,6 +162,19 @@ n.b.: the list of outputs can be obtained from command line using *swaymsg -t ge } ``` +``` +"sway/workspaces": { + "format": "{name} {windows}", + "format-window-separator": " | ", + "window-rewrite-default": "{name}", + "window-format": "{name}", + "window-rewrite": { + "class": "", + "class": "k", + } +} +``` + # Style - *#workspaces button* diff --git a/man/waybar.5.scd.in b/man/waybar.5.scd.in index 17324d69..628bbf61 100644 --- a/man/waybar.5.scd.in +++ b/man/waybar.5.scd.in @@ -108,12 +108,6 @@ Also, a minimal example configuration can be found at the bottom of this man pag Option to pass any pointer events to the window under the bar. Intended to be used with either *top* or *overlay* layers and without exclusive zone. -*gtk-layer-shell* ++ - typeof: bool ++ - default: true ++ - Option to disable the use of gtk-layer-shell for popups. - Only functional if compiled with gtk-layer-shell support. - *ipc* ++ typeof: bool ++ default: false ++ diff --git a/meson.build b/meson.build index d7549731..46ff9926 100644 --- a/meson.build +++ b/meson.build @@ -2,7 +2,7 @@ project( 'waybar', 'cpp', 'c', version: '0.9.24', license: 'MIT', - meson_version: '>= 0.50.0', + meson_version: '>= 0.59.0', default_options : [ 'cpp_std=c++20', 'buildtype=release', @@ -22,8 +22,6 @@ endif if compiler.has_link_argument('-lc++fs') cpp_link_args += ['-lc++fs'] -elif compiler.has_link_argument('-lc++experimental') - cpp_link_args += ['-lc++experimental'] elif compiler.has_link_argument('-lstdc++fs') cpp_link_args += ['-lstdc++fs'] endif @@ -33,10 +31,10 @@ git = find_program('git', native: true, required: false) if not git.found() add_project_arguments('-DVERSION="@0@"'.format(meson.project_version()), language: 'cpp') else - git_path = run_command([git.path(), 'rev-parse', '--show-toplevel']).stdout().strip() - if meson.source_root() == git_path - git_commit_hash = run_command([git.path(), 'describe', '--always', '--tags']).stdout().strip() - git_branch = run_command([git.path(), 'rev-parse', '--abbrev-ref', 'HEAD']).stdout().strip() + git_path = run_command(git, 'rev-parse', '--show-toplevel', check: false).stdout().strip() + if meson.project_source_root() == git_path + git_commit_hash = run_command(git, 'describe', '--always', '--tags', check: false).stdout().strip() + git_branch = run_command(git, 'rev-parse', '--abbrev-ref', 'HEAD', check: false).stdout().strip() version = '"@0@ (branch \'@1@\')"'.format(git_commit_hash, git_branch) add_project_arguments('-DVERSION=@0@'.format(version), language: 'cpp') else @@ -44,15 +42,6 @@ else endif endif -if not compiler.has_header('filesystem') - if compiler.has_header('experimental/filesystem') - add_project_arguments('-DFILESYSTEM_EXPERIMENTAL', language: 'cpp') - else - add_project_arguments('-DNO_FILESYSTEM', language: 'cpp') - warning('No filesystem header found, some modules may not work') - endif -endif - code = ''' #include #include @@ -86,10 +75,7 @@ wayland_cursor = dependency('wayland-cursor') wayland_protos = dependency('wayland-protocols') gtkmm = dependency('gtkmm-3.0', version : ['>=3.22.0']) dbusmenu_gtk = dependency('dbusmenu-gtk3-0.4', required: get_option('dbusmenu-gtk')) -giounix = dependency('gio-unix-2.0', required: (get_option('dbusmenu-gtk').enabled() or - get_option('logind').enabled() or - get_option('upower_glib').enabled() or - get_option('mpris').enabled())) +giounix = dependency('gio-unix-2.0') jsoncpp = dependency('jsoncpp', version : ['>=1.9.2'], fallback : ['jsoncpp', 'jsoncpp_dep']) sigcpp = dependency('sigc++-2.0') libinotify = dependency('libinotify', required: false) @@ -120,9 +106,9 @@ if libsndio.found() endif endif -gtk_layer_shell = dependency('gtk-layer-shell-0', - required: get_option('gtk-layer-shell'), - fallback : ['gtk-layer-shell', 'gtk_layer_shell_dep']) +gtk_layer_shell = dependency('gtk-layer-shell-0', version: ['>=0.6.0'], + default_options: ['introspection=false', 'vapi=false'], + fallback: ['gtk-layer-shell', 'gtk_layer_shell']) systemd = dependency('systemd', required: get_option('systemd')) cpp_lib_chrono = compiler.compute_int('__cpp_lib_chrono', prefix : '#include ') @@ -157,10 +143,10 @@ sysconfdir = get_option('sysconfdir') conf_data = configuration_data() conf_data.set('prefix', prefix) -add_project_arguments('-DSYSCONFDIR="/@0@"'.format(join_paths(prefix, sysconfdir)), language : 'cpp') +add_project_arguments('-DSYSCONFDIR="@0@"'.format(prefix / sysconfdir), language : 'cpp') if systemd.found() - user_units_dir = systemd.get_pkgconfig_variable('systemduserunitdir') + user_units_dir = systemd.get_variable(pkgconfig: 'systemduserunitdir') configure_file( configuration: conf_data, @@ -200,6 +186,15 @@ src_files = files( 'src/util/css_reload_helper.cpp' ) +man_files = files( + 'man/waybar-custom.5.scd', + 'man/waybar-disk.5.scd', + 'man/waybar-idle-inhibitor.5.scd', + 'man/waybar-image.5.scd', + 'man/waybar-states.5.scd', + 'man/waybar-temperature.5.scd', +) + inc_dirs = ['include'] if is_linux @@ -208,6 +203,7 @@ if is_linux add_project_arguments('-DHAVE_SYSTEMD_MONITOR', language: 'cpp') src_files += files( 'src/modules/battery.cpp', + 'src/modules/bluetooth.cpp', 'src/modules/cffi.cpp', 'src/modules/cpu.cpp', 'src/modules/cpu_frequency/common.cpp', @@ -218,6 +214,14 @@ if is_linux 'src/modules/memory/linux.cpp', 'src/modules/systemd_failed_units.cpp', ) + man_files += files( + 'man/waybar-battery.5.scd', + 'man/waybar-bluetooth.5.scd', + 'man/waybar-cffi.5.scd', + 'man/waybar-cpu.5.scd', + 'man/waybar-memory.5.scd', + 'man/waybar-systemd-failed-units.5.scd', + ) elif is_dragonfly or is_freebsd or is_netbsd or is_openbsd add_project_arguments('-DHAVE_CPU_BSD', language: 'cpp') add_project_arguments('-DHAVE_MEMORY_BSD', language: 'cpp') @@ -231,97 +235,149 @@ elif is_dragonfly or is_freebsd or is_netbsd or is_openbsd 'src/modules/memory/bsd.cpp', 'src/modules/memory/common.cpp', ) + man_files += files( + 'man/waybar-cffi.5.scd', + 'man/waybar-cpu.5.scd', + 'man/waybar-memory.5.scd', + ) if is_freebsd - src_files += files( - 'src/modules/battery.cpp', - ) + src_files += files('src/modules/battery.cpp') + man_files += files('man/waybar-battery.5.scd') endif endif -add_project_arguments('-DHAVE_SWAY', language: 'cpp') -src_files += [ - 'src/modules/sway/ipc/client.cpp', - 'src/modules/sway/bar.cpp', - 'src/modules/sway/mode.cpp', - 'src/modules/sway/language.cpp', - 'src/modules/sway/window.cpp', - 'src/modules/sway/workspaces.cpp', - 'src/modules/sway/scratchpad.cpp' -] +if true + add_project_arguments('-DHAVE_SWAY', language: 'cpp') + src_files += files( + 'src/modules/sway/ipc/client.cpp', + 'src/modules/sway/bar.cpp', + 'src/modules/sway/mode.cpp', + 'src/modules/sway/language.cpp', + 'src/modules/sway/window.cpp', + 'src/modules/sway/workspaces.cpp', + 'src/modules/sway/scratchpad.cpp' + ) + man_files += files( + 'man/waybar-sway-language.5.scd', + 'man/waybar-sway-mode.5.scd', + 'man/waybar-sway-scratchpad.5.scd', + 'man/waybar-sway-window.5.scd', + 'man/waybar-sway-workspaces.5.scd', + ) +endif if true - add_project_arguments('-DHAVE_WLR', language: 'cpp') - src_files += 'src/modules/wlr/taskbar.cpp' - src_files += 'src/modules/wlr/workspace_manager.cpp' - src_files += 'src/modules/wlr/workspace_manager_binding.cpp' + add_project_arguments('-DHAVE_WLR_TASKBAR', language: 'cpp') + src_files += files('src/modules/wlr/taskbar.cpp') + man_files += files('man/waybar-wlr-taskbar.5.scd') endif if true add_project_arguments('-DHAVE_RIVER', language: 'cpp') - src_files += 'src/modules/river/mode.cpp' - src_files += 'src/modules/river/tags.cpp' - src_files += 'src/modules/river/window.cpp' - src_files += 'src/modules/river/layout.cpp' + src_files += files( + 'src/modules/river/layout.cpp', + 'src/modules/river/mode.cpp', + 'src/modules/river/tags.cpp', + 'src/modules/river/window.cpp', + ) + man_files += files( + 'man/waybar-river-layout.5.scd', + 'man/waybar-river-mode.5.scd', + 'man/waybar-river-tags.5.scd', + 'man/waybar-river-window.5.scd', + ) endif if true add_project_arguments('-DHAVE_DWL', language: 'cpp') - src_files += 'src/modules/dwl/tags.cpp' + src_files += files('src/modules/dwl/tags.cpp') + man_files += files('man/waybar-dwl-tags.5.scd') endif if true add_project_arguments('-DHAVE_HYPRLAND', language: 'cpp') - src_files += 'src/modules/hyprland/backend.cpp' - src_files += 'src/modules/hyprland/window.cpp' - src_files += 'src/modules/hyprland/language.cpp' - src_files += 'src/modules/hyprland/submap.cpp' - src_files += 'src/modules/hyprland/workspaces.cpp' + src_files += files( + 'src/modules/hyprland/backend.cpp', + 'src/modules/hyprland/language.cpp', + 'src/modules/hyprland/submap.cpp', + 'src/modules/hyprland/window.cpp', + 'src/modules/hyprland/workspaces.cpp', + ) + man_files += files( + 'man/waybar-hyprland-language.5.scd', + 'man/waybar-hyprland-submap.5.scd', + 'man/waybar-hyprland-window.5.scd', + 'man/waybar-hyprland-workspaces.5.scd', + ) endif if libnl.found() and libnlgen.found() add_project_arguments('-DHAVE_LIBNL', language: 'cpp') - src_files += 'src/modules/network.cpp' + src_files += files('src/modules/network.cpp') + man_files += files('man/waybar-network.5.scd') endif -if (giounix.found() and not get_option('logind').disabled()) - add_project_arguments('-DHAVE_GAMEMODE', language: 'cpp') - src_files += 'src/modules/gamemode.cpp' +if not get_option('logind').disabled() + add_project_arguments('-DHAVE_GAMEMODE', '-DHAVE_LOGIND_INHIBITOR', language: 'cpp') + src_files += files( + 'src/modules/gamemode.cpp', + 'src/modules/inhibitor.cpp', + ) + man_files += files( + 'man/waybar-gamemode.5.scd', + 'man/waybar-inhibitor.5.scd', + ) endif -if (upower_glib.found() and giounix.found() and not get_option('logind').disabled()) +if (upower_glib.found() and not get_option('logind').disabled()) add_project_arguments('-DHAVE_UPOWER', language: 'cpp') - src_files += 'src/modules/upower/upower.cpp' - src_files += 'src/modules/upower/upower_tooltip.cpp' + src_files += files( + 'src/modules/upower/upower.cpp', + 'src/modules/upower/upower_tooltip.cpp', + ) + man_files += files('man/waybar-upower.5.scd') endif -if (pipewire.found()) +if pipewire.found() add_project_arguments('-DHAVE_PIPEWIRE', language: 'cpp') - src_files += 'src/modules/privacy/privacy.cpp' - src_files += 'src/modules/privacy/privacy_item.cpp' - src_files += 'src/util/pipewire_backend.cpp' + src_files += files( + 'src/modules/privacy/privacy.cpp', + 'src/modules/privacy/privacy_item.cpp', + 'src/util/pipewire_backend.cpp', + ) + man_files += files('man/waybar-privacy.5.scd') endif -if (playerctl.found() and giounix.found() and not get_option('logind').disabled()) +if playerctl.found() add_project_arguments('-DHAVE_MPRIS', language: 'cpp') - src_files += 'src/modules/mpris/mpris.cpp' + src_files += files('src/modules/mpris/mpris.cpp') + man_files += files('man/waybar-mpris.5.scd') endif if libpulse.found() add_project_arguments('-DHAVE_LIBPULSE', language: 'cpp') - src_files += 'src/modules/pulseaudio.cpp' - src_files += 'src/modules/pulseaudio_slider.cpp' - src_files += 'src/util/audio_backend.cpp' + src_files += files( + 'src/modules/pulseaudio.cpp', + 'src/modules/pulseaudio_slider.cpp', + 'src/util/audio_backend.cpp', + ) + man_files += files( + 'man/waybar-pulseaudio.5.scd', + 'man/waybar-pulseaudio-slider.5.scd', + ) endif if libjack.found() add_project_arguments('-DHAVE_LIBJACK', language: 'cpp') - src_files += 'src/modules/jack.cpp' + src_files += files('src/modules/jack.cpp') + man_files += files('man/waybar-jack.5.scd') endif if libwireplumber.found() add_project_arguments('-DHAVE_LIBWIREPLUMBER', language: 'cpp') - src_files += 'src/modules/wireplumber.cpp' + src_files += files('src/modules/wireplumber.cpp') + man_files += files('man/waybar-wireplumber.5.scd') endif if dbusmenu_gtk.found() @@ -332,40 +388,46 @@ if dbusmenu_gtk.found() 'src/modules/sni/host.cpp', 'src/modules/sni/item.cpp' ) + man_files += files( + 'man/waybar-tray.5.scd', + ) endif if libudev.found() and (is_linux or libepoll.found()) add_project_arguments('-DHAVE_LIBUDEV', language: 'cpp') - src_files += 'src/modules/backlight.cpp' - src_files += 'src/modules/backlight_slider.cpp' - src_files += 'src/util/backlight_backend.cpp' + src_files += files( + 'src/modules/backlight.cpp', + 'src/modules/backlight_slider.cpp', + 'src/util/backlight_backend.cpp', + ) + man_files += files( + 'man/waybar-backlight.5.scd', + 'man/waybar-backlight-slider.5.scd', + ) endif if libevdev.found() and (is_linux or libepoll.found()) and libinput.found() and (is_linux or libinotify.found()) add_project_arguments('-DHAVE_LIBEVDEV', language: 'cpp') add_project_arguments('-DHAVE_LIBINPUT', language: 'cpp') - src_files += 'src/modules/keyboard_state.cpp' + src_files += files('src/modules/keyboard_state.cpp') + man_files += files('man/waybar-keyboard-state.5.scd') endif if libmpdclient.found() add_project_arguments('-DHAVE_LIBMPDCLIENT', language: 'cpp') - src_files += 'src/modules/mpd/mpd.cpp' - src_files += 'src/modules/mpd/state.cpp' -endif - -if gtk_layer_shell.found() - add_project_arguments('-DHAVE_GTK_LAYER_SHELL', language: 'cpp') + src_files += files( + 'src/modules/mpd/mpd.cpp', + 'src/modules/mpd/state.cpp', + ) + man_files += files( + 'man/waybar-mpd.5.scd', + ) endif if libsndio.found() add_project_arguments('-DHAVE_LIBSNDIO', language: 'cpp') - src_files += 'src/modules/sndio.cpp' -endif - -if (giounix.found() and not get_option('logind').disabled()) - add_project_arguments('-DHAVE_GIO_UNIX', language: 'cpp') - src_files += 'src/modules/inhibitor.cpp' - src_files += 'src/modules/bluetooth.cpp' + src_files += files('src/modules/sndio.cpp') + man_files += files('man/waybar-sndio.5.scd') endif if get_option('rfkill').enabled() and is_linux @@ -377,27 +439,38 @@ endif if have_chrono_timezones add_project_arguments('-DHAVE_CHRONO_TIMEZONES', language: 'cpp') - src_files += 'src/modules/clock.cpp' + src_files += files('src/modules/clock.cpp') + man_files += files('man/waybar-clock.5.scd') elif tz_dep.found() add_project_arguments('-DHAVE_LIBDATE', language: 'cpp') - src_files += 'src/modules/clock.cpp' + src_files += files('src/modules/clock.cpp') + man_files += files('man/waybar-clock.5.scd') else - src_files += 'src/modules/simpleclock.cpp' + src_files += files('src/modules/simpleclock.cpp') + man_files += files('man/waybar-clock.5.scd') endif if get_option('experimental') - add_project_arguments('-DUSE_EXPERIMENTAL', language: 'cpp') + add_project_arguments('-DHAVE_WLR_WORKSPACES', language: 'cpp') + src_files += files( + 'src/modules/wlr/workspace_manager.cpp', + 'src/modules/wlr/workspace_manager_binding.cpp', + ) + man_files += files( + 'man/waybar-wlr-workspaces.5.scd', + ) endif cava = dependency('cava', - version : '>=0.9.1', + version : '>=0.10.1', required: get_option('cava'), fallback : ['cava', 'cava_dep'], not_found_message: 'cava is not found. Building waybar without cava') if cava.found() add_project_arguments('-DHAVE_LIBCAVA', language: 'cpp') - src_files += 'src/modules/cava.cpp' + src_files += files('src/modules/cava.cpp') + man_files += files('man/waybar-cava.5.scd') endif subdir('protocol') @@ -447,77 +520,24 @@ executable( install_data( './resources/config', './resources/style.css', - install_dir: sysconfdir + '/xdg/waybar' + install_dir: sysconfdir / 'xdg/waybar' ) scdoc = dependency('scdoc', version: '>=1.9.2', native: true, required: get_option('man-pages')) if scdoc.found() - scdoc_prog = find_program(scdoc.get_pkgconfig_variable('scdoc'), native: true) - sh = find_program('sh', native: true) - - main_manpage = configure_file( + man_files += configure_file( input: 'man/waybar.5.scd.in', output: 'waybar.5.scd', configuration: { - 'sysconfdir': join_paths(prefix, sysconfdir) + 'sysconfdir': prefix / sysconfdir } ) - main_manpage_path = join_paths(meson.build_root(), '@0@'.format(main_manpage)) - + fs = import('fs') mandir = get_option('mandir') - man_files = [ - main_manpage_path, - 'waybar-backlight.5.scd', - 'waybar-backlight-slider.5.scd', - 'waybar-battery.5.scd', - 'waybar-cava.5.scd', - 'waybar-cffi.5.scd', - 'waybar-clock.5.scd', - 'waybar-cpu.5.scd', - 'waybar-custom.5.scd', - 'waybar-disk.5.scd', - 'waybar-gamemode.5.scd', - 'waybar-idle-inhibitor.5.scd', - 'waybar-image.5.scd', - 'waybar-keyboard-state.5.scd', - 'waybar-memory.5.scd', - 'waybar-mpd.5.scd', - 'waybar-mpris.5.scd', - 'waybar-network.5.scd', - 'waybar-pulseaudio.5.scd', - 'waybar-pulseaudio-slider.5.scd', - 'waybar-privacy.5.scd', - 'waybar-river-mode.5.scd', - 'waybar-river-tags.5.scd', - 'waybar-river-window.5.scd', - 'waybar-river-layout.5.scd', - 'waybar-sway-language.5.scd', - 'waybar-sway-mode.5.scd', - 'waybar-sway-scratchpad.5.scd', - 'waybar-sway-window.5.scd', - 'waybar-sway-workspaces.5.scd', - 'waybar-systemd-failed-units.5.scd', - 'waybar-temperature.5.scd', - 'waybar-tray.5.scd', - 'waybar-states.5.scd', - 'waybar-wlr-taskbar.5.scd', - 'waybar-wlr-workspaces.5.scd', - 'waybar-bluetooth.5.scd', - 'waybar-sndio.5.scd', - 'waybar-upower.5.scd', - 'waybar-wireplumber.5.scd', - 'waybar-dwl-tags.5.scd', - ] - - if (giounix.found() and not get_option('logind').disabled()) - man_files += 'waybar-inhibitor.5.scd' - endif - foreach file : man_files - path = '@0@'.format(file) - basename = path.split('/')[-1] + basename = fs.name(file) topic = basename.split('.')[-3] section = basename.split('.')[-2] @@ -525,12 +545,11 @@ if scdoc.found() custom_target( output, - # drops the 'man' if `path` is an absolute path - input: join_paths('man', path), + input: file, output: output, - command: [ - sh, '-c', '@0@ < @INPUT@ > @1@'.format(scdoc_prog.path(), output) - ], + command: scdoc.get_variable('scdoc'), + feed: true, + capture: true, install: true, install_dir: '@0@/man@1@'.format(mandir, section) ) @@ -539,7 +558,7 @@ endif catch2 = dependency( 'catch2', - version: '>=3.5.1', + default_options: [ 'tests=false' ], fallback: ['catch2', 'catch2_dep'], required: get_option('tests'), ) @@ -555,7 +574,7 @@ if clangtidy.found() command: [ clangtidy, '-checks=*,-fuchsia-default-arguments', - '-p', meson.build_root() + '-p', meson.project_build_root() ] + src_files) endif diff --git a/meson_options.txt b/meson_options.txt index 827f9ac1..fef50839 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -11,7 +11,6 @@ option('systemd', type: 'feature', value: 'auto', description: 'Install systemd option('dbusmenu-gtk', type: 'feature', value: 'auto', description: 'Enable support for tray') option('man-pages', type: 'feature', value: 'auto', description: 'Generate and install man pages') option('mpd', type: 'feature', value: 'auto', description: 'Enable support for the Music Player Daemon') -option('gtk-layer-shell', type: 'feature', value: 'auto', description: 'Use gtk-layer-shell library for popups support') option('rfkill', type: 'feature', value: 'auto', description: 'Enable support for RFKILL') option('sndio', type: 'feature', value: 'auto', description: 'Enable support for sndio') option('logind', type: 'feature', value: 'auto', description: 'Enable support for logind') diff --git a/nix/default.nix b/nix/default.nix index e2643084..bf8f2f21 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -4,27 +4,32 @@ , version }: let - catch2_3 = { - src = pkgs.fetchFromGitHub - { - owner = "catchorg"; - repo = "Catch2"; - rev = "v3.5.1"; - hash = "sha256-OyYNUfnu6h1+MfCF8O+awQ4Usad0qrdCtdZhYgOY+Vw="; - }; + libcava = rec { + version = "0.10.1"; + src = pkgs.fetchFromGitHub { + owner = "LukashonakV"; + repo = "cava"; + rev = version; + hash = "sha256-iIYKvpOWafPJB5XhDOSIW9Mb4I3A4pcgIIPQdQYEqUw="; + }; }; in -(waybar.overrideAttrs (oldAttrs: rec { - inherit version; +(waybar.overrideAttrs ( + oldAttrs: { + inherit version; - src = lib.cleanSourceWith { - filter = name: type: type != "regular" || !lib.hasSuffix ".nix" name; - src = lib.cleanSource ../.; - }; -}) -).override { - catch2_3 = pkgs.catch2_3.overrideAttrs (oldAttrs: { - version = "3.5.1"; - src = catch2_3.src; - }); -} + src = lib.cleanSourceWith { + filter = name: type: type != "regular" || !lib.hasSuffix ".nix" name; + src = lib.cleanSource ../.; + }; + + mesonFlags = lib.remove "-Dgtk-layer-shell=enabled" oldAttrs.mesonFlags; + + postUnpack = '' + pushd "$sourceRoot" + cp -R --no-preserve=mode,ownership ${libcava.src} subprojects/cava-${libcava.version} + patchShebangs . + popd + ''; + } +)) \ No newline at end of file diff --git a/protocol/meson.build b/protocol/meson.build index e1e745a9..cd9a77b1 100644 --- a/protocol/meson.build +++ b/protocol/meson.build @@ -1,4 +1,4 @@ -wl_protocol_dir = wayland_protos.get_pkgconfig_variable('pkgdatadir') +wl_protocol_dir = wayland_protos.get_variable(pkgconfig: 'pkgdatadir') wayland_scanner = find_program('wayland-scanner') @@ -25,7 +25,6 @@ client_protocols = [ [wl_protocol_dir, 'stable/xdg-shell/xdg-shell.xml'], [wl_protocol_dir, 'unstable/xdg-output/xdg-output-unstable-v1.xml'], [wl_protocol_dir, 'unstable/idle-inhibit/idle-inhibit-unstable-v1.xml'], - ['wlr-layer-shell-unstable-v1.xml'], ['wlr-foreign-toplevel-management-unstable-v1.xml'], ['ext-workspace-unstable-v1.xml'], ['river-status-unstable-v1.xml'], @@ -44,7 +43,7 @@ endforeach gdbus_codegen = find_program('gdbus-codegen') -r = run_command(gdbus_codegen, '--body', '--output', '/dev/null') +r = run_command(gdbus_codegen, '--body', '--output', '/dev/null', check: false) if r.returncode() != 0 gdbus_code_dsnw = custom_target( 'dbus-status-notifier-watcher.[ch]', diff --git a/protocol/wlr-layer-shell-unstable-v1.xml b/protocol/wlr-layer-shell-unstable-v1.xml deleted file mode 100644 index f9a4fe05..00000000 --- a/protocol/wlr-layer-shell-unstable-v1.xml +++ /dev/null @@ -1,311 +0,0 @@ - - - - Copyright © 2017 Drew DeVault - - Permission to use, copy, modify, distribute, and sell this - software and its documentation for any purpose is hereby granted - without fee, provided that the above copyright notice appear in - all copies and that both that copyright notice and this permission - notice appear in supporting documentation, and that the name of - the copyright holders not be used in advertising or publicity - pertaining to distribution of the software without specific, - written prior permission. The copyright holders make no - representations about the suitability of this software for any - purpose. It is provided "as is" without express or implied - warranty. - - THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS - SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND - FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY - SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, - ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF - THIS SOFTWARE. - - - - - Clients can use this interface to assign the surface_layer role to - wl_surfaces. Such surfaces are assigned to a "layer" of the output and - rendered with a defined z-depth respective to each other. They may also be - anchored to the edges and corners of a screen and specify input handling - semantics. This interface should be suitable for the implementation of - many desktop shell components, and a broad number of other applications - that interact with the desktop. - - - - - Create a layer surface for an existing surface. This assigns the role of - layer_surface, or raises a protocol error if another role is already - assigned. - - Creating a layer surface from a wl_surface which has a buffer attached - or committed is a client error, and any attempts by a client to attach - or manipulate a buffer prior to the first layer_surface.configure call - must also be treated as errors. - - You may pass NULL for output to allow the compositor to decide which - output to use. Generally this will be the one that the user most - recently interacted with. - - Clients can specify a namespace that defines the purpose of the layer - surface. - - - - - - - - - - - - - - - - - These values indicate which layers a surface can be rendered in. They - are ordered by z depth, bottom-most first. Traditional shell surfaces - will typically be rendered between the bottom and top layers. - Fullscreen shell surfaces are typically rendered at the top layer. - Multiple surfaces can share a single layer, and ordering within a - single layer is undefined. - - - - - - - - - - - - - This request indicates that the client will not use the layer_shell - object any more. Objects that have been created through this instance - are not affected. - - - - - - - An interface that may be implemented by a wl_surface, for surfaces that - are designed to be rendered as a layer of a stacked desktop-like - environment. - - Layer surface state (layer, size, anchor, exclusive zone, - margin, interactivity) is double-buffered, and will be applied at the - time wl_surface.commit of the corresponding wl_surface is called. - - - - - Sets the size of the surface in surface-local coordinates. The - compositor will display the surface centered with respect to its - anchors. - - If you pass 0 for either value, the compositor will assign it and - inform you of the assignment in the configure event. You must set your - anchor to opposite edges in the dimensions you omit; not doing so is a - protocol error. Both values are 0 by default. - - Size is double-buffered, see wl_surface.commit. - - - - - - - - Requests that the compositor anchor the surface to the specified edges - and corners. If two orthogonal edges are specified (e.g. 'top' and - 'left'), then the anchor point will be the intersection of the edges - (e.g. the top left corner of the output); otherwise the anchor point - will be centered on that edge, or in the center if none is specified. - - Anchor is double-buffered, see wl_surface.commit. - - - - - - - Requests that the compositor avoids occluding an area with other - surfaces. The compositor's use of this information is - implementation-dependent - do not assume that this region will not - actually be occluded. - - A positive value is only meaningful if the surface is anchored to one - edge or an edge and both perpendicular edges. If the surface is not - anchored, anchored to only two perpendicular edges (a corner), anchored - to only two parallel edges or anchored to all edges, a positive value - will be treated the same as zero. - - A positive zone is the distance from the edge in surface-local - coordinates to consider exclusive. - - Surfaces that do not wish to have an exclusive zone may instead specify - how they should interact with surfaces that do. If set to zero, the - surface indicates that it would like to be moved to avoid occluding - surfaces with a positive exclusive zone. If set to -1, the surface - indicates that it would not like to be moved to accommodate for other - surfaces, and the compositor should extend it all the way to the edges - it is anchored to. - - For example, a panel might set its exclusive zone to 10, so that - maximized shell surfaces are not shown on top of it. A notification - might set its exclusive zone to 0, so that it is moved to avoid - occluding the panel, but shell surfaces are shown underneath it. A - wallpaper or lock screen might set their exclusive zone to -1, so that - they stretch below or over the panel. - - The default value is 0. - - Exclusive zone is double-buffered, see wl_surface.commit. - - - - - - - Requests that the surface be placed some distance away from the anchor - point on the output, in surface-local coordinates. Setting this value - for edges you are not anchored to has no effect. - - The exclusive zone includes the margin. - - Margin is double-buffered, see wl_surface.commit. - - - - - - - - - - Set to 1 to request that the seat send keyboard events to this layer - surface. For layers below the shell surface layer, the seat will use - normal focus semantics. For layers above the shell surface layers, the - seat will always give exclusive keyboard focus to the top-most layer - which has keyboard interactivity set to true. - - Layer surfaces receive pointer, touch, and tablet events normally. If - you do not want to receive them, set the input region on your surface - to an empty region. - - Events is double-buffered, see wl_surface.commit. - - - - - - - This assigns an xdg_popup's parent to this layer_surface. This popup - should have been created via xdg_surface::get_popup with the parent set - to NULL, and this request must be invoked before committing the popup's - initial state. - - See the documentation of xdg_popup for more details about what an - xdg_popup is and how it is used. - - - - - - - When a configure event is received, if a client commits the - surface in response to the configure event, then the client - must make an ack_configure request sometime before the commit - request, passing along the serial of the configure event. - - If the client receives multiple configure events before it - can respond to one, it only has to ack the last configure event. - - A client is not required to commit immediately after sending - an ack_configure request - it may even ack_configure several times - before its next surface commit. - - A client may send multiple ack_configure requests before committing, but - only the last request sent before a commit indicates which configure - event the client really is responding to. - - - - - - - This request destroys the layer surface. - - - - - - The configure event asks the client to resize its surface. - - Clients should arrange their surface for the new states, and then send - an ack_configure request with the serial sent in this configure event at - some point before committing the new surface. - - The client is free to dismiss all but the last configure event it - received. - - The width and height arguments specify the size of the window in - surface-local coordinates. - - The size is a hint, in the sense that the client is free to ignore it if - it doesn't resize, pick a smaller size (to satisfy aspect ratio or - resize in steps of NxM pixels). If the client picks a smaller size and - is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the - surface will be centered on this axis. - - If the width or height arguments are zero, it means the client should - decide its own window dimension. - - - - - - - - - The closed event is sent by the compositor when the surface will no - longer be shown. The output may have been destroyed or the user may - have asked for it to be removed. Further changes to the surface will be - ignored. The client should destroy the resource after receiving this - event, and create a new surface if they so choose. - - - - - - - - - - - - - - - - - - - - - Change the layer that the surface is rendered on. - - Layer is double-buffered, see wl_surface.commit. - - - - - diff --git a/resources/config b/resources/config index adf03a1f..420262db 100644 --- a/resources/config +++ b/resources/config @@ -5,9 +5,31 @@ // "width": 1280, // Waybar width "spacing": 4, // Gaps between modules (4px) // Choose the order of the modules - "modules-left": ["sway/workspaces", "sway/mode", "sway/scratchpad", "custom/media"], - "modules-center": ["sway/window"], - "modules-right": ["mpd", "idle_inhibitor", "pulseaudio", "network", "cpu", "memory", "temperature", "backlight", "keyboard-state", "sway/language", "battery", "battery#bat2", "clock", "tray"], + "modules-left": [ + "sway/workspaces", + "sway/mode", + "sway/scratchpad", + "custom/media" + ], + "modules-center": [ + "sway/window" + ], + "modules-right": [ + "mpd", + "idle_inhibitor", + "pulseaudio", + "network", + "cpu", + "memory", + "temperature", + "backlight", + "keyboard-state", + "sway/language", + "battery", + "battery#bat2", + "clock", + "tray" + ], // Modules configuration // "sway/workspaces": { // "disable-scroll": true, diff --git a/resources/custom_modules/mediaplayer.py b/resources/custom_modules/mediaplayer.py index 51a48373..4aea4171 100755 --- a/resources/custom_modules/mediaplayer.py +++ b/resources/custom_modules/mediaplayer.py @@ -23,7 +23,7 @@ def signal_handler(sig, frame): class PlayerManager: - def __init__(self, selected_player=None): + def __init__(self, selected_player=None, excluded_player=[]): self.manager = Playerctl.PlayerManager() self.loop = GLib.MainLoop() self.manager.connect( @@ -35,11 +35,14 @@ class PlayerManager: signal.signal(signal.SIGTERM, signal_handler) signal.signal(signal.SIGPIPE, signal.SIG_DFL) self.selected_player = selected_player + self.excluded_player = excluded_player.split(',') if excluded_player else [] self.init_players() def init_players(self): for player in self.manager.props.player_names: + if player.name in self.excluded_player: + continue if self.selected_player is not None and self.selected_player != player.name: logger.debug(f"{player.name} is not the filtered player, skipping it") continue @@ -149,6 +152,8 @@ def parse_arguments(): # Increase verbosity with every occurrence of -v parser.add_argument("-v", "--verbose", action="count", default=0) + parser.add_argument("-x", "--exclude", "- Comma-separated list of excluded player") + # Define for which player we"re listening parser.add_argument("--player") @@ -174,7 +179,10 @@ def main(): logger.info("Creating player manager") if arguments.player: logger.info(f"Filtering for player: {arguments.player}") - player = PlayerManager(arguments.player) + if arguments.exclude: + logger.info(f"Exclude player {arguments.exclude}") + + player = PlayerManager(arguments.player, arguments.exclude) player.run() diff --git a/resources/style.css b/resources/style.css index 3d44829f..6e4fcebc 100644 --- a/resources/style.css +++ b/resources/style.css @@ -69,7 +69,7 @@ button:hover { #mode { background-color: #64727D; - border-bottom: 3px solid #ffffff; + box-shadow: inset 0 -3px #ffffff; } #clock, diff --git a/src/ALabel.cpp b/src/ALabel.cpp index c87e3228..7840819d 100644 --- a/src/ALabel.cpp +++ b/src/ALabel.cpp @@ -20,6 +20,7 @@ ALabel::ALabel(const Json::Value& config, const std::string& name, const std::st if (!id.empty()) { label_.get_style_context()->add_class(id); } + label_.get_style_context()->add_class(MODULE_CLASS); event_box_.add(label_); if (config_["max-length"].isUInt()) { label_.set_max_width_chars(config_["max-length"].asInt()); diff --git a/src/ASlider.cpp b/src/ASlider.cpp index a5e3889c..b434be30 100644 --- a/src/ASlider.cpp +++ b/src/ASlider.cpp @@ -13,6 +13,7 @@ ASlider::ASlider(const Json::Value& config, const std::string& name, const std:: if (!id.empty()) { scale_.get_style_context()->add_class(id); } + scale_.get_style_context()->add_class(MODULE_CLASS); event_box_.add(scale_); scale_.signal_value_changed().connect(sigc::mem_fun(*this, &ASlider::onValueChanged)); diff --git a/src/bar.cpp b/src/bar.cpp index 1ffe2ef6..31afcd43 100644 --- a/src/bar.cpp +++ b/src/bar.cpp @@ -1,16 +1,13 @@ -#ifdef HAVE_GTK_LAYER_SHELL -#include -#endif +#include "bar.hpp" +#include #include #include -#include "bar.hpp" #include "client.hpp" #include "factory.hpp" #include "group.hpp" -#include "wlr-layer-shell-unstable-v1-client-protocol.h" #ifdef HAVE_SWAY #include "modules/sway/bar.hpp" @@ -25,9 +22,6 @@ static constexpr const char* MIN_WIDTH_MSG = static constexpr const char* BAR_SIZE_MSG = "Bar configured (width: {}, height: {}) for output: {}"; -static constexpr const char* SIZE_DEFINED = - "{} size is defined in the config file so it will stay like that"; - const Bar::bar_mode_map Bar::PRESET_MODES = { // {"default", {// Special mode to hold the global bar configuration @@ -60,8 +54,8 @@ const Bar::bar_mode_map Bar::PRESET_MODES = { // .passthrough = true, .visible = true}}}; -const std::string_view Bar::MODE_DEFAULT = "default"; -const std::string_view Bar::MODE_INVISIBLE = "invisible"; +const std::string Bar::MODE_DEFAULT = "default"; +const std::string Bar::MODE_INVISIBLE = "invisible"; const std::string_view DEFAULT_BAR_ID = "bar-0"; /* Deserializer for enum bar_layer */ @@ -93,11 +87,38 @@ void from_json(const Json::Value& j, bar_mode& m) { } } +/* Deserializer for enum Gtk::PositionType */ +void from_json(const Json::Value& j, Gtk::PositionType& pos) { + if (j == "left") { + pos = Gtk::POS_LEFT; + } else if (j == "right") { + pos = Gtk::POS_RIGHT; + } else if (j == "top") { + pos = Gtk::POS_TOP; + } else if (j == "bottom") { + pos = Gtk::POS_BOTTOM; + } +} + +Glib::ustring to_string(Gtk::PositionType pos) { + switch (pos) { + case Gtk::POS_LEFT: + return "left"; + case Gtk::POS_RIGHT: + return "right"; + case Gtk::POS_TOP: + return "top"; + case Gtk::POS_BOTTOM: + return "bottom"; + } + throw std::runtime_error("Invalid Gtk::PositionType"); +} + /* Deserializer for JSON Object -> map * Assumes that all the values in the object are deserializable to the same type. */ template ::value>> + typename = std::enable_if_t::value>> void from_json(const Json::Value& j, std::map& m) { if (j.isObject()) { for (auto it = j.begin(); it != j.end(); ++it) { @@ -106,375 +127,6 @@ void from_json(const Json::Value& j, std::map& m) { } } -#ifdef HAVE_GTK_LAYER_SHELL -struct GLSSurfaceImpl : public BarSurface, public sigc::trackable { - GLSSurfaceImpl(Gtk::Window& window, struct waybar_output& output) : window_{window} { - output_name_ = output.name; - // this has to be executed before GtkWindow.realize - gtk_layer_init_for_window(window_.gobj()); - gtk_layer_set_keyboard_interactivity(window.gobj(), FALSE); - gtk_layer_set_monitor(window_.gobj(), output.monitor->gobj()); - gtk_layer_set_namespace(window_.gobj(), "waybar"); - - window.signal_map_event().connect_notify(sigc::mem_fun(*this, &GLSSurfaceImpl::onMap)); - window.signal_configure_event().connect_notify( - sigc::mem_fun(*this, &GLSSurfaceImpl::onConfigure)); - } - - void setExclusiveZone(bool enable) override { - if (enable) { - gtk_layer_auto_exclusive_zone_enable(window_.gobj()); - } else { - gtk_layer_set_exclusive_zone(window_.gobj(), 0); - } - } - - void setMargins(const struct bar_margins& margins) override { - gtk_layer_set_margin(window_.gobj(), GTK_LAYER_SHELL_EDGE_LEFT, margins.left); - gtk_layer_set_margin(window_.gobj(), GTK_LAYER_SHELL_EDGE_RIGHT, margins.right); - gtk_layer_set_margin(window_.gobj(), GTK_LAYER_SHELL_EDGE_TOP, margins.top); - gtk_layer_set_margin(window_.gobj(), GTK_LAYER_SHELL_EDGE_BOTTOM, margins.bottom); - } - - void setLayer(bar_layer value) override { - auto layer = GTK_LAYER_SHELL_LAYER_BOTTOM; - if (value == bar_layer::TOP) { - layer = GTK_LAYER_SHELL_LAYER_TOP; - } else if (value == bar_layer::OVERLAY) { - layer = GTK_LAYER_SHELL_LAYER_OVERLAY; - } - gtk_layer_set_layer(window_.gobj(), layer); - } - - void setPassThrough(bool enable) override { - passthrough_ = enable; - auto gdk_window = window_.get_window(); - if (gdk_window) { - Cairo::RefPtr region; - if (enable) { - region = Cairo::Region::create(); - } - gdk_window->input_shape_combine_region(region, 0, 0); - } - } - - void setPosition(const std::string_view& position) override { - auto unanchored = GTK_LAYER_SHELL_EDGE_BOTTOM; - vertical_ = false; - if (position == "bottom") { - unanchored = GTK_LAYER_SHELL_EDGE_TOP; - } else if (position == "left") { - unanchored = GTK_LAYER_SHELL_EDGE_RIGHT; - vertical_ = true; - } else if (position == "right") { - vertical_ = true; - unanchored = GTK_LAYER_SHELL_EDGE_LEFT; - } - for (auto edge : {GTK_LAYER_SHELL_EDGE_LEFT, GTK_LAYER_SHELL_EDGE_RIGHT, - GTK_LAYER_SHELL_EDGE_TOP, GTK_LAYER_SHELL_EDGE_BOTTOM}) { - gtk_layer_set_anchor(window_.gobj(), edge, unanchored != edge); - } - - // Disable anchoring for other edges too if the width - // or the height has been set to a value other than 'auto' - // otherwise the bar will use all space - if (vertical_ && height_ > 1) { - gtk_layer_set_anchor(window_.gobj(), GTK_LAYER_SHELL_EDGE_BOTTOM, false); - gtk_layer_set_anchor(window_.gobj(), GTK_LAYER_SHELL_EDGE_TOP, false); - } else if (!vertical_ && width_ > 1) { - gtk_layer_set_anchor(window_.gobj(), GTK_LAYER_SHELL_EDGE_LEFT, false); - gtk_layer_set_anchor(window_.gobj(), GTK_LAYER_SHELL_EDGE_RIGHT, false); - } - } - - void setSize(uint32_t width, uint32_t height) override { - width_ = width; - height_ = height; - window_.set_size_request(width_, height_); - }; - - private: - Gtk::Window& window_; - std::string output_name_; - uint32_t width_; - uint32_t height_; - bool passthrough_ = false; - bool vertical_ = false; - - void onMap(GdkEventAny* ev) { setPassThrough(passthrough_); } - - void onConfigure(GdkEventConfigure* ev) { - /* - * GTK wants new size for the window. - * Actual resizing and management of the exclusve zone is handled within the gtk-layer-shell - * code. This event handler only updates stored size of the window and prints some warnings. - * - * Note: forced resizing to a window smaller than required by GTK would not work with - * gtk-layer-shell. - */ - if (vertical_) { - if (width_ > 1 && ev->width > static_cast(width_)) { - spdlog::warn(MIN_WIDTH_MSG, width_, ev->width); - } - } else { - if (height_ > 1 && ev->height > static_cast(height_)) { - spdlog::warn(MIN_HEIGHT_MSG, height_, ev->height); - } - } - width_ = ev->width; - height_ = ev->height; - spdlog::info(BAR_SIZE_MSG, width_, height_, output_name_); - } -}; -#endif - -struct RawSurfaceImpl : public BarSurface, public sigc::trackable { - RawSurfaceImpl(Gtk::Window& window, struct waybar_output& output) : window_{window} { - output_ = gdk_wayland_monitor_get_wl_output(output.monitor->gobj()); - output_name_ = output.name; - - window.signal_realize().connect_notify(sigc::mem_fun(*this, &RawSurfaceImpl::onRealize)); - window.signal_map_event().connect_notify(sigc::mem_fun(*this, &RawSurfaceImpl::onMap)); - window.signal_configure_event().connect_notify( - sigc::mem_fun(*this, &RawSurfaceImpl::onConfigure)); - - if (window.get_realized()) { - onRealize(); - } - } - - void setExclusiveZone(bool enable) override { - exclusive_zone_ = enable; - if (layer_surface_) { - auto zone = 0; - if (enable) { - // exclusive zone already includes margin for anchored edge, - // only opposite margin should be added - if ((anchor_ & VERTICAL_ANCHOR) == VERTICAL_ANCHOR) { - zone += width_; - zone += (anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT) ? margins_.right : margins_.left; - } else { - zone += height_; - zone += (anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP) ? margins_.bottom : margins_.top; - } - } - spdlog::debug("Set exclusive zone {} for output {}", zone, output_name_); - zwlr_layer_surface_v1_set_exclusive_zone(layer_surface_.get(), zone); - } - } - - void setLayer(bar_layer layer) override { - layer_ = ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM; - if (layer == bar_layer::TOP) { - layer_ = ZWLR_LAYER_SHELL_V1_LAYER_TOP; - } else if (layer == bar_layer::OVERLAY) { - layer_ = ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY; - } - // updating already mapped window - if (layer_surface_) { - if (zwlr_layer_surface_v1_get_version(layer_surface_.get()) >= - ZWLR_LAYER_SURFACE_V1_SET_LAYER_SINCE_VERSION) { - zwlr_layer_surface_v1_set_layer(layer_surface_.get(), layer_); - } else { - spdlog::warn("Unable to change layer: layer-shell implementation is too old"); - } - } - } - - void setMargins(const struct bar_margins& margins) override { - margins_ = margins; - // updating already mapped window - if (layer_surface_) { - zwlr_layer_surface_v1_set_margin(layer_surface_.get(), margins_.top, margins_.right, - margins_.bottom, margins_.left); - } - } - - void setPassThrough(bool enable) override { - passthrough_ = enable; - /* GTK overwrites any region changes applied directly to the wl_surface, - * thus the same GTK region API as in the GLS impl has to be used. */ - auto gdk_window = window_.get_window(); - if (gdk_window) { - Cairo::RefPtr region; - if (enable) { - region = Cairo::Region::create(); - } - gdk_window->input_shape_combine_region(region, 0, 0); - } - } - - void setPosition(const std::string_view& position) override { - anchor_ = HORIZONTAL_ANCHOR | ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP; - if (position == "bottom") { - anchor_ = HORIZONTAL_ANCHOR | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM; - } else if (position == "left") { - anchor_ = VERTICAL_ANCHOR | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT; - } else if (position == "right") { - anchor_ = VERTICAL_ANCHOR | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; - } - - // updating already mapped window - if (layer_surface_) { - zwlr_layer_surface_v1_set_anchor(layer_surface_.get(), anchor_); - } - } - - void setSize(uint32_t width, uint32_t height) override { - configured_width_ = width_ = width; - configured_height_ = height_ = height; - // layer_shell.configure handler should update exclusive zone if size changes - window_.set_size_request(width, height); - }; - - void commit() override { - if (surface_) { - wl_surface_commit(surface_); - } - } - - private: - constexpr static uint8_t VERTICAL_ANCHOR = - ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM; - constexpr static uint8_t HORIZONTAL_ANCHOR = - ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; - - template - using deleter_fn = std::integral_constant; - using layer_surface_ptr = - std::unique_ptr>; - - Gtk::Window& window_; - std::string output_name_; - uint32_t configured_width_ = 0; - uint32_t configured_height_ = 0; - uint32_t width_ = 0; - uint32_t height_ = 0; - uint8_t anchor_ = HORIZONTAL_ANCHOR | ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP; - bool exclusive_zone_ = true; - bool passthrough_ = false; - struct bar_margins margins_; - - zwlr_layer_shell_v1_layer layer_ = ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM; - struct wl_output* output_ = nullptr; // owned by GTK - struct wl_surface* surface_ = nullptr; // owned by GTK - layer_surface_ptr layer_surface_; - - void onRealize() { - auto gdk_window = window_.get_window()->gobj(); - gdk_wayland_window_set_use_custom_surface(gdk_window); - } - - void onMap(GdkEventAny* ev) { - static const struct zwlr_layer_surface_v1_listener layer_surface_listener = { - .configure = onSurfaceConfigure, - .closed = onSurfaceClosed, - }; - auto client = Client::inst(); - auto gdk_window = window_.get_window()->gobj(); - surface_ = gdk_wayland_window_get_wl_surface(gdk_window); - - layer_surface_.reset(zwlr_layer_shell_v1_get_layer_surface(client->layer_shell, surface_, - output_, layer_, "waybar")); - - zwlr_layer_surface_v1_add_listener(layer_surface_.get(), &layer_surface_listener, this); - zwlr_layer_surface_v1_set_keyboard_interactivity(layer_surface_.get(), false); - zwlr_layer_surface_v1_set_anchor(layer_surface_.get(), anchor_); - zwlr_layer_surface_v1_set_margin(layer_surface_.get(), margins_.top, margins_.right, - margins_.bottom, margins_.left); - - setSurfaceSize(width_, height_); - setExclusiveZone(exclusive_zone_); - setPassThrough(passthrough_); - - commit(); - wl_display_roundtrip(client->wl_display); - } - - void onConfigure(GdkEventConfigure* ev) { - /* - * GTK wants new size for the window. - * - * Prefer configured size if it's non-default. - * If the size is not set and the window is smaller than requested by GTK, request resize from - * layer surface. - */ - auto tmp_height = height_; - auto tmp_width = width_; - if (ev->height > static_cast(height_)) { - // Default minimal value - if (height_ > 1) { - spdlog::warn(MIN_HEIGHT_MSG, height_, ev->height); - } - if (configured_height_ > 1) { - spdlog::info(SIZE_DEFINED, "Height"); - } else { - tmp_height = ev->height; - } - } - if (ev->width > static_cast(width_)) { - // Default minimal value - if (width_ > 1) { - spdlog::warn(MIN_WIDTH_MSG, width_, ev->width); - } - if (configured_width_ > 1) { - spdlog::info(SIZE_DEFINED, "Width"); - } else { - tmp_width = ev->width; - } - } - if (tmp_width != width_ || tmp_height != height_) { - setSurfaceSize(tmp_width, tmp_height); - commit(); - } - } - - void setSurfaceSize(uint32_t width, uint32_t height) { - /* If the client is anchored to two opposite edges, layer_surface.configure will return - * size without margins for the axis. - * layer_surface.set_size, however, expects size with margins for the anchored axis. - * This is not specified by wlr-layer-shell and based on actual behavior of sway. - * - * If the size for unanchored axis is not set (0), change request to 1 to avoid automatic - * assignment by the compositor. - */ - if ((anchor_ & VERTICAL_ANCHOR) == VERTICAL_ANCHOR) { - width = width > 0 ? width : 1; - if (height > 1) { - height += margins_.top + margins_.bottom; - } - } else { - height = height > 0 ? height : 1; - if (width > 1) { - width += margins_.right + margins_.left; - } - } - spdlog::debug("Set surface size {}x{} for output {}", width, height, output_name_); - zwlr_layer_surface_v1_set_size(layer_surface_.get(), width, height); - } - - static void onSurfaceConfigure(void* data, struct zwlr_layer_surface_v1* surface, uint32_t serial, - uint32_t width, uint32_t height) { - auto o = static_cast(data); - if (width != o->width_ || height != o->height_) { - o->width_ = width; - o->height_ = height; - o->window_.set_size_request(o->width_, o->height_); - o->window_.resize(o->width_, o->height_); - o->setExclusiveZone(o->exclusive_zone_); - spdlog::info(BAR_SIZE_MSG, o->width_ == 1 ? "auto" : std::to_string(o->width_), - o->height_ == 1 ? "auto" : std::to_string(o->height_), o->output_name_); - o->commit(); - } - zwlr_layer_surface_v1_ack_configure(surface, serial); - } - - static void onSurfaceClosed(void* data, struct zwlr_layer_surface_v1* /* surface */) { - auto o = static_cast(data); - o->layer_surface_.reset(); - } -}; - }; // namespace waybar waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) @@ -493,17 +145,18 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) window.set_decorated(false); window.get_style_context()->add_class(output->name); window.get_style_context()->add_class(config["name"].asString()); - window.get_style_context()->add_class(config["position"].asString()); - auto position = config["position"].asString(); + from_json(config["position"], position); + orientation = (position == Gtk::POS_LEFT || position == Gtk::POS_RIGHT) + ? Gtk::ORIENTATION_VERTICAL + : Gtk::ORIENTATION_HORIZONTAL; - if (position == "right" || position == "left") { - left_ = Gtk::Box(Gtk::ORIENTATION_VERTICAL, 0); - center_ = Gtk::Box(Gtk::ORIENTATION_VERTICAL, 0); - right_ = Gtk::Box(Gtk::ORIENTATION_VERTICAL, 0); - box_ = Gtk::Box(Gtk::ORIENTATION_VERTICAL, 0); - vertical = true; - } + window.get_style_context()->add_class(to_string(position)); + + left_ = Gtk::Box(orientation, 0); + center_ = Gtk::Box(orientation, 0); + right_ = Gtk::Box(orientation, 0); + box_ = Gtk::Box(orientation, 0); left_.get_style_context()->add_class("modules-left"); center_.get_style_context()->add_class("modules-center"); @@ -516,8 +169,8 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) right_.set_spacing(spacing); } - uint32_t height = config["height"].isUInt() ? config["height"].asUInt() : 0; - uint32_t width = config["width"].isUInt() ? config["width"].asUInt() : 0; + height_ = config["height"].isUInt() ? config["height"].asUInt() : 0; + width_ = config["width"].isUInt() ? config["width"].asUInt() : 0; if (config["margin-top"].isInt() || config["margin-right"].isInt() || config["margin-bottom"].isInt() || config["margin-left"].isInt()) { @@ -568,21 +221,23 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) output->monitor->property_geometry().signal_changed().connect( sigc::mem_fun(*this, &Bar::onOutputGeometryChanged)); -#ifdef HAVE_GTK_LAYER_SHELL - bool use_gls = config["gtk-layer-shell"].isBool() ? config["gtk-layer-shell"].asBool() : true; - if (use_gls) { - surface_impl_ = std::make_unique(window, *output); - } else -#endif - { - surface_impl_ = std::make_unique(window, *output); - } + // this has to be executed before GtkWindow.realize + auto* gtk_window = window.gobj(); + gtk_layer_init_for_window(gtk_window); + gtk_layer_set_keyboard_mode(gtk_window, GTK_LAYER_SHELL_KEYBOARD_MODE_NONE); + gtk_layer_set_monitor(gtk_window, output->monitor->gobj()); + gtk_layer_set_namespace(gtk_window, "waybar"); + + gtk_layer_set_margin(gtk_window, GTK_LAYER_SHELL_EDGE_LEFT, margins_.left); + gtk_layer_set_margin(gtk_window, GTK_LAYER_SHELL_EDGE_RIGHT, margins_.right); + gtk_layer_set_margin(gtk_window, GTK_LAYER_SHELL_EDGE_TOP, margins_.top); + gtk_layer_set_margin(gtk_window, GTK_LAYER_SHELL_EDGE_BOTTOM, margins_.bottom); + + window.set_size_request(width_, height_); - surface_impl_->setMargins(margins_); - surface_impl_->setSize(width, height); // Position needs to be set after calculating the height due to the // GTK layer shell anchors logic relying on the dimensions of the bar. - surface_impl_->setPosition(position); + setPosition(position); /* Read custom modes if available */ if (auto modes = config.get("modes", {}); modes.isObject()) { @@ -638,7 +293,7 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) /* Need to define it here because of forward declared members */ waybar::Bar::~Bar() = default; -void waybar::Bar::setMode(const std::string_view& mode) { +void waybar::Bar::setMode(const std::string& mode) { using namespace std::literals::string_literals; auto style = window.get_style_context(); @@ -659,9 +314,23 @@ void waybar::Bar::setMode(const std::string_view& mode) { } void waybar::Bar::setMode(const struct bar_mode& mode) { - surface_impl_->setLayer(mode.layer); - surface_impl_->setExclusiveZone(mode.exclusive); - surface_impl_->setPassThrough(mode.passthrough); + auto* gtk_window = window.gobj(); + + auto layer = GTK_LAYER_SHELL_LAYER_BOTTOM; + if (mode.layer == bar_layer::TOP) { + layer = GTK_LAYER_SHELL_LAYER_TOP; + } else if (mode.layer == bar_layer::OVERLAY) { + layer = GTK_LAYER_SHELL_LAYER_OVERLAY; + } + gtk_layer_set_layer(gtk_window, layer); + + if (mode.exclusive) { + gtk_layer_auto_exclusive_zone_enable(gtk_window); + } else { + gtk_layer_set_exclusive_zone(gtk_window, 0); + } + + setPassThrough(passthrough_ = mode.passthrough); if (mode.visible) { window.get_style_context()->remove_class("hidden"); @@ -670,7 +339,58 @@ void waybar::Bar::setMode(const struct bar_mode& mode) { window.get_style_context()->add_class("hidden"); window.set_opacity(0); } - surface_impl_->commit(); +} + +void waybar::Bar::setPassThrough(bool passthrough) { + auto gdk_window = window.get_window(); + if (gdk_window) { + Cairo::RefPtr region; + if (passthrough) { + region = Cairo::Region::create(); + } + gdk_window->input_shape_combine_region(region, 0, 0); + } +} + +void waybar::Bar::setPosition(Gtk::PositionType position) { + std::array anchors; + anchors.fill(TRUE); + + auto orientation = (position == Gtk::POS_LEFT || position == Gtk::POS_RIGHT) + ? Gtk::ORIENTATION_VERTICAL + : Gtk::ORIENTATION_HORIZONTAL; + + switch (position) { + case Gtk::POS_LEFT: + anchors[GTK_LAYER_SHELL_EDGE_RIGHT] = FALSE; + break; + case Gtk::POS_RIGHT: + anchors[GTK_LAYER_SHELL_EDGE_LEFT] = FALSE; + break; + case Gtk::POS_BOTTOM: + anchors[GTK_LAYER_SHELL_EDGE_TOP] = FALSE; + break; + default: /* Gtk::POS_TOP */ + anchors[GTK_LAYER_SHELL_EDGE_BOTTOM] = FALSE; + break; + }; + // Disable anchoring for other edges too if the width + // or the height has been set to a value other than 'auto' + // otherwise the bar will use all space + uint32_t configured_width = config["width"].isUInt() ? config["width"].asUInt() : 0; + uint32_t configured_height = config["height"].isUInt() ? config["height"].asUInt() : 0; + if (orientation == Gtk::ORIENTATION_VERTICAL && configured_height > 1) { + anchors[GTK_LAYER_SHELL_EDGE_TOP] = FALSE; + anchors[GTK_LAYER_SHELL_EDGE_BOTTOM] = FALSE; + } else if (orientation == Gtk::ORIENTATION_HORIZONTAL && configured_width > 1) { + anchors[GTK_LAYER_SHELL_EDGE_LEFT] = FALSE; + anchors[GTK_LAYER_SHELL_EDGE_RIGHT] = FALSE; + } + + for (auto edge : {GTK_LAYER_SHELL_EDGE_LEFT, GTK_LAYER_SHELL_EDGE_RIGHT, GTK_LAYER_SHELL_EDGE_TOP, + GTK_LAYER_SHELL_EDGE_BOTTOM}) { + gtk_layer_set_anchor(window.gobj(), edge, anchors[edge]); + } } void waybar::Bar::onMap(GdkEventAny*) { @@ -680,6 +400,8 @@ void waybar::Bar::onMap(GdkEventAny*) { auto gdk_window = window.get_window()->gobj(); surface = gdk_wayland_window_get_wl_surface(gdk_window); configureGlobalOffset(gdk_window_get_width(gdk_window), gdk_window_get_height(gdk_window)); + + setPassThrough(passthrough_); } void waybar::Bar::setVisible(bool value) { @@ -824,39 +546,63 @@ auto waybar::Bar::setupWidgets() -> void { } void waybar::Bar::onConfigure(GdkEventConfigure* ev) { + /* + * GTK wants new size for the window. + * Actual resizing and management of the exclusve zone is handled within the gtk-layer-shell + * code. This event handler only updates stored size of the window and prints some warnings. + * + * Note: forced resizing to a window smaller than required by GTK would not work with + * gtk-layer-shell. + */ + if (orientation == Gtk::ORIENTATION_VERTICAL) { + if (width_ > 1 && ev->width > static_cast(width_)) { + spdlog::warn(MIN_WIDTH_MSG, width_, ev->width); + } + } else { + if (height_ > 1 && ev->height > static_cast(height_)) { + spdlog::warn(MIN_HEIGHT_MSG, height_, ev->height); + } + } + width_ = ev->width; + height_ = ev->height; + configureGlobalOffset(ev->width, ev->height); + spdlog::info(BAR_SIZE_MSG, ev->width, ev->height, output->name); } void waybar::Bar::configureGlobalOffset(int width, int height) { auto monitor_geometry = *output->monitor->property_geometry().get_value().gobj(); - auto position = config["position"].asString(); int x; int y; - if (position == "bottom") { - if (width + margins_.left + margins_.right >= monitor_geometry.width) + switch (position) { + case Gtk::POS_BOTTOM: + if (width + margins_.left + margins_.right >= monitor_geometry.width) + x = margins_.left; + else + x = (monitor_geometry.width - width) / 2; + y = monitor_geometry.height - height - margins_.bottom; + break; + case Gtk::POS_LEFT: x = margins_.left; - else - x = (monitor_geometry.width - width) / 2; - y = monitor_geometry.height - height - margins_.bottom; - } else if (position == "left") { - x = margins_.left; - if (height + margins_.top + margins_.bottom >= monitor_geometry.height) + if (height + margins_.top + margins_.bottom >= monitor_geometry.height) + y = margins_.top; + else + y = (monitor_geometry.height - height) / 2; + break; + case Gtk::POS_RIGHT: + x = monitor_geometry.width - width - margins_.right; + if (height + margins_.top + margins_.bottom >= monitor_geometry.height) + y = margins_.top; + else + y = (monitor_geometry.height - height) / 2; + break; + default: /* Gtk::POS_TOP */ + if (width + margins_.left + margins_.right >= monitor_geometry.width) + x = margins_.left; + else + x = (monitor_geometry.width - width) / 2; y = margins_.top; - else - y = (monitor_geometry.height - height) / 2; - } else if (position == "right") { - x = monitor_geometry.width - width - margins_.right; - if (height + margins_.top + margins_.bottom >= monitor_geometry.height) - y = margins_.top; - else - y = (monitor_geometry.height - height) / 2; - } else { - // position is top - if (width + margins_.left + margins_.right >= monitor_geometry.width) - x = margins_.left; - else - x = (monitor_geometry.width - width) / 2; - y = margins_.top; + break; } x_global = x + monitor_geometry.x; diff --git a/src/client.cpp b/src/client.cpp index 73c06fb8..7c59dd5e 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -1,5 +1,6 @@ #include "client.hpp" +#include #include #include @@ -8,7 +9,6 @@ #include "idle-inhibit-unstable-v1-client-protocol.h" #include "util/clara.hpp" #include "util/format.hpp" -#include "wlr-layer-shell-unstable-v1-client-protocol.h" waybar::Client *waybar::Client::inst() { static auto c = new Client(); @@ -18,13 +18,8 @@ waybar::Client *waybar::Client::inst() { void waybar::Client::handleGlobal(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { auto client = static_cast(data); - if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) { - // limit version to a highest supported by the client protocol file - version = std::min(version, zwlr_layer_shell_v1_interface.version); - client->layer_shell = static_cast( - wl_registry_bind(registry, name, &zwlr_layer_shell_v1_interface, version)); - } else if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0 && - version >= ZXDG_OUTPUT_V1_NAME_SINCE_VERSION) { + if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0 && + version >= ZXDG_OUTPUT_V1_NAME_SINCE_VERSION) { client->xdg_output_manager = static_cast(wl_registry_bind( registry, name, &zxdg_output_manager_v1_interface, ZXDG_OUTPUT_V1_NAME_SINCE_VERSION)); } else if (strcmp(interface, zwp_idle_inhibit_manager_v1_interface.name) == 0) { @@ -200,7 +195,12 @@ void waybar::Client::bindInterfaces() { }; wl_registry_add_listener(registry, ®istry_listener, this); wl_display_roundtrip(wl_display); - if (layer_shell == nullptr || xdg_output_manager == nullptr) { + + if (!gtk_layer_is_supported()) { + throw std::runtime_error("The Wayland compositor does not support wlr-layer-shell protocol"); + } + + if (xdg_output_manager == nullptr) { throw std::runtime_error("Failed to acquire required resources."); } // add existing outputs and subscribe to updates diff --git a/src/factory.cpp b/src/factory.cpp index 2ad5b6fa..6b709f33 100644 --- a/src/factory.cpp +++ b/src/factory.cpp @@ -1,12 +1,112 @@ #include "factory.hpp" -#ifdef HAVE_LIBPULSE -#include "modules/pulseaudio_slider.hpp" -#endif +#include "bar.hpp" +#if defined(HAVE_CHRONO_TIMEZONES) || defined(HAVE_LIBDATE) +#include "modules/clock.hpp" +#else +#include "modules/simpleclock.hpp" +#endif +#ifdef HAVE_SWAY +#include "modules/sway/language.hpp" +#include "modules/sway/mode.hpp" +#include "modules/sway/scratchpad.hpp" +#include "modules/sway/window.hpp" +#include "modules/sway/workspaces.hpp" +#endif +#ifdef HAVE_WLR_TASKBAR +#include "modules/wlr/taskbar.hpp" +#endif +#ifdef HAVE_WLR_WORKSPACES +#include "modules/wlr/workspace_manager.hpp" +#endif +#ifdef HAVE_RIVER +#include "modules/river/layout.hpp" +#include "modules/river/mode.hpp" +#include "modules/river/tags.hpp" +#include "modules/river/window.hpp" +#endif +#ifdef HAVE_DWL +#include "modules/dwl/tags.hpp" +#endif +#ifdef HAVE_HYPRLAND +#include "modules/hyprland/language.hpp" +#include "modules/hyprland/submap.hpp" +#include "modules/hyprland/window.hpp" +#include "modules/hyprland/workspaces.hpp" +#endif +#if defined(__FreeBSD__) || defined(__linux__) +#include "modules/battery.hpp" +#endif +#if defined(HAVE_CPU_LINUX) || defined(HAVE_CPU_BSD) +#include "modules/cpu.hpp" +#include "modules/cpu_frequency.hpp" +#include "modules/cpu_usage.hpp" +#include "modules/load.hpp" +#endif +#include "modules/idle_inhibitor.hpp" +#if defined(HAVE_MEMORY_LINUX) || defined(HAVE_MEMORY_BSD) +#include "modules/memory.hpp" +#endif +#include "modules/disk.hpp" +#ifdef HAVE_DBUSMENU +#include "modules/sni/tray.hpp" +#endif +#ifdef HAVE_MPRIS +#include "modules/mpris/mpris.hpp" +#endif +#ifdef HAVE_LIBNL +#include "modules/network.hpp" +#endif #ifdef HAVE_LIBUDEV +#include "modules/backlight.hpp" #include "modules/backlight_slider.hpp" #endif +#ifdef HAVE_LIBEVDEV +#include "modules/keyboard_state.hpp" +#endif +#ifdef HAVE_GAMEMODE +#include "modules/gamemode.hpp" +#endif +#ifdef HAVE_UPOWER +#include "modules/upower/upower.hpp" +#endif +#ifdef HAVE_PIPEWIRE +#include "modules/privacy/privacy.hpp" +#endif +#ifdef HAVE_LIBPULSE +#include "modules/pulseaudio.hpp" +#include "modules/pulseaudio_slider.hpp" +#endif +#ifdef HAVE_LIBMPDCLIENT +#include "modules/mpd/mpd.hpp" +#endif +#ifdef HAVE_LIBSNDIO +#include "modules/sndio.hpp" +#endif +#if defined(__linux__) +#include "modules/bluetooth.hpp" +#endif +#ifdef HAVE_LOGIND_INHIBITOR +#include "modules/inhibitor.hpp" +#endif +#ifdef HAVE_LIBJACK +#include "modules/jack.hpp" +#endif +#ifdef HAVE_LIBWIREPLUMBER +#include "modules/wireplumber.hpp" +#endif +#ifdef HAVE_LIBCAVA +#include "modules/cava.hpp" +#endif +#ifdef HAVE_SYSTEMD_MONITOR +#include "modules/systemd_failed_units.hpp" +#endif +#include "modules/cffi.hpp" +#include "modules/custom.hpp" +#include "modules/image.hpp" +#include "modules/temperature.hpp" +#include "modules/user.hpp" waybar::Factory::Factory(const Bar& bar, const Json::Value& config) : bar_(bar), config_(config) {} @@ -16,7 +116,7 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name, auto hash_pos = name.find('#'); auto ref = name.substr(0, hash_pos); auto id = hash_pos != std::string::npos ? name.substr(hash_pos + 1) : ""; -#if defined(__FreeBSD__) || (defined(__linux__) && !defined(NO_FILESYSTEM)) +#if defined(__FreeBSD__) || defined(__linux__) if (ref == "battery") { return new waybar::modules::Battery(id, bar_, config_[name]); } @@ -58,16 +158,16 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name, return new waybar::modules::sway::Scratchpad(id, config_[name]); } #endif -#ifdef HAVE_WLR +#ifdef HAVE_WLR_TASKBAR if (ref == "wlr/taskbar") { return new waybar::modules::wlr::Taskbar(id, bar_, config_[name]); } -#ifdef USE_EXPERIMENTAL +#endif +#ifdef HAVE_WLR_WORKSPACES if (ref == "wlr/workspaces") { return new waybar::modules::wlr::WorkspaceManager(id, bar_, config_[name]); } #endif -#endif #ifdef HAVE_RIVER if (ref == "river/mode") { return new waybar::modules::river::Mode(id, bar_, config_[name]); @@ -178,10 +278,12 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name, return new waybar::modules::Sndio(id, config_[name]); } #endif -#ifdef HAVE_GIO_UNIX +#if defined(__linux__) if (ref == "bluetooth") { return new waybar::modules::Bluetooth(id, config_[name]); } +#endif +#ifdef HAVE_LOGIND_INHIBITOR if (ref == "inhibitor") { return new waybar::modules::Inhibitor(id, bar_, config_[name]); } diff --git a/src/group.cpp b/src/group.cpp index 9deb4f3c..262cae65 100644 --- a/src/group.cpp +++ b/src/group.cpp @@ -75,8 +75,7 @@ Group::Group(const std::string& name, const std::string& id, const Json::Value& if (left_to_right) { box.pack_end(revealer); - } - else { + } else { box.pack_start(revealer); } diff --git a/src/modules/cava.cpp b/src/modules/cava.cpp index c0ce0076..07227546 100644 --- a/src/modules/cava.cpp +++ b/src/modules/cava.cpp @@ -53,8 +53,8 @@ waybar::modules::Cava::Cava(const std::string& id, const Json::Value& config) if (config_["method"].isString()) prm_.input = cava::input_method_by_name(config_["method"].asString().c_str()); if (config_["source"].isString()) prm_.audio_source = config_["source"].asString().data(); - if (config_["sample_rate"].isNumeric()) prm_.fifoSample = config_["sample_rate"].asLargestInt(); - if (config_["sample_bits"].isInt()) prm_.fifoSampleBits = config_["sample_bits"].asInt(); + if (config_["sample_rate"].isNumeric()) prm_.samplerate = config_["sample_rate"].asLargestInt(); + if (config_["sample_bits"].isInt()) prm_.samplebits = config_["sample_bits"].asInt(); if (config_["stereo"].isBool()) prm_.stereo = config_["stereo"].asBool(); if (config_["reverse"].isBool()) prm_.reverse = config_["reverse"].asBool(); if (config_["bar_delimiter"].isInt()) prm_.bar_delim = config_["bar_delimiter"].asInt(); diff --git a/src/modules/clock.cpp b/src/modules/clock.cpp index 495dfab3..b54a360f 100644 --- a/src/modules/clock.cpp +++ b/src/modules/clock.cpp @@ -2,8 +2,10 @@ #include +#include #include #include +#include #include "util/ustring_clen.hpp" @@ -20,7 +22,8 @@ waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config) tlpFmt_{(config_["tooltip-format"].isString()) ? config_["tooltip-format"].asString() : ""}, cldInTooltip_{tlpFmt_.find("{" + kCldPlaceholder + "}") != std::string::npos}, tzInTooltip_{tlpFmt_.find("{" + kTZPlaceholder + "}") != std::string::npos}, - tzCurrIdx_{0} { + tzCurrIdx_{0}, + ordInTooltip_{tlpFmt_.find("{" + kOrdPlaceholder + "}") != std::string::npos} { tlpText_ = tlpFmt_; if (config_["timezones"].isArray() && !config_["timezones"].empty()) { @@ -127,7 +130,7 @@ waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config) } auto waybar::modules::Clock::update() -> void { - auto tz{tzList_[tzCurrIdx_] ?: current_zone()}; + const auto* tz = tzList_[tzCurrIdx_] != nullptr ? tzList_[tzCurrIdx_] : current_zone(); const zoned_time now{tz, floor(system_clock::now())}; label_.set_markup(fmt_lib::vformat(locale_, format_, fmt_lib::make_format_args(now))); @@ -140,11 +143,14 @@ auto waybar::modules::Clock::update() -> void { if (tzInTooltip_) tzText_ = getTZtext(now.get_sys_time()); if (cldInTooltip_) cldText_ = get_calendar(today, shiftedDay, tz); - if (tzInTooltip_ || cldInTooltip_) { + if (ordInTooltip_) ordText_ = get_ordinal_date(shiftedDay); + if (tzInTooltip_ || cldInTooltip_ || ordInTooltip_) { // std::vformat doesn't support named arguments. tlpText_ = std::regex_replace(tlpFmt_, std::regex("\\{" + kTZPlaceholder + "\\}"), tzText_); tlpText_ = std::regex_replace(tlpText_, std::regex("\\{" + kCldPlaceholder + "\\}"), cldText_); + tlpText_ = + std::regex_replace(tlpText_, std::regex("\\{" + kOrdPlaceholder + "\\}"), ordText_); } tlpText_ = fmt_lib::vformat(locale_, tlpText_, fmt_lib::make_format_args(shiftedNow)); @@ -161,7 +167,8 @@ auto waybar::modules::Clock::getTZtext(sys_seconds now) -> std::string { std::stringstream os; for (size_t tz_idx{0}; tz_idx < tzList_.size(); ++tz_idx) { if (static_cast(tz_idx) == tzCurrIdx_) continue; - auto zt{zoned_time{tzList_[tz_idx], now}}; + const auto* tz = tzList_[tz_idx] != nullptr ? tzList_[tz_idx] : current_zone(); + auto zt{zoned_time{tz, now}}; os << fmt_lib::vformat(locale_, format_, fmt_lib::make_format_args(zt)) << '\n'; } @@ -214,22 +221,22 @@ auto getCalendarLine(const year_month_day& currDate, const year_month ym, const } // Print first week prefixed with spaces if necessary case 2: { + auto d{day{1}}; auto wd{weekday{ym / 1}}; os << std::string((wd - firstdow).count() * 3, ' '); - if (currDate != ym / 1d) - os << date::format(*locale_, "{:L%e}", 1d); + if (currDate != ym / d) + os << date::format(*locale_, "{:L%e}", d); else os << "{today}"; - auto d{2d}; while (++wd != firstdow) { + ++d; + if (currDate != ym / d) os << date::format(*locale_, " {:L%e}", d); else os << " {today}"; - - ++d; } break; } @@ -437,3 +444,28 @@ auto waybar::modules::Clock::first_day_of_week() -> weekday { #endif return Sunday; } + +auto waybar::modules::Clock::get_ordinal_date(const year_month_day& today) -> std::string { + auto day = static_cast(today.day()); + std::stringstream res; + res << day; + if (day >= 11 && day <= 13) { + res << "th"; + return res.str(); + } + + switch (day % 10) { + case 1: + res << "st"; + break; + case 2: + res << "nd"; + break; + case 3: + res << "rd"; + break; + default: + res << "th"; + } + return res.str(); +} \ No newline at end of file diff --git a/src/modules/cpu_frequency/bsd.cpp b/src/modules/cpu_frequency/bsd.cpp index c837c1fd..743fb288 100644 --- a/src/modules/cpu_frequency/bsd.cpp +++ b/src/modules/cpu_frequency/bsd.cpp @@ -1,15 +1,27 @@ #include - -#include // NAN +#include #include "modules/cpu_frequency.hpp" std::vector waybar::modules::CpuFrequency::parseCpuFrequencies() { - static std::vector frequencies; + std::vector frequencies; + char buffer[256]; + size_t len; + int32_t freq; + uint32_t i = 0; + + while (true) { + len = 4; + snprintf(buffer, 256, "dev.cpu.%u.freq", i); + if (sysctlbyname(buffer, &freq, &len, NULL, 0) == -1 || len <= 0) break; + frequencies.push_back(freq); + ++i; + } + if (frequencies.empty()) { - spdlog::warn( - "cpu/bsd: parseCpuFrequencies is not implemented, expect garbage in {*_frequency}"); + spdlog::warn("cpu/bsd: parseCpuFrequencies failed, not found in sysctl"); frequencies.push_back(NAN); } + return frequencies; } diff --git a/src/modules/custom.cpp b/src/modules/custom.cpp index d628b85d..5e5d7019 100644 --- a/src/modules/custom.cpp +++ b/src/modules/custom.cpp @@ -170,22 +170,30 @@ auto waybar::modules::Custom::update() -> void { if (label_.get_tooltip_markup() != str) { label_.set_tooltip_markup(str); } + } else if (config_["tooltip-format"].isString()) { + auto tooltip = config_["tooltip-format"].asString(); + tooltip = fmt::format(fmt::runtime(tooltip), text_, fmt::arg("alt", alt_), + fmt::arg("icon", getIcon(percentage_, alt_)), + fmt::arg("percentage", percentage_)); + label_.set_tooltip_markup(tooltip); } else { if (label_.get_tooltip_markup() != tooltip_) { label_.set_tooltip_markup(tooltip_); } } } - auto classes = label_.get_style_context()->list_classes(); + auto style = label_.get_style_context(); + auto classes = style->list_classes(); for (auto const& c : classes) { if (c == id_) continue; - label_.get_style_context()->remove_class(c); + style->remove_class(c); } for (auto const& c : class_) { - label_.get_style_context()->add_class(c); + style->add_class(c); } - label_.get_style_context()->add_class("flat"); - label_.get_style_context()->add_class("text-button"); + style->add_class("flat"); + style->add_class("text-button"); + style->add_class(MODULE_CLASS); event_box_.show(); } } diff --git a/src/modules/dwl/tags.cpp b/src/modules/dwl/tags.cpp index 7faa5c52..f36ece1d 100644 --- a/src/modules/dwl/tags.cpp +++ b/src/modules/dwl/tags.cpp @@ -93,7 +93,7 @@ Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &con status_manager_{nullptr}, seat_{nullptr}, bar_(bar), - box_{bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0}, + box_{bar.orientation, 0}, output_status_{nullptr} { struct wl_display *display = Client::inst()->wl_display; struct wl_registry *registry = wl_display_get_registry(display); @@ -113,6 +113,7 @@ Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &con if (!id.empty()) { box_.get_style_context()->add_class(id); } + box_.get_style_context()->add_class(MODULE_CLASS); event_box_.add(box_); // Default to 9 tags, cap at 32 diff --git a/src/modules/hyprland/backend.cpp b/src/modules/hyprland/backend.cpp index 3c8313fc..05db94ec 100644 --- a/src/modules/hyprland/backend.cpp +++ b/src/modules/hyprland/backend.cpp @@ -54,22 +54,22 @@ void IPC::startIPC() { return; } - auto file = fdopen(socketfd, "r"); + auto* file = fdopen(socketfd, "r"); while (true) { - char buffer[1024]; // Hyprland socket2 events are max 1024 bytes + std::array buffer; // Hyprland socket2 events are max 1024 bytes - auto recievedCharPtr = fgets(buffer, 1024, file); + auto* receivedCharPtr = fgets(buffer.data(), buffer.size(), file); - if (!recievedCharPtr) { + if (receivedCharPtr == nullptr) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); continue; } - std::string messageRecieved(buffer); - messageRecieved = messageRecieved.substr(0, messageRecieved.find_first_of('\n')); - spdlog::debug("hyprland IPC received {}", messageRecieved); - parseIPC(messageRecieved); + std::string messageReceived(buffer.data()); + messageReceived = messageReceived.substr(0, messageReceived.find_first_of('\n')); + spdlog::debug("hyprland IPC received {}", messageReceived); + parseIPC(messageReceived); std::this_thread::sleep_for(std::chrono::milliseconds(1)); } @@ -78,9 +78,9 @@ void IPC::startIPC() { void IPC::parseIPC(const std::string& ev) { std::string request = ev.substr(0, ev.find_first_of('>')); - std::unique_lock lock(m_callbackMutex); + std::unique_lock lock(callbackMutex_); - for (auto& [eventname, handler] : m_callbacks) { + for (auto& [eventname, handler] : callbacks_) { if (eventname == request) { handler->onEvent(ev); } @@ -88,25 +88,25 @@ void IPC::parseIPC(const std::string& ev) { } void IPC::registerForIPC(const std::string& ev, EventHandler* ev_handler) { - if (!ev_handler) { + if (ev_handler == nullptr) { return; } - std::unique_lock lock(m_callbackMutex); - m_callbacks.emplace_back(ev, ev_handler); + std::unique_lock lock(callbackMutex_); + callbacks_.emplace_back(ev, ev_handler); } void IPC::unregisterForIPC(EventHandler* ev_handler) { - if (!ev_handler) { + if (ev_handler == nullptr) { return; } - std::unique_lock lock(m_callbackMutex); + std::unique_lock lock(callbackMutex_); - for (auto it = m_callbacks.begin(); it != m_callbacks.end();) { + for (auto it = callbacks_.begin(); it != callbacks_.end();) { auto& [eventname, handler] = *it; if (handler == ev_handler) { - m_callbacks.erase(it++); + callbacks_.erase(it++); } else { ++it; } @@ -135,9 +135,9 @@ std::string IPC::getSocket1Reply(const std::string& rq) { } // get the instance signature - auto instanceSig = getenv("HYPRLAND_INSTANCE_SIGNATURE"); + auto* instanceSig = getenv("HYPRLAND_INSTANCE_SIGNATURE"); - if (!instanceSig) { + if (instanceSig == nullptr) { spdlog::error("Hyprland IPC: HYPRLAND_INSTANCE_SIGNATURE was not set! (Is Hyprland running?)"); return ""; } @@ -169,18 +169,18 @@ std::string IPC::getSocket1Reply(const std::string& rq) { return ""; } - char buffer[8192] = {0}; + std::array buffer = {0}; std::string response; do { - sizeWritten = read(serverSocket, buffer, 8192); + sizeWritten = read(serverSocket, buffer.data(), 8192); if (sizeWritten < 0) { spdlog::error("Hyprland IPC: Couldn't read (5)"); close(serverSocket); return ""; } - response.append(buffer, sizeWritten); + response.append(buffer.data(), sizeWritten); } while (sizeWritten > 0); close(serverSocket); @@ -188,7 +188,7 @@ std::string IPC::getSocket1Reply(const std::string& rq) { } Json::Value IPC::getSocket1JsonReply(const std::string& rq) { - return m_parser.parse(getSocket1Reply("j/" + rq)); + return parser_.parse(getSocket1Reply("j/" + rq)); } } // namespace waybar::modules::hyprland diff --git a/src/modules/hyprland/language.cpp b/src/modules/hyprland/language.cpp index 5339ee9e..549faf73 100644 --- a/src/modules/hyprland/language.cpp +++ b/src/modules/hyprland/language.cpp @@ -13,7 +13,7 @@ Language::Language(const std::string& id, const Bar& bar, const Json::Value& con : ALabel(config, "language", id, "{}", 0, true), bar_(bar) { modulesReady = true; - if (!gIPC.get()) { + if (!gIPC) { gIPC = std::make_unique(); } @@ -102,11 +102,11 @@ void Language::initLanguage() { } auto Language::getLayout(const std::string& fullName) -> Layout { - const auto CONTEXT = rxkb_context_new(RXKB_CONTEXT_LOAD_EXOTIC_RULES); - rxkb_context_parse_default_ruleset(CONTEXT); + auto* const context = rxkb_context_new(RXKB_CONTEXT_LOAD_EXOTIC_RULES); + rxkb_context_parse_default_ruleset(context); - rxkb_layout* layout = rxkb_layout_first(CONTEXT); - while (layout) { + rxkb_layout* layout = rxkb_layout_first(context); + while (layout != nullptr) { std::string nameOfLayout = rxkb_layout_get_description(layout); if (nameOfLayout != fullName) { @@ -115,21 +115,20 @@ auto Language::getLayout(const std::string& fullName) -> Layout { } auto name = std::string(rxkb_layout_get_name(layout)); - auto variant_ = rxkb_layout_get_variant(layout); - std::string variant = variant_ == nullptr ? "" : std::string(variant_); + const auto* variantPtr = rxkb_layout_get_variant(layout); + std::string variant = variantPtr == nullptr ? "" : std::string(variantPtr); - auto short_description_ = rxkb_layout_get_brief(layout); - std::string short_description = - short_description_ == nullptr ? "" : std::string(short_description_); + const auto* descriptionPtr = rxkb_layout_get_brief(layout); + std::string description = descriptionPtr == nullptr ? "" : std::string(descriptionPtr); - Layout info = Layout{nameOfLayout, name, variant, short_description}; + Layout info = Layout{nameOfLayout, name, variant, description}; - rxkb_context_unref(CONTEXT); + rxkb_context_unref(context); return info; } - rxkb_context_unref(CONTEXT); + rxkb_context_unref(context); spdlog::debug("hyprland language didn't find matching layout"); diff --git a/src/modules/hyprland/submap.cpp b/src/modules/hyprland/submap.cpp index b9ae9f21..3d8ed699 100644 --- a/src/modules/hyprland/submap.cpp +++ b/src/modules/hyprland/submap.cpp @@ -12,7 +12,7 @@ Submap::Submap(const std::string& id, const Bar& bar, const Json::Value& config) parseConfig(config); - if (!gIPC.get()) { + if (!gIPC) { gIPC = std::make_unique(); } diff --git a/src/modules/hyprland/window.cpp b/src/modules/hyprland/window.cpp index 77723bc0..c7d287e5 100644 --- a/src/modules/hyprland/window.cpp +++ b/src/modules/hyprland/window.cpp @@ -17,9 +17,9 @@ namespace waybar::modules::hyprland { Window::Window(const std::string& id, const Bar& bar, const Json::Value& config) : AAppIconLabel(config, "window", id, "{title}", 0, true), bar_(bar) { modulesReady = true; - separate_outputs = config["separate-outputs"].asBool(); + separateOutputs_ = config["separate-outputs"].asBool(); - if (!gIPC.get()) { + if (!gIPC) { gIPC = std::make_unique(); } @@ -45,18 +45,18 @@ auto Window::update() -> void { // fix ampersands std::lock_guard lg(mutex_); - std::string window_name = waybar::util::sanitize_string(workspace_.last_window_title); - std::string window_address = workspace_.last_window; + std::string windowName = waybar::util::sanitize_string(workspace_.last_window_title); + std::string windowAddress = workspace_.last_window; - window_data_.title = window_name; + windowData_.title = windowName; if (!format_.empty()) { label_.show(); label_.set_markup(waybar::util::rewriteString( - fmt::format(fmt::runtime(format_), fmt::arg("title", window_name), - fmt::arg("initialTitle", window_data_.initial_title), - fmt::arg("class", window_data_.class_name), - fmt::arg("initialClass", window_data_.initial_class_name)), + fmt::format(fmt::runtime(format_), fmt::arg("title", windowName), + fmt::arg("initialTitle", windowData_.initial_title), + fmt::arg("class", windowData_.class_name), + fmt::arg("initialClass", windowData_.initial_class_name)), config_["rewrite"])); } else { label_.hide(); @@ -64,22 +64,22 @@ auto Window::update() -> void { setClass("empty", workspace_.windows == 0); setClass("solo", solo_); - setClass("floating", all_floating_); + setClass("floating", allFloating_); setClass("swallowing", swallowing_); setClass("fullscreen", fullscreen_); - if (!last_solo_class_.empty() && solo_class_ != last_solo_class_) { - if (bar_.window.get_style_context()->has_class(last_solo_class_)) { - bar_.window.get_style_context()->remove_class(last_solo_class_); - spdlog::trace("Removing solo class: {}", last_solo_class_); + if (!lastSoloClass_.empty() && soloClass_ != lastSoloClass_) { + if (bar_.window.get_style_context()->has_class(lastSoloClass_)) { + bar_.window.get_style_context()->remove_class(lastSoloClass_); + spdlog::trace("Removing solo class: {}", lastSoloClass_); } } - if (!solo_class_.empty() && solo_class_ != last_solo_class_) { - bar_.window.get_style_context()->add_class(solo_class_); - spdlog::trace("Adding solo class: {}", solo_class_); + if (!soloClass_.empty() && soloClass_ != lastSoloClass_) { + bar_.window.get_style_context()->add_class(soloClass_); + spdlog::trace("Adding solo class: {}", soloClass_); } - last_solo_class_ = solo_class_; + lastSoloClass_ = soloClass_; AAppIconLabel::update(); } @@ -113,8 +113,12 @@ auto Window::getActiveWorkspace(const std::string& monitorName) -> Workspace { } auto Window::Workspace::parse(const Json::Value& value) -> Window::Workspace { - return Workspace{value["id"].asInt(), value["windows"].asInt(), value["lastwindow"].asString(), - value["lastwindowtitle"].asString()}; + return Workspace{ + value["id"].asInt(), + value["windows"].asInt(), + value["lastwindow"].asString(), + value["lastwindowtitle"].asString(), + }; } auto Window::WindowData::parse(const Json::Value& value) -> Window::WindowData { @@ -127,7 +131,7 @@ auto Window::WindowData::parse(const Json::Value& value) -> Window::WindowData { void Window::queryActiveWorkspace() { std::lock_guard lg(mutex_); - if (separate_outputs) { + if (separateOutputs_) { workspace_ = getActiveWorkspace(this->bar_.output->name); } else { workspace_ = getActiveWorkspace(); @@ -136,31 +140,33 @@ void Window::queryActiveWorkspace() { if (workspace_.windows > 0) { const auto clients = gIPC->getSocket1JsonReply("clients"); assert(clients.isArray()); - auto active_window = std::find_if(clients.begin(), clients.end(), [&](Json::Value window) { + auto activeWindow = std::find_if(clients.begin(), clients.end(), [&](Json::Value window) { return window["address"] == workspace_.last_window; }); - if (active_window == std::end(clients)) { + if (activeWindow == std::end(clients)) { return; } - window_data_ = WindowData::parse(*active_window); - updateAppIconName(window_data_.class_name, window_data_.initial_class_name); - std::vector workspace_windows; - std::copy_if(clients.begin(), clients.end(), std::back_inserter(workspace_windows), + windowData_ = WindowData::parse(*activeWindow); + updateAppIconName(windowData_.class_name, windowData_.initial_class_name); + std::vector workspaceWindows; + std::copy_if(clients.begin(), clients.end(), std::back_inserter(workspaceWindows), [&](Json::Value window) { return window["workspace"]["id"] == workspace_.id && window["mapped"].asBool(); }); - swallowing_ = std::any_of(workspace_windows.begin(), workspace_windows.end(), - [&](Json::Value window) { return !window["swallowing"].isNull(); }); - std::vector visible_windows; - std::copy_if(workspace_windows.begin(), workspace_windows.end(), - std::back_inserter(visible_windows), + swallowing_ = + std::any_of(workspaceWindows.begin(), workspaceWindows.end(), [&](Json::Value window) { + return !window["swallowing"].isNull() && window["swallowing"].asString() != "0x0"; + }); + std::vector visibleWindows; + std::copy_if(workspaceWindows.begin(), workspaceWindows.end(), + std::back_inserter(visibleWindows), [&](Json::Value window) { return !window["hidden"].asBool(); }); - solo_ = 1 == std::count_if(visible_windows.begin(), visible_windows.end(), + solo_ = 1 == std::count_if(visibleWindows.begin(), visibleWindows.end(), [&](Json::Value window) { return !window["floating"].asBool(); }); - all_floating_ = std::all_of(visible_windows.begin(), visible_windows.end(), - [&](Json::Value window) { return window["floating"].asBool(); }); - fullscreen_ = window_data_.fullscreen; + allFloating_ = std::all_of(visibleWindows.begin(), visibleWindows.end(), + [&](Json::Value window) { return window["floating"].asBool(); }); + fullscreen_ = windowData_.fullscreen; // Fullscreen windows look like they are solo if (fullscreen_) { @@ -168,23 +174,23 @@ void Window::queryActiveWorkspace() { } // Grouped windows have a tab bar and therefore don't look fullscreen or solo - if (window_data_.grouped) { + if (windowData_.grouped) { fullscreen_ = false; solo_ = false; } if (solo_) { - solo_class_ = window_data_.class_name; + soloClass_ = windowData_.class_name; } else { - solo_class_ = ""; + soloClass_ = ""; } } else { - window_data_ = WindowData{}; - all_floating_ = false; + windowData_ = WindowData{}; + allFloating_ = false; swallowing_ = false; fullscreen_ = false; solo_ = false; - solo_class_ = ""; + soloClass_ = ""; } } diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index b05ce134..3e393121 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -34,9 +34,7 @@ int Workspaces::windowRewritePriorityFunction(std::string const &window_rule) { } Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value &config) - : AModule(config, "workspaces", id, false, false), - m_bar(bar), - m_box(bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0) { + : AModule(config, "workspaces", id, false, false), m_bar(bar), m_box(bar.orientation, 0) { modulesReady = true; parseConfig(config); @@ -44,12 +42,14 @@ Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value if (!id.empty()) { m_box.get_style_context()->add_class(id); } + m_box.get_style_context()->add_class(MODULE_CLASS); event_box_.add(m_box); if (!gIPC) { gIPC = std::make_unique(); } + setCurrentMonitorId(); init(); registerIpc(); } @@ -65,7 +65,6 @@ auto Workspaces::parseConfig(const Json::Value &config) -> void { for (std::string &name : formatIcons.getMemberNames()) { m_iconsMap.emplace(name, formatIcons[name].asString()); } - m_iconsMap.emplace("", ""); } @@ -113,11 +112,26 @@ auto Workspaces::parseConfig(const Json::Value &config) -> void { } } + if (config_["persistent_workspaces"].isObject()) { + spdlog::warn( + "persistent_workspaces is deprecated. Please change config to use persistent-workspaces."); + } + + if (config_["persistent-workspaces"].isObject() || config_["persistent_workspaces"].isObject()) { + m_persistentWorkspaceConfig = config_["persistent-workspaces"].isObject() + ? config_["persistent-workspaces"] + : config_["persistent_workspaces"]; + } + const Json::Value &formatWindowSeparator = config["format-window-separator"]; m_formatWindowSeparator = formatWindowSeparator.isString() ? formatWindowSeparator.asString() : " "; const Json::Value &windowRewrite = config["window-rewrite"]; + if (!windowRewrite.isObject()) { + spdlog::debug("window-rewrite is not defined or is not an object, using default rules."); + return; + } const Json::Value &windowRewriteDefaultConfig = config["window-rewrite-default"]; std::string windowRewriteDefault = @@ -128,14 +142,15 @@ auto Workspaces::parseConfig(const Json::Value &config) -> void { [this](std::string &window_rule) { return windowRewritePriorityFunction(window_rule); }); } -void Workspaces::registerOrphanWindow(WindowCreationPayload create_window_paylod) { - if (!create_window_paylod.isEmpty(*this)) { - m_orphanWindowMap[create_window_paylod.getAddress()] = create_window_paylod.repr(*this); +void Workspaces::registerOrphanWindow(WindowCreationPayload create_window_payload) { + if (!create_window_payload.isEmpty(*this)) { + m_orphanWindowMap[create_window_payload.getAddress()] = create_window_payload.repr(*this); } } auto Workspaces::registerIpc() -> void { gIPC->registerForIPC("workspace", this); + gIPC->registerForIPC("activespecial", this); gIPC->registerForIPC("createworkspace", this); gIPC->registerForIPC("destroyworkspace", this); gIPC->registerForIPC("focusedmon", this); @@ -145,6 +160,7 @@ auto Workspaces::registerIpc() -> void { gIPC->registerForIPC("closewindow", this); gIPC->registerForIPC("movewindow", this); gIPC->registerForIPC("urgent", this); + gIPC->registerForIPC("configreloaded", this); if (windowRewriteConfigUsesTitle()) { spdlog::info( @@ -176,6 +192,7 @@ void Workspaces::doUpdate() { m_workspacesToCreate.clear(); // get all active workspaces + spdlog::trace("Getting active workspaces"); auto monitors = gIPC->getSocket1JsonReply("monitors"); std::vector visibleWorkspaces; for (Json::Value &monitor : monitors) { @@ -183,11 +200,18 @@ void Workspaces::doUpdate() { if (ws.isObject() && (ws["name"].isString())) { visibleWorkspaces.push_back(ws["name"].asString()); } + auto sws = monitor["specialWorkspace"]; + auto name = sws["name"].asString(); + if (sws.isObject() && (sws["name"].isString()) && !name.empty()) { + visibleWorkspaces.push_back(!name.starts_with("special:") ? name : name.substr(8)); + } } + spdlog::trace("Updating workspace states"); for (auto &workspace : m_workspaces) { // active - workspace->setActive(workspace->name() == m_activeWorkspaceName); + workspace->setActive(workspace->name() == m_activeWorkspaceName || + workspace->name() == m_activeSpecialWorkspaceName); // disable urgency if workspace is active if (workspace->name() == m_activeWorkspaceName && workspace->isUrgent()) { workspace->setUrgent(false); @@ -205,6 +229,7 @@ void Workspaces::doUpdate() { workspace->update(m_format, workspaceIcon); } + spdlog::trace("Updating window count"); bool anyWindowCreated = false; std::vector notCreated; @@ -266,6 +291,8 @@ void Workspaces::onEvent(const std::string &ev) { if (eventName == "workspace") { onWorkspaceActivated(payload); + } else if (eventName == "activespecial") { + onSpecialWorkspaceActivated(payload); } else if (eventName == "destroyworkspace") { onWorkspaceDestroyed(payload); } else if (eventName == "createworkspace") { @@ -286,6 +313,8 @@ void Workspaces::onEvent(const std::string &ev) { onWorkspaceRenamed(payload); } else if (eventName == "windowtitle") { onWindowTitleEvent(payload); + } else if (eventName == "configreloaded") { + onConfigReloaded(); } dp.emit(); @@ -295,6 +324,11 @@ void Workspaces::onWorkspaceActivated(std::string const &payload) { m_activeWorkspaceName = payload; } +void Workspaces::onSpecialWorkspaceActivated(std::string const &payload) { + std::string name(begin(payload), begin(payload) + payload.find_first_of(',')); + m_activeSpecialWorkspaceName = (!name.starts_with("special:") ? name : name.substr(8)); +} + void Workspaces::onWorkspaceDestroyed(std::string const &payload) { if (!isDoubleSpecial(payload)) { m_workspacesToRemove.push_back(payload); @@ -303,22 +337,37 @@ void Workspaces::onWorkspaceDestroyed(std::string const &payload) { void Workspaces::onWorkspaceCreated(std::string const &workspaceName, Json::Value const &clientsData) { - const Json::Value workspacesJson = gIPC->getSocket1JsonReply("workspaces"); + spdlog::debug("Workspace created: {}", workspaceName); + auto const workspacesJson = gIPC->getSocket1JsonReply("workspaces"); if (!isWorkspaceIgnored(workspaceName)) { + auto const workspaceRules = gIPC->getSocket1JsonReply("workspacerules"); for (Json::Value workspaceJson : workspacesJson) { std::string name = workspaceJson["name"].asString(); - if (name == workspaceName && - (allOutputs() || m_bar.output->name == workspaceJson["monitor"].asString()) && - (showSpecial() || !name.starts_with("special")) && !isDoubleSpecial(workspaceName)) { - m_workspacesToCreate.emplace_back(workspaceJson, clientsData); - break; + if (name == workspaceName) { + if ((allOutputs() || m_bar.output->name == workspaceJson["monitor"].asString()) && + (showSpecial() || !name.starts_with("special")) && !isDoubleSpecial(workspaceName)) { + for (Json::Value const &rule : workspaceRules) { + if (rule["workspaceString"].asString() == workspaceName) { + workspaceJson["persistent"] = rule["persistent"].asBool(); + break; + } + } + + m_workspacesToCreate.emplace_back(workspaceJson, clientsData); + break; + } + } else { + extendOrphans(workspaceJson["id"].asInt(), clientsData); } } + } else { + spdlog::trace("Not creating workspace because it is ignored: {}", workspaceName); } } void Workspaces::onWorkspaceMoved(std::string const &payload) { + spdlog::debug("Workspace moved: {}", payload); std::string workspaceName = payload.substr(0, payload.find(',')); std::string monitorName = payload.substr(payload.find(',') + 1); @@ -326,11 +375,13 @@ void Workspaces::onWorkspaceMoved(std::string const &payload) { Json::Value clientsData = gIPC->getSocket1JsonReply("clients"); onWorkspaceCreated(workspaceName, clientsData); } else { + spdlog::debug("Removing workspace because it was moved to another monitor: {}"); onWorkspaceDestroyed(workspaceName); } } void Workspaces::onWorkspaceRenamed(std::string const &payload) { + spdlog::debug("Workspace renamed: {}", payload); std::string workspaceIdStr = payload.substr(0, payload.find(',')); int workspaceId = workspaceIdStr == "special" ? -99 : std::stoi(workspaceIdStr); std::string newName = payload.substr(payload.find(',') + 1); @@ -347,10 +398,19 @@ void Workspaces::onWorkspaceRenamed(std::string const &payload) { } void Workspaces::onMonitorFocused(std::string const &payload) { + spdlog::trace("Monitor focused: {}", payload); m_activeWorkspaceName = payload.substr(payload.find(',') + 1); + + for (Json::Value &monitor : gIPC->getSocket1JsonReply("monitors")) { + if (monitor["name"].asString() == payload.substr(0, payload.find(','))) { + auto name = monitor["specialWorkspace"]["name"].asString(); + m_activeSpecialWorkspaceName = !name.starts_with("special:") ? name : name.substr(8); + } + } } void Workspaces::onWindowOpened(std::string const &payload) { + spdlog::trace("Window opened: {}", payload); updateWindowCount(); size_t lastCommaIdx = 0; size_t nextCommaIdx = payload.find(','); @@ -370,6 +430,7 @@ void Workspaces::onWindowOpened(std::string const &payload) { } void Workspaces::onWindowClosed(std::string const &addr) { + spdlog::trace("Window closed: {}", addr); updateWindowCount(); for (auto &workspace : m_workspaces) { if (workspace->closeWindow(addr)) { @@ -379,6 +440,7 @@ void Workspaces::onWindowClosed(std::string const &addr) { } void Workspaces::onWindowMoved(std::string const &payload) { + spdlog::trace("Window moved: {}", payload); updateWindowCount(); size_t lastCommaIdx = 0; size_t nextCommaIdx = payload.find(','); @@ -417,6 +479,7 @@ void Workspaces::onWindowMoved(std::string const &payload) { } void Workspaces::onWindowTitleEvent(std::string const &payload) { + spdlog::trace("Window title changed: {}", payload); std::optional> inserter; // If the window was an orphan, rename it at the orphan's vector @@ -460,12 +523,19 @@ void Workspaces::onWindowTitleEvent(std::string const &payload) { } } +void Workspaces::onConfigReloaded() { + spdlog::info("Hyprland config reloaded, reinitializing hyprland/workspaces module..."); + init(); +} + void Workspaces::updateWindowCount() { const Json::Value workspacesJson = gIPC->getSocket1JsonReply("workspaces"); for (auto &workspace : m_workspaces) { - auto workspaceJson = std::find_if( - workspacesJson.begin(), workspacesJson.end(), - [&](Json::Value const &x) { return x["name"].asString() == workspace->name(); }); + auto workspaceJson = + std::find_if(workspacesJson.begin(), workspacesJson.end(), [&](Json::Value const &x) { + return x["name"].asString() == workspace->name() || + (workspace->isSpecial() && x["name"].asString() == "special:" + workspace->name()); + }); uint32_t count = 0; if (workspaceJson != workspacesJson.end()) { try { @@ -516,8 +586,11 @@ std::optional Workspace::closeWindow(WindowAddress const &addr) { void Workspaces::createWorkspace(Json::Value const &workspace_data, Json::Value const &clients_data) { - // avoid recreating existing workspaces auto workspaceName = workspace_data["name"].asString(); + spdlog::debug("Creating workspace {}, persistent: {}", workspaceName, + workspace_data["persistent"].asBool() ? "true" : "false"); + + // avoid recreating existing workspaces auto workspace = std::find_if( m_workspaces.begin(), m_workspaces.end(), [workspaceName](std::unique_ptr const &w) { @@ -526,9 +599,8 @@ void Workspaces::createWorkspace(Json::Value const &workspace_data, }); if (workspace != m_workspaces.end()) { - if (workspace_data["persistent"].asBool() and !(*workspace)->isPersistent()) { - (*workspace)->setPersistent(); - } + // don't recreate workspace, but update persistency if necessary + (*workspace)->setPersistent(workspace_data["persistent"].asBool()); return; } @@ -541,6 +613,7 @@ void Workspaces::createWorkspace(Json::Value const &workspace_data, } void Workspaces::removeWorkspace(std::string const &name) { + spdlog::debug("Removing workspace {}", name); auto workspace = std::find_if(m_workspaces.begin(), m_workspaces.end(), [&](std::unique_ptr &x) { return (name.starts_with("special:") && name.substr(8) == x->name()) || name == x->name(); @@ -552,7 +625,7 @@ void Workspaces::removeWorkspace(std::string const &name) { } if ((*workspace)->isPersistent()) { - // don't remove persistent workspaces, createWorkspace will take care of replacement + spdlog::trace("Not removing persistent workspace {}", name); return; } @@ -560,94 +633,106 @@ void Workspaces::removeWorkspace(std::string const &name) { m_workspaces.erase(workspace); } -void Workspaces::fillPersistentWorkspaces() { - if (config_["persistent_workspaces"].isObject()) { - spdlog::warn( - "persistent_workspaces is deprecated. Please change config to use persistent-workspaces."); +Json::Value createPersistentWorkspaceData(std::string const &name, std::string const &monitor) { + spdlog::trace("Creating persistent workspace: {} on monitor {}", name, monitor); + Json::Value workspaceData; + try { + // numbered persistent workspaces get the name as ID + workspaceData["id"] = name == "special" ? -99 : std::stoi(name); + } catch (const std::exception &e) { + // named persistent workspaces start with ID=0 + workspaceData["id"] = 0; } + workspaceData["name"] = name; + workspaceData["monitor"] = monitor; + workspaceData["windows"] = 0; + workspaceData["persistent"] = true; + return workspaceData; +} - if (config_["persistent-workspaces"].isObject() || config_["persistent_workspaces"].isObject()) { - const Json::Value persistentWorkspaces = config_["persistent-workspaces"].isObject() - ? config_["persistent-workspaces"] - : config_["persistent_workspaces"]; - const std::vector keys = persistentWorkspaces.getMemberNames(); +void Workspaces::loadPersistentWorkspacesFromConfig(Json::Value const &clientsJson) { + spdlog::info("Loading persistent workspaces from Waybar config"); + const std::vector keys = m_persistentWorkspaceConfig.getMemberNames(); + std::vector persistentWorkspacesToCreate; - for (const std::string &key : keys) { - // only add if either: - // 1. key is "*" and this monitor is not already defined in the config - // 2. key is the current monitor name - bool canCreate = - (key == "*" && std::find(keys.begin(), keys.end(), m_bar.output->name) == keys.end()) || - key == m_bar.output->name; - const Json::Value &value = persistentWorkspaces[key]; + const std::string currentMonitor = m_bar.output->name; + const bool monitorInConfig = std::find(keys.begin(), keys.end(), currentMonitor) != keys.end(); + for (const std::string &key : keys) { + // only add if either: + // 1. key is the current monitor name + // 2. key is "*" and this monitor is not already defined in the config + bool canCreate = key == currentMonitor || (key == "*" && !monitorInConfig); + const Json::Value &value = m_persistentWorkspaceConfig[key]; + spdlog::trace("Parsing persistent workspace config: {} => {}", key, value.toStyledString()); - if (value.isInt()) { - // value is a number => create that many workspaces for this monitor - if (canCreate) { - int amount = value.asInt(); - spdlog::debug("Creating {} persistent workspaces for monitor {}", amount, - m_bar.output->name); - for (int i = 0; i < amount; i++) { - m_persistentWorkspacesToCreate.emplace_back( - std::to_string(m_monitorId * amount + i + 1)); - } + if (value.isInt()) { + // value is a number => create that many workspaces for this monitor + if (canCreate) { + int amount = value.asInt(); + spdlog::debug("Creating {} persistent workspaces for monitor {}", amount, currentMonitor); + for (int i = 0; i < amount; i++) { + persistentWorkspacesToCreate.emplace_back(std::to_string(m_monitorId * amount + i + 1)); } - } else if (value.isArray() && !value.empty()) { - // value is an array => create defined workspaces for this monitor - if (canCreate) { - for (const Json::Value &workspace : value) { - if (workspace.isInt()) { - spdlog::debug("Creating workspace {} on monitor {}", workspace, m_bar.output->name); - m_persistentWorkspacesToCreate.emplace_back(std::to_string(workspace.asInt())); - } - } - } else { - // key is the workspace and value is array of monitors to create on - for (const Json::Value &monitor : value) { - if (monitor.isString() && monitor.asString() == m_bar.output->name) { - m_persistentWorkspacesToCreate.emplace_back(key); - break; - } + } + } else if (value.isArray() && !value.empty()) { + // value is an array => create defined workspaces for this monitor + if (canCreate) { + for (const Json::Value &workspace : value) { + if (workspace.isInt()) { + spdlog::debug("Creating workspace {} on monitor {}", workspace, currentMonitor); + persistentWorkspacesToCreate.emplace_back(std::to_string(workspace.asInt())); } } } else { - // this workspace should be displayed on all monitors - m_persistentWorkspacesToCreate.emplace_back(key); + // key is the workspace and value is array of monitors to create on + for (const Json::Value &monitor : value) { + if (monitor.isString() && monitor.asString() == currentMonitor) { + persistentWorkspacesToCreate.emplace_back(currentMonitor); + break; + } + } } + } else { + // this workspace should be displayed on all monitors + persistentWorkspacesToCreate.emplace_back(key); + } + } + + for (auto const &workspace : persistentWorkspacesToCreate) { + auto const workspaceData = createPersistentWorkspaceData(workspace, m_bar.output->name); + m_workspacesToCreate.emplace_back(workspaceData, clientsJson); + } +} + +void Workspaces::loadPersistentWorkspacesFromWorkspaceRules(const Json::Value &clientsJson) { + spdlog::info("Loading persistent workspaces from Hyprland workspace rules"); + + auto const workspaceRules = gIPC->getSocket1JsonReply("workspacerules"); + for (Json::Value const &rule : workspaceRules) { + if (!rule["workspaceString"].isString()) { + spdlog::warn("Workspace rules: invalid workspaceString, skipping: {}", rule); + continue; + } + if (!rule["persistent"].asBool()) { + continue; + } + auto const &workspace = rule["workspaceString"].asString(); + auto const &monitor = rule["monitor"].asString(); + // create this workspace persistently if: + // 1. the allOutputs config option is enabled + // 2. the rule's monitor is the current monitor + // 3. no monitor is specified in the rule => assume it needs to be persistent on every monitor + if (allOutputs() || m_bar.output->name == monitor || monitor.empty()) { + // => persistent workspace should be shown on this monitor + auto workspaceData = createPersistentWorkspaceData(workspace, m_bar.output->name); + m_workspacesToCreate.emplace_back(workspaceData, clientsJson); + } else { + m_workspacesToRemove.emplace_back(workspace); } } } -void Workspaces::createPersistentWorkspaces() { - for (const std::string &workspaceName : m_persistentWorkspacesToCreate) { - Json::Value newWorkspace; - try { - // numbered persistent workspaces get the name as ID - newWorkspace["id"] = workspaceName == "special" ? -99 : std::stoi(workspaceName); - } catch (const std::exception &e) { - // named persistent workspaces start with ID=0 - newWorkspace["id"] = 0; - } - newWorkspace["name"] = workspaceName; - newWorkspace["monitor"] = m_bar.output->name; - newWorkspace["windows"] = 0; - newWorkspace["persistent"] = true; - - createWorkspace(newWorkspace); - } -} - -void Workspaces::extendOrphans(int workspaceId, Json::Value const &clientsJson) { - for (const auto &client : clientsJson) { - if (client["workspace"]["id"].asInt() == workspaceId) { - registerOrphanWindow({client}); - } - } -} - -void Workspaces::init() { - m_activeWorkspaceName = (gIPC->getSocket1JsonReply("activeworkspace"))["name"].asString(); - +void Workspaces::setCurrentMonitorId() { // get monitor ID from name (used by persistent workspaces) m_monitorId = 0; auto monitors = gIPC->getSocket1JsonReply("monitors"); @@ -658,29 +743,58 @@ void Workspaces::init() { spdlog::error("Monitor '{}' does not have an ID? Using 0", m_bar.output->name); } else { m_monitorId = (*currentMonitor)["id"].asInt(); + spdlog::trace("Current monitor ID: {}", m_monitorId); + } +} + +void Workspaces::initializeWorkspaces() { + spdlog::debug("Initializing workspaces"); + + // if the workspace rules changed since last initialization, make sure we reset everything: + for (auto &workspace : m_workspaces) { + m_workspacesToRemove.push_back(workspace->name()); } - const Json::Value workspacesJson = gIPC->getSocket1JsonReply("workspaces"); - const Json::Value clientsJson = gIPC->getSocket1JsonReply("clients"); + // get all current workspaces + auto const workspacesJson = gIPC->getSocket1JsonReply("workspaces"); + auto const clientsJson = gIPC->getSocket1JsonReply("clients"); for (Json::Value workspaceJson : workspacesJson) { std::string workspaceName = workspaceJson["name"].asString(); if ((allOutputs() || m_bar.output->name == workspaceJson["monitor"].asString()) && (!workspaceName.starts_with("special") || showSpecial()) && !isWorkspaceIgnored(workspaceName)) { - createWorkspace(workspaceJson, clientsJson); + m_workspacesToCreate.emplace_back(workspaceJson, clientsJson); } else { extendOrphans(workspaceJson["id"].asInt(), clientsJson); } } - fillPersistentWorkspaces(); - createPersistentWorkspaces(); + spdlog::debug("Initializing persistent workspaces"); + if (m_persistentWorkspaceConfig.isObject()) { + // a persistent workspace config is defined, so use that instead of workspace rules + loadPersistentWorkspacesFromConfig(clientsJson); + } else { + // no persistent workspaces config defined, use Hyprland's workspace rules + loadPersistentWorkspacesFromWorkspaceRules(clientsJson); + } +} +void Workspaces::extendOrphans(int workspaceId, Json::Value const &clientsJson) { + spdlog::trace("Extending orphans with workspace {}", workspaceId); + for (const auto &client : clientsJson) { + if (client["workspace"]["id"].asInt() == workspaceId) { + registerOrphanWindow({client}); + } + } +} + +void Workspaces::init() { + m_activeWorkspaceName = (gIPC->getSocket1JsonReply("activeworkspace"))["name"].asString(); + + initializeWorkspaces(); updateWindowCount(); - sortWorkspaces(); - dp.emit(); } @@ -697,7 +811,8 @@ Workspace::Workspace(const Json::Value &workspace_data, Workspaces &workspace_ma m_name(workspace_data["name"].asString()), m_output(workspace_data["monitor"].asString()), // TODO:allow using monitor desc m_windows(workspace_data["windows"].asInt()), - m_active(true) { + m_isActive(true), + m_isPersistent(workspace_data["persistent"].asBool()) { if (m_name.starts_with("name:")) { m_name = m_name.substr(5); } else if (m_name.starts_with("special")) { @@ -705,10 +820,6 @@ Workspace::Workspace(const Json::Value &workspace_data, Workspaces &workspace_ma m_isSpecial = true; } - if (workspace_data.isMember("persistent")) { - m_isPersistent = workspace_data["persistent"].asBool(); - } - m_button.add_events(Gdk::BUTTON_PRESS_MASK); m_button.signal_button_press_event().connect(sigc::mem_fun(*this, &Workspace::handleClicked), false); @@ -814,8 +925,7 @@ void Workspaces::sortWorkspaces() { if (a->id() == -99 || b->id() == -99) { return b->id() == -99; } - // both are 0 (not yet named persistents) / both are named specials (-98 <= ID - // <=-1) + // both are 0 (not yet named persistents) / named specials (-98 <= ID <= -1) return isNameLess; } @@ -834,6 +944,7 @@ void Workspaces::sortWorkspaces() { } std::string &Workspace::selectIcon(std::map &icons_map) { + spdlog::trace("Selecting icon for workspace {}", name()); if (isUrgent()) { auto urgentIconIt = icons_map.find("urgent"); if (urgentIconIt != icons_map.end()) { @@ -892,9 +1003,9 @@ std::string &Workspace::selectIcon(std::map &icons_map bool Workspace::handleClicked(GdkEventButton *bt) const { if (bt->type == GDK_BUTTON_PRESS) { try { - if (id() > 0) { // normal or numbered persistent + if (id() > 0) { // normal gIPC->getSocket1Reply("dispatch workspace " + std::to_string(id())); - } else if (!isSpecial()) { // named + } else if (!isSpecial()) { // named (this includes persistent) gIPC->getSocket1Reply("dispatch workspace name:" + name()); } else if (id() != -99) { // named special gIPC->getSocket1Reply("dispatch togglespecialworkspace " + name()); diff --git a/src/modules/image.cpp b/src/modules/image.cpp index 3c90b557..8274d323 100644 --- a/src/modules/image.cpp +++ b/src/modules/image.cpp @@ -7,6 +7,7 @@ waybar::modules::Image::Image(const std::string& id, const Json::Value& config) if (!id.empty()) { box_.get_style_context()->add_class(id); } + box_.get_style_context()->add_class(MODULE_CLASS); event_box_.add(box_); dp.emit(); diff --git a/src/modules/keyboard_state.cpp b/src/modules/keyboard_state.cpp index 5e5d4acd..18ce0a7c 100644 --- a/src/modules/keyboard_state.cpp +++ b/src/modules/keyboard_state.cpp @@ -81,7 +81,7 @@ auto supportsLockStates(const libevdev* dev) -> bool { waybar::modules::KeyboardState::KeyboardState(const std::string& id, const Bar& bar, const Json::Value& config) : AModule(config, "keyboard-state", id, false, !config["disable-scroll"].asBool()), - box_(bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0), + box_(bar.orientation, 0), numlock_label_(""), capslock_label_(""), numlock_format_(config_["format"].isString() ? config_["format"].asString() @@ -132,6 +132,7 @@ waybar::modules::KeyboardState::KeyboardState(const std::string& id, const Bar& if (!id.empty()) { box_.get_style_context()->add_class(id); } + box_.get_style_context()->add_class(MODULE_CLASS); event_box_.add(box_); if (config_["device-path"].isString()) { diff --git a/src/modules/river/tags.cpp b/src/modules/river/tags.cpp index baa6b7ec..9e7cd5aa 100644 --- a/src/modules/river/tags.cpp +++ b/src/modules/river/tags.cpp @@ -87,7 +87,7 @@ Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &con control_{nullptr}, seat_{nullptr}, bar_(bar), - box_{bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0}, + box_{bar.orientation, 0}, output_status_{nullptr} { struct wl_display *display = Client::inst()->wl_display; struct wl_registry *registry = wl_display_get_registry(display); @@ -111,6 +111,7 @@ Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &con if (!id.empty()) { box_.get_style_context()->add_class(id); } + box_.get_style_context()->add_class(MODULE_CLASS); event_box_.add(box_); // Default to 9 tags, cap at 32 diff --git a/src/modules/sni/tray.cpp b/src/modules/sni/tray.cpp index 09a7ff30..a2c56808 100644 --- a/src/modules/sni/tray.cpp +++ b/src/modules/sni/tray.cpp @@ -6,7 +6,7 @@ namespace waybar::modules::SNI { Tray::Tray(const std::string& id, const Bar& bar, const Json::Value& config) : AModule(config, "tray", id), - box_(bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0), + box_(bar.orientation, 0), watcher_(SNI::Watcher::getInstance()), host_(nb_hosts_, config, bar, std::bind(&Tray::onAdd, this, std::placeholders::_1), std::bind(&Tray::onRemove, this, std::placeholders::_1)) { @@ -15,6 +15,7 @@ Tray::Tray(const std::string& id, const Bar& bar, const Json::Value& config) if (!id.empty()) { box_.get_style_context()->add_class(id); } + box_.get_style_context()->add_class(MODULE_CLASS); if (config_["spacing"].isUInt()) { box_.set_spacing(config_["spacing"].asUInt()); } diff --git a/src/modules/sway/language.cpp b/src/modules/sway/language.cpp index a5860bd0..a005df17 100644 --- a/src/modules/sway/language.cpp +++ b/src/modules/sway/language.cpp @@ -19,6 +19,7 @@ const std::string Language::XKB_ACTIVE_LAYOUT_NAME_KEY = "xkb_active_layout_name Language::Language(const std::string& id, const Json::Value& config) : ALabel(config, "language", id, "{}", 0, true) { + hide_single_ = config["hide-single-layout"].isBool() && config["hide-single-layout"].asBool(); is_variant_displayed = format_.find("{variant}") != std::string::npos; if (format_.find("{}") != std::string::npos || format_.find("{short}") != std::string::npos) { displayed_short_flag |= static_cast(DispayedShortFlag::ShortName); @@ -95,6 +96,10 @@ void Language::onEvent(const struct Ipc::ipc_response& res) { auto Language::update() -> void { std::lock_guard lock(mutex_); + if (hide_single_ && layouts_map_.size() <= 1) { + event_box_.hide(); + return; + } auto display_layout = trim(fmt::format( fmt::runtime(format_), fmt::arg("short", layout_.short_name), fmt::arg("shortDescription", layout_.short_description), fmt::arg("long", layout_.full_name), diff --git a/src/modules/sway/workspaces.cpp b/src/modules/sway/workspaces.cpp index 1a47e478..1bc9f382 100644 --- a/src/modules/sway/workspaces.cpp +++ b/src/modules/sway/workspaces.cpp @@ -24,10 +24,28 @@ int Workspaces::convertWorkspaceNameToNum(std::string name) { return -1; } +int Workspaces::windowRewritePriorityFunction(std::string const &window_rule) { + // Rules that match against title are prioritized + // Rules that don't specify if they're matching against either title or class are deprioritized + bool const hasTitle = window_rule.find("title") != std::string::npos; + bool const hasClass = window_rule.find("class") != std::string::npos; + + if (hasTitle && hasClass) { + return 3; + } + if (hasTitle) { + return 2; + } + if (hasClass) { + return 1; + } + return 0; +} + Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value &config) : AModule(config, "workspaces", id, false, !config["disable-scroll"].asBool()), bar_(bar), - box_(bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0) { + box_(bar.orientation, 0) { if (config["format-icons"]["high-priority-named"].isArray()) { for (auto &it : config["format-icons"]["high-priority-named"]) { high_priority_named_.push_back(it.asString()); @@ -37,11 +55,27 @@ Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value if (!id.empty()) { box_.get_style_context()->add_class(id); } + box_.get_style_context()->add_class(MODULE_CLASS); event_box_.add(box_); + if (config_["format-window-separator"].isString()) { + m_formatWindowSeperator = config_["format-window-separator"].asString(); + } else { + m_formatWindowSeperator = " "; + } + const Json::Value &windowRewrite = config["window-rewrite"]; + + const Json::Value &windowRewriteDefaultConfig = config["window-rewrite-default"]; + m_windowRewriteDefault = + windowRewriteDefaultConfig.isString() ? windowRewriteDefaultConfig.asString() : "?"; + + m_windowRewriteRules = waybar::util::RegexCollection( + windowRewrite, m_windowRewriteDefault, + [this](std::string &window_rule) { return windowRewritePriorityFunction(window_rule); }); ipc_.subscribe(R"(["workspace"])"); + ipc_.subscribe(R"(["window"])"); ipc_.signal_event.connect(sigc::mem_fun(*this, &Workspaces::onEvent)); ipc_.signal_cmd.connect(sigc::mem_fun(*this, &Workspaces::onCmd)); - ipc_.sendCmd(IPC_GET_WORKSPACES); + ipc_.sendCmd(IPC_GET_TREE); if (config["enable-bar-scroll"].asBool()) { auto &window = const_cast(bar_).window; window.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK); @@ -59,26 +93,33 @@ Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value void Workspaces::onEvent(const struct Ipc::ipc_response &res) { try { - ipc_.sendCmd(IPC_GET_WORKSPACES); + ipc_.sendCmd(IPC_GET_TREE); } catch (const std::exception &e) { spdlog::error("Workspaces: {}", e.what()); } } void Workspaces::onCmd(const struct Ipc::ipc_response &res) { - if (res.type == IPC_GET_WORKSPACES) { + if (res.type == IPC_GET_TREE) { try { { std::lock_guard lock(mutex_); auto payload = parser_.parse(res.payload); workspaces_.clear(); - std::copy_if(payload.begin(), payload.end(), std::back_inserter(workspaces_), + std::vector outputs; + std::copy_if(payload["nodes"].begin(), payload["nodes"].end(), std::back_inserter(outputs), [&](const auto &workspace) { return !config_["all-outputs"].asBool() - ? workspace["output"].asString() == bar_.output->name + ? workspace["name"].asString() == bar_.output->name : true; }); + for (auto &output : outputs) { + std::copy(output["nodes"].begin(), output["nodes"].end(), + std::back_inserter(workspaces_)); + std::copy(output["floating_nodes"].begin(), output["floating_nodes"].end(), + std::back_inserter(workspaces_)); + } if (config_["persistent_workspaces"].isObject()) { spdlog::warn( "persistent_workspaces is deprecated. Please change config to use " @@ -203,6 +244,40 @@ bool Workspaces::filterButtons() { return needReorder; } +bool Workspaces::hasFlag(const Json::Value &node, const std::string &flag) { + if (node[flag].asBool()) { + return true; + } + + if (std::any_of(node["nodes"].begin(), node["nodes"].end(), + [&](auto const &e) { return hasFlag(e, flag); })) { + return true; + } + return false; +} + +void Workspaces::updateWindows(const Json::Value &node, std::string &windows) { + auto format = config_["window-format"].asString(); + if ((node["type"].asString() == "con" || node["type"].asString() == "floating_con") && + node["name"].isString()) { + std::string title = g_markup_escape_text(node["name"].asString().c_str(), -1); + std::string windowClass = node["app_id"].asString(); + std::string windowReprKey = fmt::format("class<{}> title<{}>", windowClass, title); + std::string window = m_windowRewriteRules.get(windowReprKey); + // allow result to have formatting + window = + fmt::format(fmt::runtime(window), fmt::arg("name", title), fmt::arg("class", windowClass)); + windows.append(window); + windows.append(m_formatWindowSeperator); + } + for (const Json::Value &child : node["nodes"]) { + updateWindows(child, windows); + } + for (const Json::Value &child : node["floating_nodes"]) { + updateWindows(child, windows); + } +} + auto Workspaces::update() -> void { std::lock_guard lock(mutex_); bool needReorder = filterButtons(); @@ -212,22 +287,25 @@ auto Workspaces::update() -> void { needReorder = true; } auto &button = bit == buttons_.end() ? addButton(*it) : bit->second; - if ((*it)["focused"].asBool()) { + if (needReorder) { + box_.reorder_child(button, it - workspaces_.begin()); + } + if (hasFlag((*it), "focused")) { button.get_style_context()->add_class("focused"); } else { button.get_style_context()->remove_class("focused"); } - if ((*it)["visible"].asBool()) { + if (hasFlag((*it), "visible")) { button.get_style_context()->add_class("visible"); } else { button.get_style_context()->remove_class("visible"); } - if ((*it)["urgent"].asBool()) { + if (hasFlag((*it), "urgent")) { button.get_style_context()->add_class("urgent"); } else { button.get_style_context()->remove_class("urgent"); } - if ((*it)["target_output"].isString()) { + if (hasFlag((*it), "target_output")) { button.get_style_context()->add_class("persistent"); } else { button.get_style_context()->remove_class("persistent"); @@ -241,16 +319,19 @@ auto Workspaces::update() -> void { } else { button.get_style_context()->remove_class("current_output"); } - if (needReorder) { - box_.reorder_child(button, it - workspaces_.begin()); - } std::string output = (*it)["name"].asString(); + std::string windows = ""; + if (config_["window-format"].isString()) { + updateWindows((*it), windows); + } if (config_["format"].isString()) { auto format = config_["format"].asString(); - output = fmt::format(fmt::runtime(format), fmt::arg("icon", getIcon(output, *it)), - fmt::arg("value", output), fmt::arg("name", trimWorkspaceName(output)), - fmt::arg("index", (*it)["num"].asString()), - fmt::arg("output", (*it)["output"].asString())); + output = fmt::format( + fmt::runtime(format), fmt::arg("icon", getIcon(output, *it)), fmt::arg("value", output), + fmt::arg("name", trimWorkspaceName(output)), fmt::arg("index", (*it)["num"].asString()), + fmt::arg("windows", + windows.substr(0, windows.length() - m_formatWindowSeperator.length())), + fmt::arg("output", (*it)["output"].asString())); } if (!config_["disable-markup"].asBool()) { static_cast(button.get_children()[0])->set_markup(output); diff --git a/src/modules/temperature.cpp b/src/modules/temperature.cpp index 5ef2f4c9..accab969 100644 --- a/src/modules/temperature.cpp +++ b/src/modules/temperature.cpp @@ -24,11 +24,16 @@ waybar::modules::Temperature::Temperature(const std::string& id, const Json::Val } } } else if (config_["hwmon-path-abs"].isString() && config_["input-filename"].isString()) { - file_path_ = (*std::filesystem::directory_iterator(config_["hwmon-path-abs"].asString())) - .path() - .string() + - "/" + config_["input-filename"].asString(); - } else { + for (const auto& hwmon : + std::filesystem::directory_iterator(config_["hwmon-path-abs"].asString())) { + if (hwmon.path().filename().string().starts_with("hwmon")) { + file_path_ = hwmon.path().string() + "/" + config_["input-filename"].asString(); + break; + } + } + } + + if (file_path_.empty()) { auto zone = config_["thermal-zone"].isInt() ? config_["thermal-zone"].asInt() : 0; file_path_ = fmt::format("/sys/class/thermal/thermal_zone{}/temp", zone); } diff --git a/src/modules/wlr/taskbar.cpp b/src/modules/wlr/taskbar.cpp index 0eaf264a..2709584b 100644 --- a/src/modules/wlr/taskbar.cpp +++ b/src/modules/wlr/taskbar.cpp @@ -277,7 +277,7 @@ Task::Task(const waybar::Bar &bar, const Json::Value &config, Taskbar *tbar, handle_{tl_handle}, seat_{seat}, id_{global_id++}, - content_{bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0} { + content_{bar.orientation, 0} { zwlr_foreign_toplevel_handle_v1_add_listener(handle_, &toplevel_handle_impl, this); button.set_relief(Gtk::RELIEF_NONE); @@ -730,13 +730,14 @@ static const wl_registry_listener registry_listener_impl = {.global = handle_glo Taskbar::Taskbar(const std::string &id, const waybar::Bar &bar, const Json::Value &config) : waybar::AModule(config, "taskbar", id, false, false), bar_(bar), - box_{bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0}, + box_{bar.orientation, 0}, manager_{nullptr}, seat_{nullptr} { box_.set_name("taskbar"); if (!id.empty()) { box_.get_style_context()->add_class(id); } + box_.get_style_context()->add_class(MODULE_CLASS); box_.get_style_context()->add_class("empty"); event_box_.add(box_); diff --git a/src/modules/wlr/workspace_manager.cpp b/src/modules/wlr/workspace_manager.cpp index ce14b3b5..f556a161 100644 --- a/src/modules/wlr/workspace_manager.cpp +++ b/src/modules/wlr/workspace_manager.cpp @@ -21,9 +21,7 @@ std::map Workspace::icons_map_; WorkspaceManager::WorkspaceManager(const std::string &id, const waybar::Bar &bar, const Json::Value &config) - : waybar::AModule(config, "workspaces", id, false, false), - bar_(bar), - box_(bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0) { + : waybar::AModule(config, "workspaces", id, false, false), bar_(bar), box_(bar.orientation, 0) { auto config_sort_by_name = config_["sort-by-name"]; if (config_sort_by_name.isBool()) { sort_by_name_ = config_sort_by_name.asBool(); @@ -54,6 +52,7 @@ WorkspaceManager::WorkspaceManager(const std::string &id, const waybar::Bar &bar if (!id.empty()) { box_.get_style_context()->add_class(id); } + box_.get_style_context()->add_class(MODULE_CLASS); event_box_.add(box_); add_registry_listener(this); diff --git a/src/util/regex_collection.cpp b/src/util/regex_collection.cpp index df0879c1..704d6545 100644 --- a/src/util/regex_collection.cpp +++ b/src/util/regex_collection.cpp @@ -66,4 +66,4 @@ std::string& RegexCollection::get(std::string& value) { return get(value, matched_any); } -} // namespace waybar::util \ No newline at end of file +} // namespace waybar::util diff --git a/subprojects/cava.wrap b/subprojects/cava.wrap index 73fc9512..19383d11 100644 --- a/subprojects/cava.wrap +++ b/subprojects/cava.wrap @@ -1,7 +1,7 @@ [wrap-file] -directory = cava-0.9.1 -source_url = https://github.com/LukashonakV/cava/archive/0.9.1.tar.gz -source_filename = cava-0.9.1.tar.gz -source_hash = 4df540b7f4892f72e48772929a15bc9ad61e2bce7a084be2df01c72ca5c02333 +directory = cava-0.10.1 +source_url = https://github.com/LukashonakV/cava/archive/0.10.1.tar.gz +source_filename = cava-0.10.1.tar.gz +source_hash = ae8c7339908d6febeac5ab8df4576c03c9fdbca6c8e8975daf9ce68b57038bb5 [provide] cava = cava_dep diff --git a/subprojects/gtk-layer-shell.wrap b/subprojects/gtk-layer-shell.wrap index 555fbcb6..cb730345 100644 --- a/subprojects/gtk-layer-shell.wrap +++ b/subprojects/gtk-layer-shell.wrap @@ -1,5 +1,5 @@ [wrap-file] -directory = gtk-layer-shell-0.4.0 -source_filename = gtk-layer-shell-0.4.0.tar.gz -source_hash = 52fd74d3161fefa5528585ca5a523c3150934961f2284ad010ae54336dad097e -source_url = https://github.com/wmww/gtk-layer-shell/archive/v0.4.0/gtk-layer-shell-0.4.0.tar.gz +directory = gtk-layer-shell-0.8.2 +source_filename = gtk-layer-shell-0.8.2.tar.gz +source_hash = 254dd246303127c5d5236ea640f01a82e35d2d652a48d139dd669c832a0f0dce +source_url = https://github.com/wmww/gtk-layer-shell/archive/v0.8.2/gtk-layer-shell-0.8.2.tar.gz diff --git a/test/css_reload_helper.cpp b/test/css_reload_helper.cpp index 01850bc3..f3888a83 100644 --- a/test/css_reload_helper.cpp +++ b/test/css_reload_helper.cpp @@ -4,7 +4,6 @@ #if __has_include() #include -#include #else #include #endif diff --git a/test/date.cpp b/test/date.cpp index 004d6aa8..d317f98a 100644 --- a/test/date.cpp +++ b/test/date.cpp @@ -7,7 +7,7 @@ #if __has_include() #include -#include +#include #else #include #endif diff --git a/test/main.cpp b/test/main.cpp index daeee69e..15e17b0f 100644 --- a/test/main.cpp +++ b/test/main.cpp @@ -3,8 +3,9 @@ #include #include -#if __has_include() -#include +#if __has_include() +#include +#include #include #else #include diff --git a/test/meson.build b/test/meson.build index 4c71d326..7c922671 100644 --- a/test/meson.build +++ b/test/meson.build @@ -31,5 +31,5 @@ waybar_test = executable( test( 'waybar', waybar_test, - workdir: meson.source_root(), + workdir: meson.project_source_root(), )