diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml index 40fd3126..4a774dbd 100644 --- a/.github/workflows/clang-format.yml +++ b/.github/workflows/clang-format.yml @@ -16,4 +16,5 @@ jobs: with: source: "." extensions: "hpp,h,cpp,c" - clangFormatVersion: 16 + style: "file:.clang-format" + clangFormatVersion: 18 diff --git a/README.md b/README.md index a019eb6f..55a6c7d9 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ - Sway (Workspaces, Binding mode, Focused window name) - River (Mapping mode, Tags, Focused window name) - Hyprland (Window Icons, Workspaces, Focused window name) +- Niri (Workspaces, Focused window name, Language) - DWL (Tags, Focused window name) [requires dwl ipc patch](https://github.com/djpohly/dwl/wiki/ipc) - Tray [#21](https://github.com/Alexays/Waybar/issues/21) - Local time diff --git a/flake.lock b/flake.lock index b8f68f4b..9bd73acc 100644 --- a/flake.lock +++ b/flake.lock @@ -18,11 +18,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1722185531, - "narHash": "sha256-veKR07psFoJjINLC8RK4DiLniGGMgF3QMlS4tb74S6k=", + "lastModified": 1724819573, + "narHash": "sha256-GnR7/ibgIH1vhoy8cYdmXE6iyZqKqFxQSVkFgosBh6w=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "52ec9ac3b12395ad677e8b62106f0b98c1f8569d", + "rev": "71e91c409d1e654808b2621f28a327acfdad8dc2", "type": "github" }, "original": { diff --git a/include/AModule.hpp b/include/AModule.hpp index facb3130..94a88371 100644 --- a/include/AModule.hpp +++ b/include/AModule.hpp @@ -16,7 +16,7 @@ class AModule : public IModule { ~AModule() override; auto update() -> void override; - virtual auto refresh(int shouldRefresh) -> void{}; + virtual auto refresh(int shouldRefresh) -> void {}; operator Gtk::Widget &() override; auto doAction(const std::string &name) -> void override; diff --git a/include/modules/clock.hpp b/include/modules/clock.hpp index c212ec8b..0c62b676 100644 --- a/include/modules/clock.hpp +++ b/include/modules/clock.hpp @@ -51,8 +51,8 @@ class Clock final : public ALabel { day cldBaseDay_{0}; // calendar Cached day. Is used when today is changing(midnight) std::string cldText_{""}; // calendar text to print CldMode cldMode_{CldMode::MONTH}; - auto get_calendar(const year_month_day& today, const year_month_day& ymd, const time_zone* tz) - -> const std::string; + auto get_calendar(const year_month_day& today, const year_month_day& ymd, + const time_zone* tz) -> const std::string; // get local time zone auto local_zone() -> const time_zone*; diff --git a/include/modules/hyprland/workspaces.hpp b/include/modules/hyprland/workspaces.hpp index f5c20f69..a9d56b79 100644 --- a/include/modules/hyprland/workspaces.hpp +++ b/include/modules/hyprland/workspaces.hpp @@ -61,8 +61,8 @@ class Workspaces : public AModule, public EventHandler { // Config void parseConfig(const Json::Value& config); auto populateIconsMap(const Json::Value& formatIcons) -> void; - static auto populateBoolConfig(const Json::Value& config, const std::string& key, bool& member) - -> void; + static auto populateBoolConfig(const Json::Value& config, const std::string& key, + bool& member) -> void; auto populateSortByConfig(const Json::Value& config) -> void; auto populateIgnoreWorkspacesConfig(const Json::Value& config) -> void; auto populateFormatWindowSeparatorConfig(const Json::Value& config) -> void; diff --git a/include/modules/niri/backend.hpp b/include/modules/niri/backend.hpp new file mode 100644 index 00000000..42b9ff7f --- /dev/null +++ b/include/modules/niri/backend.hpp @@ -0,0 +1,52 @@ +#pragma once + +#include +#include +#include +#include + +#include "util/json.hpp" + +namespace waybar::modules::niri { + +class EventHandler { + public: + virtual void onEvent(const Json::Value& ev) = 0; + virtual ~EventHandler() = default; +}; + +class IPC { + public: + IPC() { startIPC(); } + + void registerForIPC(const std::string& ev, EventHandler* ev_handler); + void unregisterForIPC(EventHandler* handler); + + static Json::Value send(const Json::Value& request); + + // The data members are only safe to access while dataMutex_ is locked. + std::lock_guard lockData() { return std::lock_guard(dataMutex_); } + const std::vector& workspaces() const { return workspaces_; } + const std::vector& windows() const { return windows_; } + const std::vector& keyboardLayoutNames() const { return keyboardLayoutNames_; } + unsigned keyboardLayoutCurrent() const { return keyboardLayoutCurrent_; } + + private: + void startIPC(); + static int connectToSocket(); + void parseIPC(const std::string&); + + std::mutex dataMutex_; + std::vector workspaces_; + std::vector windows_; + std::vector keyboardLayoutNames_; + unsigned keyboardLayoutCurrent_; + + util::JsonParser parser_; + std::mutex callbackMutex_; + std::list> callbacks_; +}; + +inline std::unique_ptr gIPC; + +}; // namespace waybar::modules::niri diff --git a/include/modules/niri/language.hpp b/include/modules/niri/language.hpp new file mode 100644 index 00000000..42b90ac4 --- /dev/null +++ b/include/modules/niri/language.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include + +#include "ALabel.hpp" +#include "bar.hpp" +#include "modules/niri/backend.hpp" + +namespace waybar::modules::niri { + +class Language : public ALabel, public EventHandler { + public: + Language(const std::string &, const Bar &, const Json::Value &); + ~Language() override; + void update() override; + + private: + void updateFromIPC(); + void onEvent(const Json::Value &ev) override; + void doUpdate(); + + struct Layout { + std::string full_name; + std::string short_name; + std::string variant; + std::string short_description; + }; + + static Layout getLayout(const std::string &fullName); + + std::mutex mutex_; + const Bar &bar_; + + std::vector layouts_; + unsigned current_idx_; +}; + +} // namespace waybar::modules::niri diff --git a/include/modules/niri/window.hpp b/include/modules/niri/window.hpp new file mode 100644 index 00000000..909ae6f0 --- /dev/null +++ b/include/modules/niri/window.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include +#include + +#include "AAppIconLabel.hpp" +#include "bar.hpp" +#include "modules/niri/backend.hpp" + +namespace waybar::modules::niri { + +class Window : public AAppIconLabel, public EventHandler { + public: + Window(const std::string &, const Bar &, const Json::Value &); + ~Window() override; + void update() override; + + private: + void onEvent(const Json::Value &ev) override; + void doUpdate(); + void setClass(const std::string &className, bool enable); + + const Bar &bar_; + + std::string oldAppId_; +}; + +} // namespace waybar::modules::niri diff --git a/include/modules/niri/workspaces.hpp b/include/modules/niri/workspaces.hpp new file mode 100644 index 00000000..a6850ed1 --- /dev/null +++ b/include/modules/niri/workspaces.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include +#include + +#include "AModule.hpp" +#include "bar.hpp" +#include "modules/niri/backend.hpp" + +namespace waybar::modules::niri { + +class Workspaces : public AModule, public EventHandler { + public: + Workspaces(const std::string &, const Bar &, const Json::Value &); + ~Workspaces() override; + void update() override; + + private: + void onEvent(const Json::Value &ev) override; + void doUpdate(); + Gtk::Button &addButton(const Json::Value &ws); + std::string getIcon(const std::string &value, const Json::Value &ws); + + const Bar &bar_; + Gtk::Box box_; + // Map from niri workspace id to button. + std::unordered_map buttons_; +}; + +} // namespace waybar::modules::niri diff --git a/include/util/clara.hpp b/include/util/clara.hpp index 73fa5415..da7151fe 100644 --- a/include/util/clara.hpp +++ b/include/util/clara.hpp @@ -622,8 +622,8 @@ inline auto convertInto(std::string const &source, bool &target) -> ParserResult } #ifdef CLARA_CONFIG_OPTIONAL_TYPE template -inline auto convertInto(std::string const &source, CLARA_CONFIG_OPTIONAL_TYPE &target) - -> ParserResult { +inline auto convertInto(std::string const &source, + CLARA_CONFIG_OPTIONAL_TYPE &target) -> ParserResult { T temp; auto result = convertInto(source, temp); if (result) target = std::move(temp); @@ -751,8 +751,8 @@ class ParserBase { public: virtual ~ParserBase() = default; virtual auto validate() const -> Result { return Result::ok(); } - virtual auto parse(std::string const &exeName, TokenStream const &tokens) const - -> InternalParseResult = 0; + virtual auto parse(std::string const &exeName, + TokenStream const &tokens) const -> InternalParseResult = 0; virtual auto cardinality() const -> size_t { return 1; } auto parse(Args const &args) const -> InternalParseResult { @@ -1098,8 +1098,8 @@ struct Parser : ParserBase { using ParserBase::parse; - auto parse(std::string const &exeName, TokenStream const &tokens) const - -> InternalParseResult override { + auto parse(std::string const &exeName, + TokenStream const &tokens) const -> InternalParseResult override { struct ParserInfo { ParserBase const *parser = nullptr; size_t count = 0; diff --git a/include/util/format.hpp b/include/util/format.hpp index cf8d706b..c8ed837a 100644 --- a/include/util/format.hpp +++ b/include/util/format.hpp @@ -6,7 +6,7 @@ class pow_format { public: pow_format(long long val, std::string&& unit, bool binary = false) - : val_(val), unit_(unit), binary_(binary){}; + : val_(val), unit_(unit), binary_(binary) {}; long long val_; std::string unit_; diff --git a/man/waybar-backlight.5.scd b/man/waybar-backlight.5.scd index 1f674fc0..5286c2ed 100644 --- a/man/waybar-backlight.5.scd +++ b/man/waybar-backlight.5.scd @@ -38,7 +38,7 @@ The *backlight* module displays the current backlight level. *rotate*: ++ typeof: integer ++ - Positive value to rotate the text label. + Positive value to rotate the text label (in 90 degree increments). *states*: ++ typeof: object ++ diff --git a/man/waybar-battery.5.scd b/man/waybar-battery.5.scd index 4fe9650a..29cb7d6d 100644 --- a/man/waybar-battery.5.scd +++ b/man/waybar-battery.5.scd @@ -69,7 +69,7 @@ The *battery* module displays the current capacity and state (eg. charging) of y *rotate*: ++ typeof: integer++ - Positive value to rotate the text label. + Positive value to rotate the text label (in 90 degree increments). *on-click*: ++ typeof: string ++ diff --git a/man/waybar-bluetooth.5.scd b/man/waybar-bluetooth.5.scd index 1783dab3..bd64f457 100644 --- a/man/waybar-bluetooth.5.scd +++ b/man/waybar-bluetooth.5.scd @@ -54,7 +54,7 @@ Addressed by *bluetooth* *rotate*: ++ typeof: integer ++ - Positive value to rotate the text label. + Positive value to rotate the text label (in 90 degree increments). *max-length*: ++ typeof: integer ++ diff --git a/man/waybar-clock.5.scd b/man/waybar-clock.5.scd index 40aedd15..3947266d 100644 --- a/man/waybar-clock.5.scd +++ b/man/waybar-clock.5.scd @@ -51,7 +51,7 @@ $XDG_CONFIG_HOME/waybar/config ++ |[ *rotate* :[ integer :[ -:[ Positive value to rotate the text label +:[ Positive value to rotate the text label (in 90 degree increments) |[ *on-click* :[ string :[ diff --git a/man/waybar-cpu.5.scd b/man/waybar-cpu.5.scd index fcbd1265..6b13a563 100644 --- a/man/waybar-cpu.5.scd +++ b/man/waybar-cpu.5.scd @@ -43,7 +43,7 @@ The *cpu* module displays the current CPU utilization. *rotate*: ++ typeof: integer ++ - Positive value to rotate the text label. + Positive value to rotate the text label (in 90 degree increments). *states*: ++ typeof: object ++ diff --git a/man/waybar-custom.5.scd b/man/waybar-custom.5.scd index 4cf3c33d..aba1c18f 100644 --- a/man/waybar-custom.5.scd +++ b/man/waybar-custom.5.scd @@ -64,7 +64,7 @@ Addressed by *custom/* *rotate*: ++ typeof: integer ++ - Positive value to rotate the text label. + Positive value to rotate the text label (in 90 degree increments). *max-length*: ++ typeof: integer ++ diff --git a/man/waybar-disk.5.scd b/man/waybar-disk.5.scd index df9ca4e5..1699a511 100644 --- a/man/waybar-disk.5.scd +++ b/man/waybar-disk.5.scd @@ -29,7 +29,7 @@ Addressed by *disk* *rotate*: ++ typeof: integer ++ - Positive value to rotate the text label. + Positive value to rotate the text label (in 90 degree increments). *states*: ++ typeof: object ++ diff --git a/man/waybar-dwl-window.5.scd b/man/waybar-dwl-window.5.scd index c2f5b93e..f185c82c 100644 --- a/man/waybar-dwl-window.5.scd +++ b/man/waybar-dwl-window.5.scd @@ -19,7 +19,7 @@ Addressed by *dwl/window* *rotate*: ++ typeof: integer ++ - Positive value to rotate the text label. + Positive value to rotate the text label (in 90 degree increments). *max-length*: ++ typeof: integer ++ diff --git a/man/waybar-hyprland-submap.5.scd b/man/waybar-hyprland-submap.5.scd index 64398e61..e27138e7 100644 --- a/man/waybar-hyprland-submap.5.scd +++ b/man/waybar-hyprland-submap.5.scd @@ -19,7 +19,7 @@ Addressed by *hyprland/submap* *rotate*: ++ typeof: integer ++ - Positive value to rotate the text label. + Positive value to rotate the text label (in 90 degree increments). *max-length*: ++ typeof: integer ++ diff --git a/man/waybar-idle-inhibitor.5.scd b/man/waybar-idle-inhibitor.5.scd index f7677634..81a097a7 100644 --- a/man/waybar-idle-inhibitor.5.scd +++ b/man/waybar-idle-inhibitor.5.scd @@ -21,7 +21,7 @@ screensaver, also known as "presentation mode". *rotate*: ++ typeof: integer ++ - Positive value to rotate the text label. + Positive value to rotate the text label (in 90 degree increments). *max-length*: ++ typeof: integer ++ diff --git a/man/waybar-inhibitor.5.scd b/man/waybar-inhibitor.5.scd index 679a5c4b..5513cc49 100644 --- a/man/waybar-inhibitor.5.scd +++ b/man/waybar-inhibitor.5.scd @@ -25,7 +25,7 @@ See *systemd-inhibit*(1) for more information. *rotate*: ++ typeof: integer ++ - Positive value to rotate the text label. + Positive value to rotate the text label (in 90 degree increments). *max-length*: ++ typeof: integer ++ diff --git a/man/waybar-jack.5.scd b/man/waybar-jack.5.scd index 573b36c2..180143b7 100644 --- a/man/waybar-jack.5.scd +++ b/man/waybar-jack.5.scd @@ -51,7 +51,7 @@ Addressed by *jack* *rotate*: ++ typeof: integer ++ - Positive value to rotate the text label. + Positive value to rotate the text label (in 90 degree increments). *max-length*: ++ typeof: integer ++ diff --git a/man/waybar-memory.5.scd b/man/waybar-memory.5.scd index 7738c576..5c368ae8 100644 --- a/man/waybar-memory.5.scd +++ b/man/waybar-memory.5.scd @@ -29,7 +29,7 @@ Addressed by *memory* *rotate*: ++ typeof: integer ++ - Positive value to rotate the text label. + Positive value to rotate the text label (in 90 degree increments). *states*: ++ typeof: object ++ diff --git a/man/waybar-mpd.5.scd b/man/waybar-mpd.5.scd index 2f1bdf20..c576a5c0 100644 --- a/man/waybar-mpd.5.scd +++ b/man/waybar-mpd.5.scd @@ -91,7 +91,7 @@ Addressed by *mpd* *rotate*: ++ typeof: integer ++ - Positive value to rotate the text label. + Positive value to rotate the text label (in 90 degree increments). *max-length*: ++ typeof: integer ++ diff --git a/man/waybar-mpris.5.scd b/man/waybar-mpris.5.scd index 455fcb17..380a1a19 100644 --- a/man/waybar-mpris.5.scd +++ b/man/waybar-mpris.5.scd @@ -107,7 +107,7 @@ The *mpris* module displays currently playing media via libplayerctl. *rotate*: ++ typeof: integer ++ - Positive value to rotate the text label. + Positive value to rotate the text label (in 90 degree increments). *max-length*: ++ typeof: integer ++ diff --git a/man/waybar-network.5.scd b/man/waybar-network.5.scd index bd546916..e1cf810d 100644 --- a/man/waybar-network.5.scd +++ b/man/waybar-network.5.scd @@ -58,7 +58,7 @@ Addressed by *network* *rotate*: ++ typeof: integer ++ - Positive value to rotate the text label. + Positive value to rotate the text label (in 90 degree increments). *max-length*: ++ typeof: integer ++ diff --git a/man/waybar-niri-language.5.scd b/man/waybar-niri-language.5.scd new file mode 100644 index 00000000..6895d25c --- /dev/null +++ b/man/waybar-niri-language.5.scd @@ -0,0 +1,58 @@ +waybar-niri-language(5) + +# NAME + +waybar - niri language module + +# DESCRIPTION + +The *language* module displays the currently selected language in niri. + +# CONFIGURATION + +Addressed by *niri/language* + +*format*: ++ + typeof: string ++ + default: {} ++ + The format, how information should be displayed. + +*format-* ++ + typeof: string++ + Provide an alternative name to display per language where is the language of your choosing. Can be passed multiple times with multiple languages as shown by the example below. + +*menu*: ++ + typeof: string ++ + Action that popups the menu. + +*menu-file*: ++ + typeof: string ++ + Location of the menu descriptor file. There need to be an element of type GtkMenu with id *menu* + +*menu-actions*: ++ + typeof: array ++ + The actions corresponding to the buttons of the menu. + +# FORMAT REPLACEMENTS + +*{short}*: Short name of layout (e.g. "us"). Equals to {}. + +*{shortDescription}*: Short description of layout (e.g. "en"). + +*{long}*: Long name of layout (e.g. "English (Dvorak)"). + +*{variant}*: Variant of layout (e.g. "dvorak"). + +# EXAMPLES + +``` +"niri/language": { + "format": "Lang: {long}" + "format-en": "AMERICA, HELL YEAH!" + "format-tr": "As bayrakları" +} +``` + +# STYLE + +- *#language* diff --git a/man/waybar-niri-window.5.scd b/man/waybar-niri-window.5.scd new file mode 100644 index 00000000..9e2e9f63 --- /dev/null +++ b/man/waybar-niri-window.5.scd @@ -0,0 +1,81 @@ +waybar-niri-window(5) + +# NAME + +waybar - niri window module + +# DESCRIPTION + +The *window* module displays the title of the currently focused window in niri. + +# CONFIGURATION + +Addressed by *niri/window* + +*format*: ++ + typeof: string ++ + default: {title} ++ + The format, how information should be displayed. On {} the current window title is displayed. + +*rewrite*: ++ + typeof: object ++ + Rules to rewrite window title. See *rewrite rules*. + +*separate-outputs*: ++ + typeof: bool ++ + Show the active window of the monitor the bar belongs to, instead of the focused window. + +*icon*: ++ + typeof: bool ++ + default: false ++ + Option to hide the application icon. + +*icon-size*: ++ + typeof: integer ++ + default: 24 ++ + Option to change the size of the application icon. + +# FORMAT REPLACEMENTS + +See the output of "niri msg windows" for examples + +*{title}*: The current title of the focused window. + +*{app_id}*: The current app ID of the focused window. + +# REWRITE RULES + +*rewrite* is an object where keys are regular expressions and values are +rewrite rules if the expression matches. Rules may contain references to +captures of the expression. + +Regular expression and replacement follow ECMA-script rules. + +If no expression matches, the title is left unchanged. + +Invalid expressions (e.g., mismatched parentheses) are skipped. + +# EXAMPLES + +``` +"niri/window": { + "format": "{}", + "rewrite": { + "(.*) - Mozilla Firefox": "🌎 $1", + "(.*) - zsh": "> [$1]" + } +} +``` + +# STYLE + +- *#window* +- *window#waybar.empty #window* When no windows are on the workspace + +The following classes are applied to the entire Waybar rather than just the +window widget: + +- *window#waybar.empty* When no windows are in the workspace +- *window#waybar.solo* When only one window is on the workspace +- *window#waybar.* Where *app-id* is the app ID of the only window on + the workspace diff --git a/man/waybar-niri-workspaces.5.scd b/man/waybar-niri-workspaces.5.scd new file mode 100644 index 00000000..50e497cd --- /dev/null +++ b/man/waybar-niri-workspaces.5.scd @@ -0,0 +1,97 @@ +waybar-niri-workspaces(5) + +# NAME + +waybar - niri workspaces module + +# DESCRIPTION + +The *workspaces* module displays the currently used workspaces in niri. + +# CONFIGURATION + +Addressed by *niri/workspaces* + +*all-outputs*: ++ + typeof: bool ++ + default: false ++ + If set to false, workspaces will only be shown on the output they are on. If set to true all workspaces will be shown on every output. + +*format*: ++ + typeof: string ++ + default: {value} ++ + The format, how information should be displayed. + +*format-icons*: ++ + typeof: array ++ + Based on the workspace name, index and state, the corresponding icon gets selected. See *icons*. + +*disable-click*: ++ + typeof: bool ++ + default: false ++ + If set to false, you can click to change workspace. If set to true this behaviour is disabled. + +*disable-markup*: ++ + typeof: bool ++ + default: false ++ + If set to true, button label will escape pango markup. + +*current-only*: ++ + typeof: bool ++ + default: false ++ + If set to true, only the active or focused workspace will be shown. + +*on-update*: ++ + typeof: string ++ + Command to execute when the module is updated. + +# FORMAT REPLACEMENTS + +*{value}*: Name of the workspace, or index for unnamed workspaces, +as defined by niri. + +*{name}*: Name of the workspace for named workspaces. + +*{icon}*: Icon, as defined in *format-icons*. + +*{index}*: Index of the workspace on its output. + +*{output}*: Output where the workspace is located. + +# ICONS + +Additional to workspace name matching, the following *format-icons* can be set. + +- *default*: Will be shown, when no string matches are found. +- *focused*: Will be shown, when workspace is focused. +- *active*: Will be shown, when workspace is active on its output. + +# EXAMPLES + +``` +"niri/workspaces": { + "format": "{icon}", + "format-icons": { + // Named workspaces + // (you need to configure them in niri) + "browser": "", + "discord": "", + "chat": "", + + // Icons by state + "active": "", + "default": "" + } +} +``` + +# Style + +- *#workspaces button* +- *#workspaces button.focused*: The single focused workspace. +- *#workspaces button.active*: The workspace is active (visible) on its output. +- *#workspaces button.empty*: The workspace is empty. +- *#workspaces button.current_output*: The workspace is from the same output as + the bar that it is displayed on. +- *#workspaces button#niri-workspace-*: Workspaces named this, or index + for unnamed workspaces. diff --git a/man/waybar-pulseaudio.5.scd b/man/waybar-pulseaudio.5.scd index 232e84a0..5b38e8b7 100644 --- a/man/waybar-pulseaudio.5.scd +++ b/man/waybar-pulseaudio.5.scd @@ -40,7 +40,7 @@ Additionally, you can control the volume by scrolling *up* or *down* while the c *rotate*: ++ typeof: integer ++ - Positive value to rotate the text label. + Positive value to rotate the text label (in 90 degree increments). *states*: ++ typeof: object ++ diff --git a/man/waybar-river-layout.5.scd b/man/waybar-river-layout.5.scd index 4fb23085..1368bda9 100644 --- a/man/waybar-river-layout.5.scd +++ b/man/waybar-river-layout.5.scd @@ -21,7 +21,7 @@ Addressed by *river/layout* *rotate*: ++ typeof: integer ++ - Positive value to rotate the text label. + Positive value to rotate the text label (in 90 degree increments). *max-length*: ++ typeof: integer ++ diff --git a/man/waybar-river-mode.5.scd b/man/waybar-river-mode.5.scd index 5769a9a2..b992fdaf 100644 --- a/man/waybar-river-mode.5.scd +++ b/man/waybar-river-mode.5.scd @@ -19,7 +19,7 @@ Addressed by *river/mode* *rotate*: ++ typeof: integer ++ - Positive value to rotate the text label. + Positive value to rotate the text label (in 90 degree increments). *max-length*: ++ typeof: integer ++ diff --git a/man/waybar-river-window.5.scd b/man/waybar-river-window.5.scd index 7e661f43..6db9a2fa 100644 --- a/man/waybar-river-window.5.scd +++ b/man/waybar-river-window.5.scd @@ -19,7 +19,7 @@ Addressed by *river/window* *rotate*: ++ typeof: integer ++ - Positive value to rotate the text label. + Positive value to rotate the text label (in 90 degree increments). *max-length*: ++ typeof: integer ++ diff --git a/man/waybar-sndio.5.scd b/man/waybar-sndio.5.scd index f8d1615d..03dfe0af 100644 --- a/man/waybar-sndio.5.scd +++ b/man/waybar-sndio.5.scd @@ -20,7 +20,7 @@ cursor is over the module, and clicking on the module toggles mute. *rotate*: ++ typeof: integer ++ - Positive value to rotate the text label. + Positive value to rotate the text label (in 90 degree increments). *max-length*: ++ typeof: integer ++ diff --git a/man/waybar-sway-mode.5.scd b/man/waybar-sway-mode.5.scd index 1fcf3cf8..8d5d7c2c 100644 --- a/man/waybar-sway-mode.5.scd +++ b/man/waybar-sway-mode.5.scd @@ -19,7 +19,7 @@ Addressed by *sway/mode* *rotate*: ++ typeof: integer ++ - Positive value to rotate the text label. + Positive value to rotate the text label (in 90 degree increments). *max-length*: ++ typeof: integer ++ diff --git a/man/waybar-sway-window.5.scd b/man/waybar-sway-window.5.scd index 037e6b55..6d1e3196 100644 --- a/man/waybar-sway-window.5.scd +++ b/man/waybar-sway-window.5.scd @@ -19,7 +19,7 @@ Addressed by *sway/window* *rotate*: ++ typeof: integer ++ - Positive value to rotate the text label. + Positive value to rotate the text label (in 90 degree increments). *max-length*: ++ typeof: integer ++ diff --git a/man/waybar-temperature.5.scd b/man/waybar-temperature.5.scd index eab4cbb3..541bf3af 100644 --- a/man/waybar-temperature.5.scd +++ b/man/waybar-temperature.5.scd @@ -60,7 +60,7 @@ Addressed by *temperature* *rotate*: ++ typeof: integer ++ - Positive value to rotate the text label. + Positive value to rotate the text label (in 90 degree increments). *max-length*: ++ typeof: integer ++ diff --git a/man/waybar-wireplumber.5.scd b/man/waybar-wireplumber.5.scd index 770ff0d5..9c26ebaf 100644 --- a/man/waybar-wireplumber.5.scd +++ b/man/waybar-wireplumber.5.scd @@ -31,7 +31,7 @@ The *wireplumber* module displays the current volume reported by WirePlumber. *rotate*: ++ typeof: integer ++ - Positive value to rotate the text label. + Positive value to rotate the text label (in 90 degree increments). *states*: ++ typeof: object ++ diff --git a/man/waybar.5.scd.in b/man/waybar.5.scd.in index db546e17..f3a89656 100644 --- a/man/waybar.5.scd.in +++ b/man/waybar.5.scd.in @@ -323,6 +323,9 @@ A group may hide all but one element, showing them only on mouse hover. In order - *waybar-hyprland-submap(5)* - *waybar-hyprland-window(5)* - *waybar-hyprland-workspaces(5)* +- *waybar-niri-language(5)* +- *waybar-niri-window(5)* +- *waybar-niri-workspaces(5)* - *waybar-idle-inhibitor(5)* - *waybar-image(5)* - *waybar-inhibitor(5)* diff --git a/meson.build b/meson.build index 8daa6c9c..42f9da92 100644 --- a/meson.build +++ b/meson.build @@ -1,6 +1,6 @@ project( 'waybar', 'cpp', 'c', - version: '0.10.4', + version: '0.11.0', license: 'MIT', meson_version: '>= 0.59.0', default_options : [ @@ -69,7 +69,7 @@ is_openbsd = host_machine.system() == 'openbsd' thread_dep = dependency('threads') fmt = dependency('fmt', version : ['>=8.1.1'], fallback : ['fmt', 'fmt_dep']) -spdlog = dependency('spdlog', version : ['>=1.10.0'], fallback : ['spdlog', 'spdlog_dep'], default_options : ['external_fmt=enabled']) +spdlog = dependency('spdlog', version : ['>=1.10.0'], fallback : ['spdlog', 'spdlog_dep'], default_options : ['external_fmt=enabled', 'std_format=disabled', 'tests=disabled']) wayland_client = dependency('wayland-client') wayland_cursor = dependency('wayland-cursor') wayland_protos = dependency('wayland-protocols') @@ -318,6 +318,21 @@ if true ) endif +if get_option('niri') + add_project_arguments('-DHAVE_NIRI', language: 'cpp') + src_files += files( + 'src/modules/niri/backend.cpp', + 'src/modules/niri/language.cpp', + 'src/modules/niri/window.cpp', + 'src/modules/niri/workspaces.cpp', + ) + man_files += files( + 'man/waybar-niri-language.5.scd', + 'man/waybar-niri-window.5.scd', + 'man/waybar-niri-workspaces.5.scd', + ) +endif + if libnl.found() and libnlgen.found() add_project_arguments('-DHAVE_LIBNL', language: 'cpp') src_files += files('src/modules/network.cpp') diff --git a/meson_options.txt b/meson_options.txt index fef50839..303ef038 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -19,3 +19,4 @@ option('experimental', type : 'boolean', value : false, description: 'Enable exp option('jack', type: 'feature', value: 'auto', description: 'Enable support for JACK') option('wireplumber', type: 'feature', value: 'auto', description: 'Enable support for WirePlumber') option('cava', type: 'feature', value: 'auto', description: 'Enable support for Cava') +option('niri', type: 'boolean', description: 'Enable support for niri') diff --git a/src/ALabel.cpp b/src/ALabel.cpp index ecb1b7ce..467572f1 100644 --- a/src/ALabel.cpp +++ b/src/ALabel.cpp @@ -45,6 +45,8 @@ ALabel::ALabel(const Json::Value& config, const std::string& name, const std::st if (config_["rotate"].isUInt()) { rotate = config["rotate"].asUInt(); + if (not(rotate == 0 || rotate == 90 || rotate == 180 || rotate == 270)) + spdlog::warn("'rotate' is only supported in 90 degree increments {} is not valid.", rotate); label_.set_angle(rotate); } diff --git a/src/factory.cpp b/src/factory.cpp index ca10ef95..6c2313e3 100644 --- a/src/factory.cpp +++ b/src/factory.cpp @@ -36,6 +36,11 @@ #include "modules/hyprland/window.hpp" #include "modules/hyprland/workspaces.hpp" #endif +#ifdef HAVE_NIRI +#include "modules/niri/language.hpp" +#include "modules/niri/window.hpp" +#include "modules/niri/workspaces.hpp" +#endif #if defined(__FreeBSD__) || defined(__linux__) #include "modules/battery.hpp" #endif @@ -205,6 +210,17 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name, if (ref == "hyprland/workspaces") { return new waybar::modules::hyprland::Workspaces(id, bar_, config_[name]); } +#endif +#ifdef HAVE_NIRI + if (ref == "niri/language") { + return new waybar::modules::niri::Language(id, bar_, config_[name]); + } + if (ref == "niri/window") { + return new waybar::modules::niri::Window(id, bar_, config_[name]); + } + if (ref == "niri/workspaces") { + return new waybar::modules::niri::Workspaces(id, bar_, config_[name]); + } #endif if (ref == "idle_inhibitor") { return new waybar::modules::IdleInhibitor(id, bar_, config_[name]); diff --git a/src/modules/bluetooth.cpp b/src/modules/bluetooth.cpp index 06475a2e..c8f1f996 100644 --- a/src/modules/bluetooth.cpp +++ b/src/modules/bluetooth.cpp @@ -49,8 +49,8 @@ auto getBoolProperty(GDBusProxy* proxy, const char* property_name) -> bool { return false; } -auto getOptionalStringProperty(GDBusProxy* proxy, const char* property_name) - -> std::optional { +auto getOptionalStringProperty(GDBusProxy* proxy, + const char* property_name) -> std::optional { auto gvar = g_dbus_proxy_get_cached_property(proxy, property_name); if (gvar) { std::string property_value = g_variant_get_string(gvar, NULL); @@ -345,8 +345,8 @@ auto waybar::modules::Bluetooth::onInterfaceAddedOrRemoved(GDBusObjectManager* m auto waybar::modules::Bluetooth::onInterfaceProxyPropertiesChanged( GDBusObjectManagerClient* manager, GDBusObjectProxy* object_proxy, GDBusProxy* interface_proxy, - GVariant* changed_properties, const gchar* const* invalidated_properties, gpointer user_data) - -> void { + GVariant* changed_properties, const gchar* const* invalidated_properties, + gpointer user_data) -> void { std::string interface_name = g_dbus_proxy_get_interface_name(interface_proxy); std::string object_path = g_dbus_object_get_object_path(G_DBUS_OBJECT(object_proxy)); @@ -395,8 +395,8 @@ auto waybar::modules::Bluetooth::getDeviceBatteryPercentage(GDBusObject* object) return std::nullopt; } -auto waybar::modules::Bluetooth::getDeviceProperties(GDBusObject* object, DeviceInfo& device_info) - -> bool { +auto waybar::modules::Bluetooth::getDeviceProperties(GDBusObject* object, + DeviceInfo& device_info) -> bool { GDBusProxy* proxy_device = G_DBUS_PROXY(g_dbus_object_get_interface(object, "org.bluez.Device1")); if (proxy_device != NULL) { @@ -462,9 +462,8 @@ auto waybar::modules::Bluetooth::findCurController() -> std::optional& connected_devices) - -> void { +auto waybar::modules::Bluetooth::findConnectedDevices( + const std::string& cur_controller_path, std::vector& connected_devices) -> void { GList* objects = g_dbus_object_manager_get_objects(manager_.get()); for (GList* l = objects; l != NULL; l = l->next) { GDBusObject* object = G_DBUS_OBJECT(l->data); diff --git a/src/modules/clock.cpp b/src/modules/clock.cpp index 7a4cb9c2..db2979eb 100644 --- a/src/modules/clock.cpp +++ b/src/modules/clock.cpp @@ -198,8 +198,8 @@ const unsigned cldRowsInMonth(const year_month& ym, const weekday& firstdow) { return 2u + ceil((weekday{ym / 1} - firstdow) + ((ym / last).day() - day{0})).count(); } -auto cldGetWeekForLine(const year_month& ym, const weekday& firstdow, const unsigned line) - -> const year_month_weekday { +auto cldGetWeekForLine(const year_month& ym, const weekday& firstdow, + const unsigned line) -> const year_month_weekday { unsigned index{line - 2}; if (weekday{ym / 1} == firstdow) ++index; return ym / firstdow[index]; diff --git a/src/modules/dwl/tags.cpp b/src/modules/dwl/tags.cpp index 085b8224..f8b250c8 100644 --- a/src/modules/dwl/tags.cpp +++ b/src/modules/dwl/tags.cpp @@ -53,8 +53,8 @@ static void set_layout(void *data, zdwl_ipc_output_v2 *zdwl_output_v2, uint32_t // Intentionally empty } -static void appid(void *data, zdwl_ipc_output_v2 *zdwl_output_v2, const char *appid){ - // Intentionally empty +static void appid(void *data, zdwl_ipc_output_v2 *zdwl_output_v2, const char *appid) { + // Intentionally empty }; static const zdwl_ipc_output_v2_listener output_status_listener_impl{ diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index 047703cc..13364f3f 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -590,8 +590,8 @@ auto Workspaces::populateIconsMap(const Json::Value &formatIcons) -> void { m_iconsMap.emplace("", ""); } -auto Workspaces::populateBoolConfig(const Json::Value &config, const std::string &key, bool &member) - -> void { +auto Workspaces::populateBoolConfig(const Json::Value &config, const std::string &key, + bool &member) -> void { const auto &configValue = config[key]; if (configValue.isBool()) { member = configValue.asBool(); diff --git a/src/modules/image.cpp b/src/modules/image.cpp index 8274d323..71e93b94 100644 --- a/src/modules/image.cpp +++ b/src/modules/image.cpp @@ -42,7 +42,6 @@ void waybar::modules::Image::refresh(int sig) { } auto waybar::modules::Image::update() -> void { - Glib::RefPtr pixbuf; if (config_["path"].isString()) { path_ = config_["path"].asString(); } else if (config_["exec"].isString()) { @@ -51,19 +50,24 @@ auto waybar::modules::Image::update() -> void { } else { path_ = ""; } - if (Glib::file_test(path_, Glib::FILE_TEST_EXISTS)) - pixbuf = Gdk::Pixbuf::create_from_file(path_, size_, size_); - else - pixbuf = {}; - if (pixbuf) { + if (Glib::file_test(path_, Glib::FILE_TEST_EXISTS)) { + Glib::RefPtr pixbuf; + + int scaled_icon_size = size_ * image_.get_scale_factor(); + pixbuf = Gdk::Pixbuf::create_from_file(path_, scaled_icon_size, scaled_icon_size); + + auto surface = Gdk::Cairo::create_surface_from_pixbuf(pixbuf, image_.get_scale_factor(), + image_.get_window()); + image_.set(surface); + image_.show(); + if (tooltipEnabled() && !tooltip_.empty()) { if (box_.get_tooltip_markup() != tooltip_) { box_.set_tooltip_markup(tooltip_); } } - image_.set(pixbuf); - image_.show(); + box_.get_style_context()->remove_class("empty"); } else { image_.clear(); diff --git a/src/modules/memory/bsd.cpp b/src/modules/memory/bsd.cpp index 67f9fed7..1d970e8a 100644 --- a/src/modules/memory/bsd.cpp +++ b/src/modules/memory/bsd.cpp @@ -21,13 +21,13 @@ static uint64_t get_total_memory() { u_long physmem; #endif int mib[] = { - CTL_HW, + CTL_HW, #if defined(HW_MEMSIZE) - HW_MEMSIZE, + HW_MEMSIZE, #elif defined(HW_PHYSMEM64) - HW_PHYSMEM64, + HW_PHYSMEM64, #else - HW_PHYSMEM, + HW_PHYSMEM, #endif }; u_int miblen = sizeof(mib) / sizeof(mib[0]); diff --git a/src/modules/niri/backend.cpp b/src/modules/niri/backend.cpp new file mode 100644 index 00000000..383bf113 --- /dev/null +++ b/src/modules/niri/backend.cpp @@ -0,0 +1,261 @@ +#include "modules/niri/backend.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "giomm/datainputstream.h" +#include "giomm/dataoutputstream.h" +#include "giomm/unixinputstream.h" +#include "giomm/unixoutputstream.h" + +namespace waybar::modules::niri { + +int IPC::connectToSocket() { + const char *socket_path = getenv("NIRI_SOCKET"); + + if (socket_path == nullptr) { + spdlog::warn("Niri is not running, niri IPC will not be available."); + return -1; + } + + struct sockaddr_un addr; + int socketfd = socket(AF_UNIX, SOCK_STREAM, 0); + + if (socketfd == -1) { + throw std::runtime_error("socketfd failed"); + } + + addr.sun_family = AF_UNIX; + + strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1); + + addr.sun_path[sizeof(addr.sun_path) - 1] = 0; + + int l = sizeof(struct sockaddr_un); + + if (connect(socketfd, (struct sockaddr *)&addr, l) == -1) { + close(socketfd); + throw std::runtime_error("unable to connect"); + } + + return socketfd; +} + +void IPC::startIPC() { + // will start IPC and relay events to parseIPC + + std::thread([&]() { + int socketfd; + try { + socketfd = connectToSocket(); + } catch (std::exception &e) { + spdlog::error("Niri IPC: failed to start, reason: {}", e.what()); + return; + } + if (socketfd == -1) return; + + spdlog::info("Niri IPC starting"); + + auto unix_istream = Gio::UnixInputStream::create(socketfd, true); + auto unix_ostream = Gio::UnixOutputStream::create(socketfd, false); + auto istream = Gio::DataInputStream::create(unix_istream); + auto ostream = Gio::DataOutputStream::create(unix_ostream); + + if (!ostream->put_string("\"EventStream\"\n") || !ostream->flush()) { + spdlog::error("Niri IPC: failed to start event stream"); + return; + } + + std::string line; + if (!istream->read_line(line) || line != R"({"Ok":"Handled"})") { + spdlog::error("Niri IPC: failed to start event stream"); + return; + } + + while (istream->read_line(line)) { + spdlog::debug("Niri IPC: received {}", line); + + try { + parseIPC(line); + } catch (std::exception &e) { + spdlog::warn("Failed to parse IPC message: {}, reason: {}", line, e.what()); + } catch (...) { + throw; + } + + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + }).detach(); +} + +void IPC::parseIPC(const std::string &line) { + const auto ev = parser_.parse(line); + const auto members = ev.getMemberNames(); + if (members.size() != 1) throw std::runtime_error("Event must have a single member"); + + { + auto lock = lockData(); + + if (const auto &payload = ev["WorkspacesChanged"]) { + workspaces_.clear(); + const auto &values = payload["workspaces"]; + std::copy(values.begin(), values.end(), std::back_inserter(workspaces_)); + + std::sort(workspaces_.begin(), workspaces_.end(), [](const auto &a, const auto &b) { + const auto &aOutput = a["output"].asString(); + const auto &bOutput = b["output"].asString(); + const auto aIdx = a["idx"].asUInt(); + const auto bIdx = b["idx"].asUInt(); + if (aOutput == bOutput) return aIdx < bIdx; + return aOutput < bOutput; + }); + } else if (const auto &payload = ev["WorkspaceActivated"]) { + const auto id = payload["id"].asUInt64(); + const auto focused = payload["focused"].asBool(); + auto it = std::find_if(workspaces_.begin(), workspaces_.end(), + [id](const auto &ws) { return ws["id"].asUInt64() == id; }); + if (it != workspaces_.end()) { + const auto &ws = *it; + const auto &output = ws["output"].asString(); + for (auto &ws : workspaces_) { + const auto got_activated = (ws["id"].asUInt64() == id); + if (ws["output"] == output) ws["is_active"] = got_activated; + + if (focused) ws["is_focused"] = got_activated; + } + } else { + spdlog::error("Activated unknown workspace"); + } + } else if (const auto &payload = ev["WorkspaceActiveWindowChanged"]) { + const auto workspaceId = payload["workspace_id"].asUInt64(); + auto it = std::find_if(workspaces_.begin(), workspaces_.end(), [workspaceId](const auto &ws) { + return ws["id"].asUInt64() == workspaceId; + }); + if (it != workspaces_.end()) { + auto &ws = *it; + ws["active_window_id"] = payload["active_window_id"]; + } else { + spdlog::error("Active window changed on unknown workspace"); + } + } else if (const auto &payload = ev["KeyboardLayoutsChanged"]) { + const auto &layouts = payload["keyboard_layouts"]; + const auto &names = layouts["names"]; + keyboardLayoutCurrent_ = layouts["current_idx"].asUInt(); + + keyboardLayoutNames_.clear(); + for (const auto &fullName : names) keyboardLayoutNames_.push_back(fullName.asString()); + } else if (const auto &payload = ev["KeyboardLayoutSwitched"]) { + keyboardLayoutCurrent_ = payload["idx"].asUInt(); + } else if (const auto &payload = ev["WindowsChanged"]) { + windows_.clear(); + const auto &values = payload["windows"]; + std::copy(values.begin(), values.end(), std::back_inserter(windows_)); + } else if (const auto &payload = ev["WindowOpenedOrChanged"]) { + const auto &window = payload["window"]; + const auto id = window["id"].asUInt64(); + auto it = std::find_if(windows_.begin(), windows_.end(), + [id](const auto &win) { return win["id"].asUInt64() == id; }); + if (it == windows_.end()) { + windows_.push_back(window); + + if (window["is_focused"].asBool()) { + for (auto &win : windows_) { + win["is_focused"] = win["id"].asUInt64() == id; + } + } + } else { + *it = window; + } + } else if (const auto &payload = ev["WindowClosed"]) { + const auto id = payload["id"].asUInt64(); + auto it = std::find_if(windows_.begin(), windows_.end(), + [id](const auto &win) { return win["id"].asUInt64() == id; }); + if (it != windows_.end()) { + windows_.erase(it); + } else { + spdlog::error("Unknown window closed"); + } + } else if (const auto &payload = ev["WindowFocusChanged"]) { + const auto focused = !payload["id"].isNull(); + const auto id = payload["id"].asUInt64(); + for (auto &win : windows_) { + win["is_focused"] = focused && win["id"].asUInt64() == id; + } + } + } + + std::unique_lock lock(callbackMutex_); + + for (auto &[eventname, handler] : callbacks_) { + if (eventname == members[0]) { + handler->onEvent(ev); + } + } +} + +void IPC::registerForIPC(const std::string &ev, EventHandler *ev_handler) { + if (ev_handler == nullptr) { + return; + } + + std::unique_lock lock(callbackMutex_); + callbacks_.emplace_back(ev, ev_handler); +} + +void IPC::unregisterForIPC(EventHandler *ev_handler) { + if (ev_handler == nullptr) { + return; + } + + std::unique_lock lock(callbackMutex_); + + for (auto it = callbacks_.begin(); it != callbacks_.end();) { + auto &[eventname, handler] = *it; + if (handler == ev_handler) { + it = callbacks_.erase(it); + } else { + ++it; + } + } +} + +Json::Value IPC::send(const Json::Value &request) { + int socketfd = connectToSocket(); + if (socketfd == -1) throw std::runtime_error("Niri is not running"); + + auto unix_istream = Gio::UnixInputStream::create(socketfd, true); + auto unix_ostream = Gio::UnixOutputStream::create(socketfd, false); + auto istream = Gio::DataInputStream::create(unix_istream); + auto ostream = Gio::DataOutputStream::create(unix_ostream); + + // Niri needs the request on a single line. + Json::StreamWriterBuilder builder; + builder["indentation"] = ""; + std::unique_ptr writer(builder.newStreamWriter()); + std::ostringstream oss; + writer->write(request, &oss); + oss << '\n'; + + if (!ostream->put_string(oss.str()) || !ostream->flush()) + throw std::runtime_error("error writing to niri socket"); + + std::string line; + if (!istream->read_line(line)) throw std::runtime_error("error reading from niri socket"); + + std::istringstream iss(std::move(line)); + Json::Value response; + iss >> response; + return response; +} + +} // namespace waybar::modules::niri diff --git a/src/modules/niri/language.cpp b/src/modules/niri/language.cpp new file mode 100644 index 00000000..1e4d6d10 --- /dev/null +++ b/src/modules/niri/language.cpp @@ -0,0 +1,136 @@ +#include "modules/niri/language.hpp" + +#include +#include +#include + +#include "util/string.hpp" + +namespace waybar::modules::niri { + +Language::Language(const std::string &id, const Bar &bar, const Json::Value &config) + : ALabel(config, "language", id, "{}", 0, true), bar_(bar) { + label_.hide(); + + if (!gIPC) gIPC = std::make_unique(); + + gIPC->registerForIPC("KeyboardLayoutsChanged", this); + gIPC->registerForIPC("KeyboardLayoutSwitched", this); + + updateFromIPC(); + dp.emit(); +} + +Language::~Language() { + gIPC->unregisterForIPC(this); + // wait for possible event handler to finish + std::lock_guard lock(mutex_); +} + +void Language::updateFromIPC() { + std::lock_guard lock(mutex_); + auto ipcLock = gIPC->lockData(); + + layouts_.clear(); + for (const auto &fullName : gIPC->keyboardLayoutNames()) layouts_.push_back(getLayout(fullName)); + + current_idx_ = gIPC->keyboardLayoutCurrent(); +} + +/** + * Language::doUpdate - update workspaces in UI thread. + * + * Note: some member fields are modified by both UI thread and event listener thread, use mutex_ to + * protect these member fields, and lock should released before calling ALabel::update(). + */ +void Language::doUpdate() { + std::lock_guard lock(mutex_); + + if (layouts_.size() <= current_idx_) { + spdlog::error("niri language layout index out of bounds"); + label_.hide(); + return; + } + const auto &layout = layouts_[current_idx_]; + + spdlog::debug("niri language update with full name {}", layout.full_name); + spdlog::debug("niri language update with short name {}", layout.short_name); + spdlog::debug("niri language update with short description {}", layout.short_description); + spdlog::debug("niri language update with variant {}", layout.variant); + + std::string layoutName = std::string{}; + if (config_.isMember("format-" + layout.short_description + "-" + layout.variant)) { + const auto propName = "format-" + layout.short_description + "-" + layout.variant; + layoutName = fmt::format(fmt::runtime(format_), config_[propName].asString()); + } else if (config_.isMember("format-" + layout.short_description)) { + const auto propName = "format-" + layout.short_description; + layoutName = fmt::format(fmt::runtime(format_), config_[propName].asString()); + } else { + layoutName = trim(fmt::format(fmt::runtime(format_), fmt::arg("long", layout.full_name), + fmt::arg("short", layout.short_name), + fmt::arg("shortDescription", layout.short_description), + fmt::arg("variant", layout.variant))); + } + + spdlog::debug("niri language formatted layout name {}", layoutName); + + if (!format_.empty()) { + label_.show(); + label_.set_markup(layoutName); + } else { + label_.hide(); + } +} + +void Language::update() { + doUpdate(); + ALabel::update(); +} + +void Language::onEvent(const Json::Value &ev) { + if (ev["KeyboardLayoutsChanged"]) { + updateFromIPC(); + } else if (ev["KeyboardLayoutSwitched"]) { + std::lock_guard lock(mutex_); + auto ipcLock = gIPC->lockData(); + current_idx_ = gIPC->keyboardLayoutCurrent(); + } + + dp.emit(); +} + +Language::Layout Language::getLayout(const std::string &fullName) { + 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 != nullptr) { + std::string nameOfLayout = rxkb_layout_get_description(layout); + + if (nameOfLayout != fullName) { + layout = rxkb_layout_next(layout); + continue; + } + + auto name = std::string(rxkb_layout_get_name(layout)); + const auto *variantPtr = rxkb_layout_get_variant(layout); + std::string variant = variantPtr == nullptr ? "" : std::string(variantPtr); + + const auto *descriptionPtr = rxkb_layout_get_brief(layout); + std::string description = descriptionPtr == nullptr ? "" : std::string(descriptionPtr); + + Layout info = Layout{nameOfLayout, name, variant, description}; + + rxkb_context_unref(context); + + return info; + } + + rxkb_context_unref(context); + + spdlog::debug("niri language didn't find matching layout for {}", fullName); + + return Layout{"", "", "", ""}; +} + +} // namespace waybar::modules::niri diff --git a/src/modules/niri/window.cpp b/src/modules/niri/window.cpp new file mode 100644 index 00000000..6e6fd36f --- /dev/null +++ b/src/modules/niri/window.cpp @@ -0,0 +1,109 @@ +#include "modules/niri/window.hpp" + +#include +#include +#include + +#include "util/rewrite_string.hpp" +#include "util/sanitize_str.hpp" + +namespace waybar::modules::niri { + +Window::Window(const std::string &id, const Bar &bar, const Json::Value &config) + : AAppIconLabel(config, "window", id, "{title}", 0, true), bar_(bar) { + if (!gIPC) gIPC = std::make_unique(); + + gIPC->registerForIPC("WindowsChanged", this); + gIPC->registerForIPC("WindowOpenedOrChanged", this); + gIPC->registerForIPC("WindowClosed", this); + gIPC->registerForIPC("WindowFocusChanged", this); + + dp.emit(); +} + +Window::~Window() { gIPC->unregisterForIPC(this); } + +void Window::onEvent(const Json::Value &ev) { dp.emit(); } + +void Window::doUpdate() { + auto ipcLock = gIPC->lockData(); + + const auto &windows = gIPC->windows(); + const auto &workspaces = gIPC->workspaces(); + + const auto separateOutputs = config_["separate-outputs"].asBool(); + const auto ws_it = std::find_if(workspaces.cbegin(), workspaces.cend(), [&](const auto &ws) { + if (separateOutputs) { + return ws["is_active"].asBool() && ws["output"].asString() == bar_.output->name; + } + + return ws["is_focused"].asBool(); + }); + + std::vector::const_iterator it; + if (ws_it == workspaces.cend() || (*ws_it)["active_window_id"].isNull()) { + it = windows.cend(); + } else { + const auto id = (*ws_it)["active_window_id"].asUInt64(); + it = std::find_if(windows.cbegin(), windows.cend(), + [id](const auto &win) { return win["id"].asUInt64() == id; }); + } + + setClass("empty", ws_it == workspaces.cend() || (*ws_it)["active_window_id"].isNull()); + + if (it != windows.cend()) { + const auto &window = *it; + + const auto title = window["title"].asString(); + const auto appId = window["app_id"].asString(); + const auto sanitizedTitle = waybar::util::sanitize_string(title); + const auto sanitizedAppId = waybar::util::sanitize_string(appId); + + label_.show(); + label_.set_markup(waybar::util::rewriteString( + fmt::format(fmt::runtime(format_), fmt::arg("title", sanitizedTitle), + fmt::arg("app_id", sanitizedAppId)), + config_["rewrite"])); + + updateAppIconName(appId, ""); + + if (tooltipEnabled()) label_.set_tooltip_text(title); + + const auto id = window["id"].asUInt64(); + const auto workspaceId = window["workspace_id"].asUInt64(); + const auto isSolo = std::none_of(windows.cbegin(), windows.cend(), [&](const auto &win) { + return win["id"].asUInt64() != id && win["workspace_id"].asUInt64() == workspaceId; + }); + setClass("solo", isSolo); + if (!appId.empty()) setClass(appId, isSolo); + + if (oldAppId_ != appId) { + if (!oldAppId_.empty()) setClass(oldAppId_, false); + oldAppId_ = appId; + } + } else { + label_.hide(); + updateAppIconName("", ""); + setClass("solo", false); + if (!oldAppId_.empty()) setClass(oldAppId_, false); + oldAppId_.clear(); + } +} + +void Window::update() { + doUpdate(); + AAppIconLabel::update(); +} + +void Window::setClass(const std::string &className, bool enable) { + auto styleContext = bar_.window.get_style_context(); + if (enable) { + if (!styleContext->has_class(className)) { + styleContext->add_class(className); + } + } else { + styleContext->remove_class(className); + } +} + +} // namespace waybar::modules::niri diff --git a/src/modules/niri/workspaces.cpp b/src/modules/niri/workspaces.cpp new file mode 100644 index 00000000..d2fcad5d --- /dev/null +++ b/src/modules/niri/workspaces.cpp @@ -0,0 +1,186 @@ +#include "modules/niri/workspaces.hpp" + +#include +#include +#include + +namespace waybar::modules::niri { + +Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value &config) + : AModule(config, "workspaces", id, false, false), bar_(bar), box_(bar.orientation, 0) { + box_.set_name("workspaces"); + if (!id.empty()) { + box_.get_style_context()->add_class(id); + } + box_.get_style_context()->add_class(MODULE_CLASS); + event_box_.add(box_); + + if (!gIPC) gIPC = std::make_unique(); + + gIPC->registerForIPC("WorkspacesChanged", this); + gIPC->registerForIPC("WorkspaceActivated", this); + gIPC->registerForIPC("WorkspaceActiveWindowChanged", this); + + dp.emit(); +} + +Workspaces::~Workspaces() { gIPC->unregisterForIPC(this); } + +void Workspaces::onEvent(const Json::Value &ev) { dp.emit(); } + +void Workspaces::doUpdate() { + auto ipcLock = gIPC->lockData(); + + const auto alloutputs = config_["all-outputs"].asBool(); + std::vector my_workspaces; + const auto &workspaces = gIPC->workspaces(); + std::copy_if(workspaces.cbegin(), workspaces.cend(), std::back_inserter(my_workspaces), + [&](const auto &ws) { + if (alloutputs) return true; + return ws["output"].asString() == bar_.output->name; + }); + + // Remove buttons for removed workspaces. + for (auto it = buttons_.begin(); it != buttons_.end();) { + auto ws = std::find_if(my_workspaces.begin(), my_workspaces.end(), + [it](const auto &ws) { return ws["id"].asUInt64() == it->first; }); + if (ws == my_workspaces.end()) { + it = buttons_.erase(it); + } else { + ++it; + } + } + + // Add buttons for new workspaces, update existing ones. + for (const auto &ws : my_workspaces) { + auto bit = buttons_.find(ws["id"].asUInt64()); + auto &button = bit == buttons_.end() ? addButton(ws) : bit->second; + auto style_context = button.get_style_context(); + + if (ws["is_focused"].asBool()) + style_context->add_class("focused"); + else + style_context->remove_class("focused"); + + if (ws["is_active"].asBool()) + style_context->add_class("active"); + else + style_context->remove_class("active"); + + if (ws["output"]) { + if (ws["output"].asString() == bar_.output->name) + style_context->add_class("current_output"); + else + style_context->remove_class("current_output"); + } else { + style_context->remove_class("current_output"); + } + + if (ws["active_window_id"].isNull()) + style_context->add_class("empty"); + else + style_context->remove_class("empty"); + + std::string name; + if (ws["name"]) { + name = ws["name"].asString(); + } else { + name = std::to_string(ws["idx"].asUInt()); + } + button.set_name("niri-workspace-" + name); + + if (config_["format"].isString()) { + auto format = config_["format"].asString(); + name = fmt::format(fmt::runtime(format), fmt::arg("icon", getIcon(name, ws)), + fmt::arg("value", name), fmt::arg("name", ws["name"].asString()), + fmt::arg("index", ws["idx"].asUInt()), + fmt::arg("output", ws["output"].asString())); + } + if (!config_["disable-markup"].asBool()) { + static_cast(button.get_children()[0])->set_markup(name); + } else { + button.set_label(name); + } + + if (config_["current-only"].asBool()) { + const auto *property = alloutputs ? "is_focused" : "is_active"; + if (ws[property].asBool()) + button.show(); + else + button.hide(); + } else { + button.show(); + } + } + + // Refresh the button order. + for (auto it = my_workspaces.cbegin(); it != my_workspaces.cend(); ++it) { + const auto &ws = *it; + + auto pos = ws["idx"].asUInt() - 1; + if (alloutputs) pos = it - my_workspaces.cbegin(); + + auto &button = buttons_[ws["id"].asUInt64()]; + box_.reorder_child(button, pos); + } +} + +void Workspaces::update() { + doUpdate(); + AModule::update(); +} + +Gtk::Button &Workspaces::addButton(const Json::Value &ws) { + std::string name; + if (ws["name"]) { + name = ws["name"].asString(); + } else { + name = std::to_string(ws["idx"].asUInt()); + } + + auto pair = buttons_.emplace(ws["id"].asUInt64(), name); + auto &&button = pair.first->second; + box_.pack_start(button, false, false, 0); + button.set_relief(Gtk::RELIEF_NONE); + if (!config_["disable-click"].asBool()) { + const auto id = ws["id"].asUInt64(); + button.signal_pressed().connect([=] { + try { + // {"Action":{"FocusWorkspace":{"reference":{"Id":1}}}} + Json::Value request(Json::objectValue); + auto &action = (request["Action"] = Json::Value(Json::objectValue)); + auto &focusWorkspace = (action["FocusWorkspace"] = Json::Value(Json::objectValue)); + auto &reference = (focusWorkspace["reference"] = Json::Value(Json::objectValue)); + reference["Id"] = id; + + IPC::send(request); + } catch (const std::exception &e) { + spdlog::error("Error switching workspace: {}", e.what()); + } + }); + } + return button; +} + +std::string Workspaces::getIcon(const std::string &value, const Json::Value &ws) { + const auto &icons = config_["format-icons"]; + if (!icons) return value; + + if (ws["is_focused"].asBool() && icons["focused"]) return icons["focused"].asString(); + + if (ws["is_active"].asBool() && icons["active"]) return icons["active"].asString(); + + if (ws["name"]) { + const auto &name = ws["name"].asString(); + if (icons[name]) return icons[name].asString(); + } + + const auto idx = ws["idx"].asString(); + if (icons[idx]) return icons[idx].asString(); + + if (icons["default"]) return icons["default"].asString(); + + return value; +} + +} // namespace waybar::modules::niri diff --git a/src/modules/power_profiles_daemon.cpp b/src/modules/power_profiles_daemon.cpp index eaa47023..3ae3ae83 100644 --- a/src/modules/power_profiles_daemon.cpp +++ b/src/modules/power_profiles_daemon.cpp @@ -204,7 +204,7 @@ void PowerProfilesDaemon::setPropCb(Glib::RefPtr& r) { auto _ = powerProfilesProxy_->call_finish(r); dp.emit(); } catch (const std::exception& e) { - spdlog::error("Failed to set the the active power profile: {}", e.what()); + spdlog::error("Failed to set the active power profile: {}", e.what()); } catch (const Glib::Error& e) { spdlog::error("Failed to set the active power profile: {}", std::string(e.what())); } diff --git a/src/modules/sni/item.cpp b/src/modules/sni/item.cpp index 6c4ec8c0..8afb39fb 100644 --- a/src/modules/sni/item.cpp +++ b/src/modules/sni/item.cpp @@ -104,9 +104,11 @@ void Item::proxyReady(Glib::RefPtr& result) { this->updateImage(); } catch (const Glib::Error& err) { - spdlog::error("Failed to create DBus Proxy for {} {}: {}", bus_name, object_path, err.what()); + spdlog::error("Failed to create DBus Proxy for {} {}: {}", bus_name, object_path, + std::string(err.what())); } catch (const std::exception& err) { - spdlog::error("Failed to create DBus Proxy for {} {}: {}", bus_name, object_path, err.what()); + spdlog::error("Failed to create DBus Proxy for {} {}: {}", bus_name, object_path, + std::string(err.what())); } } @@ -124,14 +126,15 @@ ToolTip get_variant(const Glib::VariantBase& value) { result.text = get_variant(container.get_child(2)); auto description = get_variant(container.get_child(3)); if (!description.empty()) { - result.text = fmt::format("{}\n{}", result.text, description); + result.text = fmt::format("{}\n{}", std::string(result.text), std::string(description)); } return result; } void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) { try { - spdlog::trace("Set tray item property: {}.{} = {}", id.empty() ? bus_name : id, name, value); + spdlog::trace("Set tray item property: {}.{} = {}", id.empty() ? bus_name : id, + std::string(name), get_variant(value)); if (name == "Category") { category = get_variant(value); @@ -176,10 +179,12 @@ void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) { } } catch (const Glib::Error& err) { spdlog::warn("Failed to set tray item property: {}.{}, value = {}, err = {}", - id.empty() ? bus_name : id, name, value, err.what()); + id.empty() ? bus_name : id, std::string(name), get_variant(value), + std::string(err.what())); } catch (const std::exception& err) { spdlog::warn("Failed to set tray item property: {}.{}, value = {}, err = {}", - id.empty() ? bus_name : id, name, value, err.what()); + id.empty() ? bus_name : id, std::string(name), get_variant(value), + std::string(err.what())); } } @@ -221,9 +226,9 @@ void Item::processUpdatedProperties(Glib::RefPtr& _result) { this->updateImage(); } catch (const Glib::Error& err) { - spdlog::warn("Failed to update properties: {}", err.what()); + spdlog::warn("Failed to update properties: {}", std::string(err.what())); } catch (const std::exception& err) { - spdlog::warn("Failed to update properties: {}", err.what()); + spdlog::warn("Failed to update properties: {}", std::string(err.what())); } update_pending_.clear(); } @@ -245,7 +250,7 @@ static const std::map> signal2props void Item::onSignal(const Glib::ustring& sender_name, const Glib::ustring& signal_name, const Glib::VariantContainerBase& arguments) { - spdlog::trace("Tray item '{}' got signal {}", id, signal_name); + spdlog::trace("Tray item '{}' got signal {}", id, std::string(signal_name)); auto changed = signal2props.find(signal_name.raw()); if (changed != signal2props.end()) { if (update_pending_.empty()) { diff --git a/src/modules/upower.cpp b/src/modules/upower.cpp index 552495f8..5ee6d64c 100644 --- a/src/modules/upower.cpp +++ b/src/modules/upower.cpp @@ -384,10 +384,11 @@ void UPower::setDisplayDevice() { displayDevice = upDevice; } } - // Unref current upDevice - if (displayDevice.upDevice != NULL) g_object_unref(thisPtr->upDevice_.upDevice); - // Reassign new upDevice - thisPtr->upDevice_ = displayDevice; + // Unref current upDevice if it exists + if (displayDevice.upDevice != NULL) { + if (thisPtr->upDevice_.upDevice != NULL) g_object_unref(thisPtr->upDevice_.upDevice); + thisPtr->upDevice_ = displayDevice; + } }, this); } diff --git a/src/modules/wlr/workspace_manager.cpp b/src/modules/wlr/workspace_manager.cpp index f556a161..3c630d81 100644 --- a/src/modules/wlr/workspace_manager.cpp +++ b/src/modules/wlr/workspace_manager.cpp @@ -118,8 +118,8 @@ auto WorkspaceManager::sort_workspaces() -> void { } } -auto WorkspaceManager::register_manager(wl_registry *registry, uint32_t name, uint32_t version) - -> void { +auto WorkspaceManager::register_manager(wl_registry *registry, uint32_t name, + uint32_t version) -> void { if (workspace_manager_) { spdlog::warn("Register workspace manager again although already registered!"); return; diff --git a/subprojects/catch2.wrap b/subprojects/catch2.wrap index f2dfd57c..489db6c6 100644 --- a/subprojects/catch2.wrap +++ b/subprojects/catch2.wrap @@ -1,10 +1,10 @@ [wrap-file] -directory = Catch2-3.5.1 -source_url = https://github.com/catchorg/Catch2/archive/v3.5.1.tar.gz -source_filename = Catch2-3.5.1.tar.gz -source_hash = 49c3ca7a68f1c8ec71307736bc6ed14fec21631707e1be9af45daf4037e75a08 -# source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/catch2_3.4.0-1/Catch2-3.4.0.tar.gz -# wrapdb_version = 3.4.0-1 +directory = Catch2-3.7.0 +source_url = https://github.com/catchorg/Catch2/archive/v3.7.0.tar.gz +source_filename = Catch2-3.7.0.tar.gz +source_hash = 5b10cd536fa3818112a82820ce0787bd9f2a906c618429e7c4dea639983c8e88 +source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/catch2_3.7.0-1/Catch2-3.7.0.tar.gz +wrapdb_version = 3.7.0-1 [provide] catch2 = catch2_dep diff --git a/subprojects/fmt.wrap b/subprojects/fmt.wrap index 9efe101e..42b61596 100644 --- a/subprojects/fmt.wrap +++ b/subprojects/fmt.wrap @@ -1,13 +1,13 @@ [wrap-file] -directory = fmt-9.1.0 -source_url = https://github.com/fmtlib/fmt/archive/9.1.0.tar.gz -source_filename = fmt-9.1.0.tar.gz -source_hash = 5dea48d1fcddc3ec571ce2058e13910a0d4a6bab4cc09a809d8b1dd1c88ae6f2 -patch_filename = fmt_9.1.0-2_patch.zip -patch_url = https://wrapdb.mesonbuild.com/v2/fmt_9.1.0-2/get_patch -patch_hash = 23e8c4829f3e63f509b5643fe6bb87cbed39eae9594c451b338475d14d051967 -source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/fmt_9.1.0-2/fmt-9.1.0.tar.gz -wrapdb_version = 9.1.0-2 +directory = fmt-11.0.1 +source_url = https://github.com/fmtlib/fmt/archive/11.0.1.tar.gz +source_filename = fmt-11.0.1.tar.gz +source_hash = 7d009f7f89ac84c0a83f79ed602463d092fbf66763766a907c97fd02b100f5e9 +patch_filename = fmt_11.0.1-1_patch.zip +patch_url = https://wrapdb.mesonbuild.com/v2/fmt_11.0.1-1/get_patch +patch_hash = 0a8b93d1ee6d84a82d3872a9bfb4c3977d8a53f7f484d42d1f7ed63ed496d549 +source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/fmt_11.0.1-1/fmt-11.0.1.tar.gz +wrapdb_version = 11.0.1-1 [provide] fmt = fmt_dep diff --git a/subprojects/gtk-layer-shell.wrap b/subprojects/gtk-layer-shell.wrap index cb730345..fc0ddf74 100644 --- a/subprojects/gtk-layer-shell.wrap +++ b/subprojects/gtk-layer-shell.wrap @@ -1,5 +1,5 @@ [wrap-file] -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 +directory = gtk-layer-shell-0.9.0 +source_filename = gtk-layer-shell-0.9.0.tar.gz +source_hash = 3809e5565d9ed02e44bb73787ff218523e8760fef65830afe60ea7322e22da1c +source_url = https://github.com/wmww/gtk-layer-shell/archive/v0.9.0/gtk-layer-shell-0.9.0.tar.gz diff --git a/subprojects/spdlog.wrap b/subprojects/spdlog.wrap index 08004c90..af00d5a7 100644 --- a/subprojects/spdlog.wrap +++ b/subprojects/spdlog.wrap @@ -1,13 +1,13 @@ [wrap-file] -directory = spdlog-1.12.0 -source_url = https://github.com/gabime/spdlog/archive/refs/tags/v1.12.0.tar.gz -source_filename = spdlog-1.12.0.tar.gz -source_hash = 4dccf2d10f410c1e2feaff89966bfc49a1abb29ef6f08246335b110e001e09a9 -patch_filename = spdlog_1.12.0-2_patch.zip -patch_url = https://wrapdb.mesonbuild.com/v2/spdlog_1.12.0-2/get_patch -patch_hash = 9596972d1eb2e0a69cea4a53273ca7bbbcb9b2fa872cd734864fc7232dc2d573 -source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/spdlog_1.12.0-2/spdlog-1.12.0.tar.gz -wrapdb_version = 1.12.0-2 +directory = spdlog-1.14.1 +source_url = https://github.com/gabime/spdlog/archive/refs/tags/v1.14.1.tar.gz +source_filename = spdlog-1.14.1.tar.gz +source_hash = 1586508029a7d0670dfcb2d97575dcdc242d3868a259742b69f100801ab4e16b +patch_filename = spdlog_1.14.1-1_patch.zip +patch_url = https://wrapdb.mesonbuild.com/v2/spdlog_1.14.1-1/get_patch +patch_hash = ae878e732330ea1048f90d7e117c40c0cd2a6fb8ae5492c7955818ce3aaade6c +source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/spdlog_1.14.1-1/spdlog-1.14.1.tar.gz +wrapdb_version = 1.14.1-1 [provide] spdlog = spdlog_dep diff --git a/test/utils/SafeSignal.cpp b/test/utils/SafeSignal.cpp index 341e8e2e..e7e096b0 100644 --- a/test/utils/SafeSignal.cpp +++ b/test/utils/SafeSignal.cpp @@ -71,7 +71,7 @@ struct TestObject { unsigned copied = 0; unsigned moved = 0; - TestObject(const T& v) : value(v){}; + TestObject(const T& v) : value(v) {}; ~TestObject() = default; TestObject(const TestObject& other)