Merge branch 'Alexays:master' into master

This commit is contained in:
Sonter 2024-09-13 11:31:22 +00:00 committed by GitHub
commit 8e2a742042
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
64 changed files with 1239 additions and 115 deletions

View File

@ -16,4 +16,5 @@ jobs:
with: with:
source: "." source: "."
extensions: "hpp,h,cpp,c" extensions: "hpp,h,cpp,c"
clangFormatVersion: 16 style: "file:.clang-format"
clangFormatVersion: 18

View File

@ -8,6 +8,7 @@
- Sway (Workspaces, Binding mode, Focused window name) - Sway (Workspaces, Binding mode, Focused window name)
- River (Mapping mode, Tags, Focused window name) - River (Mapping mode, Tags, Focused window name)
- Hyprland (Window Icons, Workspaces, 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) - 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) - Tray [#21](https://github.com/Alexays/Waybar/issues/21)
- Local time - Local time

View File

@ -18,11 +18,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1722185531, "lastModified": 1724819573,
"narHash": "sha256-veKR07psFoJjINLC8RK4DiLniGGMgF3QMlS4tb74S6k=", "narHash": "sha256-GnR7/ibgIH1vhoy8cYdmXE6iyZqKqFxQSVkFgosBh6w=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "52ec9ac3b12395ad677e8b62106f0b98c1f8569d", "rev": "71e91c409d1e654808b2621f28a327acfdad8dc2",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@ -16,7 +16,7 @@ class AModule : public IModule {
~AModule() override; ~AModule() override;
auto update() -> void override; auto update() -> void override;
virtual auto refresh(int shouldRefresh) -> void{}; virtual auto refresh(int shouldRefresh) -> void {};
operator Gtk::Widget &() override; operator Gtk::Widget &() override;
auto doAction(const std::string &name) -> void override; auto doAction(const std::string &name) -> void override;

View File

@ -51,8 +51,8 @@ class Clock final : public ALabel {
day cldBaseDay_{0}; // calendar Cached day. Is used when today is changing(midnight) day cldBaseDay_{0}; // calendar Cached day. Is used when today is changing(midnight)
std::string cldText_{""}; // calendar text to print std::string cldText_{""}; // calendar text to print
CldMode cldMode_{CldMode::MONTH}; CldMode cldMode_{CldMode::MONTH};
auto get_calendar(const year_month_day& today, const year_month_day& ymd, const time_zone* tz) auto get_calendar(const year_month_day& today, const year_month_day& ymd,
-> const std::string; const time_zone* tz) -> const std::string;
// get local time zone // get local time zone
auto local_zone() -> const time_zone*; auto local_zone() -> const time_zone*;

View File

@ -61,8 +61,8 @@ class Workspaces : public AModule, public EventHandler {
// Config // Config
void parseConfig(const Json::Value& config); void parseConfig(const Json::Value& config);
auto populateIconsMap(const Json::Value& formatIcons) -> void; auto populateIconsMap(const Json::Value& formatIcons) -> void;
static auto populateBoolConfig(const Json::Value& config, const std::string& key, bool& member) static auto populateBoolConfig(const Json::Value& config, const std::string& key,
-> void; bool& member) -> void;
auto populateSortByConfig(const Json::Value& config) -> void; auto populateSortByConfig(const Json::Value& config) -> void;
auto populateIgnoreWorkspacesConfig(const Json::Value& config) -> void; auto populateIgnoreWorkspacesConfig(const Json::Value& config) -> void;
auto populateFormatWindowSeparatorConfig(const Json::Value& config) -> void; auto populateFormatWindowSeparatorConfig(const Json::Value& config) -> void;

View File

@ -0,0 +1,52 @@
#pragma once
#include <list>
#include <mutex>
#include <string>
#include <utility>
#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<std::mutex> lockData() { return std::lock_guard(dataMutex_); }
const std::vector<Json::Value>& workspaces() const { return workspaces_; }
const std::vector<Json::Value>& windows() const { return windows_; }
const std::vector<std::string>& 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<Json::Value> workspaces_;
std::vector<Json::Value> windows_;
std::vector<std::string> keyboardLayoutNames_;
unsigned keyboardLayoutCurrent_;
util::JsonParser parser_;
std::mutex callbackMutex_;
std::list<std::pair<std::string, EventHandler*>> callbacks_;
};
inline std::unique_ptr<IPC> gIPC;
}; // namespace waybar::modules::niri

View File

@ -0,0 +1,38 @@
#pragma once
#include <string>
#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<Layout> layouts_;
unsigned current_idx_;
};
} // namespace waybar::modules::niri

View File

@ -0,0 +1,28 @@
#pragma once
#include <gtkmm/button.h>
#include <json/value.h>
#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

View File

@ -0,0 +1,30 @@
#pragma once
#include <gtkmm/button.h>
#include <json/value.h>
#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<uint64_t, Gtk::Button> buttons_;
};
} // namespace waybar::modules::niri

View File

@ -622,8 +622,8 @@ inline auto convertInto(std::string const &source, bool &target) -> ParserResult
} }
#ifdef CLARA_CONFIG_OPTIONAL_TYPE #ifdef CLARA_CONFIG_OPTIONAL_TYPE
template <typename T> template <typename T>
inline auto convertInto(std::string const &source, CLARA_CONFIG_OPTIONAL_TYPE<T> &target) inline auto convertInto(std::string const &source,
-> ParserResult { CLARA_CONFIG_OPTIONAL_TYPE<T> &target) -> ParserResult {
T temp; T temp;
auto result = convertInto(source, temp); auto result = convertInto(source, temp);
if (result) target = std::move(temp); if (result) target = std::move(temp);
@ -751,8 +751,8 @@ class ParserBase {
public: public:
virtual ~ParserBase() = default; virtual ~ParserBase() = default;
virtual auto validate() const -> Result { return Result::ok(); } virtual auto validate() const -> Result { return Result::ok(); }
virtual auto parse(std::string const &exeName, TokenStream const &tokens) const virtual auto parse(std::string const &exeName,
-> InternalParseResult = 0; TokenStream const &tokens) const -> InternalParseResult = 0;
virtual auto cardinality() const -> size_t { return 1; } virtual auto cardinality() const -> size_t { return 1; }
auto parse(Args const &args) const -> InternalParseResult { auto parse(Args const &args) const -> InternalParseResult {
@ -1098,8 +1098,8 @@ struct Parser : ParserBase {
using ParserBase::parse; using ParserBase::parse;
auto parse(std::string const &exeName, TokenStream const &tokens) const auto parse(std::string const &exeName,
-> InternalParseResult override { TokenStream const &tokens) const -> InternalParseResult override {
struct ParserInfo { struct ParserInfo {
ParserBase const *parser = nullptr; ParserBase const *parser = nullptr;
size_t count = 0; size_t count = 0;

View File

@ -6,7 +6,7 @@
class pow_format { class pow_format {
public: public:
pow_format(long long val, std::string&& unit, bool binary = false) 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_; long long val_;
std::string unit_; std::string unit_;

View File

@ -38,7 +38,7 @@ The *backlight* module displays the current backlight level.
*rotate*: ++ *rotate*: ++
typeof: integer ++ typeof: integer ++
Positive value to rotate the text label. Positive value to rotate the text label (in 90 degree increments).
*states*: ++ *states*: ++
typeof: object ++ typeof: object ++

View File

@ -69,7 +69,7 @@ The *battery* module displays the current capacity and state (eg. charging) of y
*rotate*: ++ *rotate*: ++
typeof: integer++ typeof: integer++
Positive value to rotate the text label. Positive value to rotate the text label (in 90 degree increments).
*on-click*: ++ *on-click*: ++
typeof: string ++ typeof: string ++

View File

@ -54,7 +54,7 @@ Addressed by *bluetooth*
*rotate*: ++ *rotate*: ++
typeof: integer ++ typeof: integer ++
Positive value to rotate the text label. Positive value to rotate the text label (in 90 degree increments).
*max-length*: ++ *max-length*: ++
typeof: integer ++ typeof: integer ++

View File

@ -51,7 +51,7 @@ $XDG_CONFIG_HOME/waybar/config ++
|[ *rotate* |[ *rotate*
:[ integer :[ integer
:[ :[
:[ Positive value to rotate the text label :[ Positive value to rotate the text label (in 90 degree increments)
|[ *on-click* |[ *on-click*
:[ string :[ string
:[ :[

View File

@ -43,7 +43,7 @@ The *cpu* module displays the current CPU utilization.
*rotate*: ++ *rotate*: ++
typeof: integer ++ typeof: integer ++
Positive value to rotate the text label. Positive value to rotate the text label (in 90 degree increments).
*states*: ++ *states*: ++
typeof: object ++ typeof: object ++

View File

@ -64,7 +64,7 @@ Addressed by *custom/<name>*
*rotate*: ++ *rotate*: ++
typeof: integer ++ typeof: integer ++
Positive value to rotate the text label. Positive value to rotate the text label (in 90 degree increments).
*max-length*: ++ *max-length*: ++
typeof: integer ++ typeof: integer ++

View File

@ -29,7 +29,7 @@ Addressed by *disk*
*rotate*: ++ *rotate*: ++
typeof: integer ++ typeof: integer ++
Positive value to rotate the text label. Positive value to rotate the text label (in 90 degree increments).
*states*: ++ *states*: ++
typeof: object ++ typeof: object ++

View File

@ -19,7 +19,7 @@ Addressed by *dwl/window*
*rotate*: ++ *rotate*: ++
typeof: integer ++ typeof: integer ++
Positive value to rotate the text label. Positive value to rotate the text label (in 90 degree increments).
*max-length*: ++ *max-length*: ++
typeof: integer ++ typeof: integer ++

View File

@ -19,7 +19,7 @@ Addressed by *hyprland/submap*
*rotate*: ++ *rotate*: ++
typeof: integer ++ typeof: integer ++
Positive value to rotate the text label. Positive value to rotate the text label (in 90 degree increments).
*max-length*: ++ *max-length*: ++
typeof: integer ++ typeof: integer ++

View File

@ -21,7 +21,7 @@ screensaver, also known as "presentation mode".
*rotate*: ++ *rotate*: ++
typeof: integer ++ typeof: integer ++
Positive value to rotate the text label. Positive value to rotate the text label (in 90 degree increments).
*max-length*: ++ *max-length*: ++
typeof: integer ++ typeof: integer ++

View File

@ -25,7 +25,7 @@ See *systemd-inhibit*(1) for more information.
*rotate*: ++ *rotate*: ++
typeof: integer ++ typeof: integer ++
Positive value to rotate the text label. Positive value to rotate the text label (in 90 degree increments).
*max-length*: ++ *max-length*: ++
typeof: integer ++ typeof: integer ++

View File

@ -51,7 +51,7 @@ Addressed by *jack*
*rotate*: ++ *rotate*: ++
typeof: integer ++ typeof: integer ++
Positive value to rotate the text label. Positive value to rotate the text label (in 90 degree increments).
*max-length*: ++ *max-length*: ++
typeof: integer ++ typeof: integer ++

View File

@ -29,7 +29,7 @@ Addressed by *memory*
*rotate*: ++ *rotate*: ++
typeof: integer ++ typeof: integer ++
Positive value to rotate the text label. Positive value to rotate the text label (in 90 degree increments).
*states*: ++ *states*: ++
typeof: object ++ typeof: object ++

View File

@ -91,7 +91,7 @@ Addressed by *mpd*
*rotate*: ++ *rotate*: ++
typeof: integer ++ typeof: integer ++
Positive value to rotate the text label. Positive value to rotate the text label (in 90 degree increments).
*max-length*: ++ *max-length*: ++
typeof: integer ++ typeof: integer ++

View File

@ -107,7 +107,7 @@ The *mpris* module displays currently playing media via libplayerctl.
*rotate*: ++ *rotate*: ++
typeof: integer ++ typeof: integer ++
Positive value to rotate the text label. Positive value to rotate the text label (in 90 degree increments).
*max-length*: ++ *max-length*: ++
typeof: integer ++ typeof: integer ++

View File

@ -58,7 +58,7 @@ Addressed by *network*
*rotate*: ++ *rotate*: ++
typeof: integer ++ typeof: integer ++
Positive value to rotate the text label. Positive value to rotate the text label (in 90 degree increments).
*max-length*: ++ *max-length*: ++
typeof: integer ++ typeof: integer ++

View File

@ -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-<lang>* ++
typeof: string++
Provide an alternative name to display per language where <lang> 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*

View File

@ -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.<app-id>* Where *app-id* is the app ID of the only window on
the workspace

View File

@ -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": "<b></b>",
// 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-<name>*: Workspaces named this, or index
for unnamed workspaces.

View File

@ -40,7 +40,7 @@ Additionally, you can control the volume by scrolling *up* or *down* while the c
*rotate*: ++ *rotate*: ++
typeof: integer ++ typeof: integer ++
Positive value to rotate the text label. Positive value to rotate the text label (in 90 degree increments).
*states*: ++ *states*: ++
typeof: object ++ typeof: object ++

View File

@ -21,7 +21,7 @@ Addressed by *river/layout*
*rotate*: ++ *rotate*: ++
typeof: integer ++ typeof: integer ++
Positive value to rotate the text label. Positive value to rotate the text label (in 90 degree increments).
*max-length*: ++ *max-length*: ++
typeof: integer ++ typeof: integer ++

View File

@ -19,7 +19,7 @@ Addressed by *river/mode*
*rotate*: ++ *rotate*: ++
typeof: integer ++ typeof: integer ++
Positive value to rotate the text label. Positive value to rotate the text label (in 90 degree increments).
*max-length*: ++ *max-length*: ++
typeof: integer ++ typeof: integer ++

View File

@ -19,7 +19,7 @@ Addressed by *river/window*
*rotate*: ++ *rotate*: ++
typeof: integer ++ typeof: integer ++
Positive value to rotate the text label. Positive value to rotate the text label (in 90 degree increments).
*max-length*: ++ *max-length*: ++
typeof: integer ++ typeof: integer ++

View File

@ -20,7 +20,7 @@ cursor is over the module, and clicking on the module toggles mute.
*rotate*: ++ *rotate*: ++
typeof: integer ++ typeof: integer ++
Positive value to rotate the text label. Positive value to rotate the text label (in 90 degree increments).
*max-length*: ++ *max-length*: ++
typeof: integer ++ typeof: integer ++

View File

@ -19,7 +19,7 @@ Addressed by *sway/mode*
*rotate*: ++ *rotate*: ++
typeof: integer ++ typeof: integer ++
Positive value to rotate the text label. Positive value to rotate the text label (in 90 degree increments).
*max-length*: ++ *max-length*: ++
typeof: integer ++ typeof: integer ++

View File

@ -19,7 +19,7 @@ Addressed by *sway/window*
*rotate*: ++ *rotate*: ++
typeof: integer ++ typeof: integer ++
Positive value to rotate the text label. Positive value to rotate the text label (in 90 degree increments).
*max-length*: ++ *max-length*: ++
typeof: integer ++ typeof: integer ++

View File

@ -60,7 +60,7 @@ Addressed by *temperature*
*rotate*: ++ *rotate*: ++
typeof: integer ++ typeof: integer ++
Positive value to rotate the text label. Positive value to rotate the text label (in 90 degree increments).
*max-length*: ++ *max-length*: ++
typeof: integer ++ typeof: integer ++

View File

@ -31,7 +31,7 @@ The *wireplumber* module displays the current volume reported by WirePlumber.
*rotate*: ++ *rotate*: ++
typeof: integer ++ typeof: integer ++
Positive value to rotate the text label. Positive value to rotate the text label (in 90 degree increments).
*states*: ++ *states*: ++
typeof: object ++ typeof: object ++

View File

@ -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-submap(5)*
- *waybar-hyprland-window(5)* - *waybar-hyprland-window(5)*
- *waybar-hyprland-workspaces(5)* - *waybar-hyprland-workspaces(5)*
- *waybar-niri-language(5)*
- *waybar-niri-window(5)*
- *waybar-niri-workspaces(5)*
- *waybar-idle-inhibitor(5)* - *waybar-idle-inhibitor(5)*
- *waybar-image(5)* - *waybar-image(5)*
- *waybar-inhibitor(5)* - *waybar-inhibitor(5)*

View File

@ -1,6 +1,6 @@
project( project(
'waybar', 'cpp', 'c', 'waybar', 'cpp', 'c',
version: '0.10.4', version: '0.11.0',
license: 'MIT', license: 'MIT',
meson_version: '>= 0.59.0', meson_version: '>= 0.59.0',
default_options : [ default_options : [
@ -69,7 +69,7 @@ is_openbsd = host_machine.system() == 'openbsd'
thread_dep = dependency('threads') thread_dep = dependency('threads')
fmt = dependency('fmt', version : ['>=8.1.1'], fallback : ['fmt', 'fmt_dep']) fmt = dependency('fmt', version : ['>=8.1.1'], fallback : ['fmt', 'fmt_dep'])
spdlog = dependency('spdlog', version : ['>=1.10.0'], fallback : ['spdlog', 'spdlog_dep'], default_options : ['external_fmt=enabled']) 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_client = dependency('wayland-client')
wayland_cursor = dependency('wayland-cursor') wayland_cursor = dependency('wayland-cursor')
wayland_protos = dependency('wayland-protocols') wayland_protos = dependency('wayland-protocols')
@ -318,6 +318,21 @@ if true
) )
endif 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() if libnl.found() and libnlgen.found()
add_project_arguments('-DHAVE_LIBNL', language: 'cpp') add_project_arguments('-DHAVE_LIBNL', language: 'cpp')
src_files += files('src/modules/network.cpp') src_files += files('src/modules/network.cpp')

View File

@ -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('jack', type: 'feature', value: 'auto', description: 'Enable support for JACK')
option('wireplumber', type: 'feature', value: 'auto', description: 'Enable support for WirePlumber') option('wireplumber', type: 'feature', value: 'auto', description: 'Enable support for WirePlumber')
option('cava', type: 'feature', value: 'auto', description: 'Enable support for Cava') option('cava', type: 'feature', value: 'auto', description: 'Enable support for Cava')
option('niri', type: 'boolean', description: 'Enable support for niri')

View File

@ -45,6 +45,8 @@ ALabel::ALabel(const Json::Value& config, const std::string& name, const std::st
if (config_["rotate"].isUInt()) { if (config_["rotate"].isUInt()) {
rotate = config["rotate"].asUInt(); 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); label_.set_angle(rotate);
} }

View File

@ -36,6 +36,11 @@
#include "modules/hyprland/window.hpp" #include "modules/hyprland/window.hpp"
#include "modules/hyprland/workspaces.hpp" #include "modules/hyprland/workspaces.hpp"
#endif #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__) #if defined(__FreeBSD__) || defined(__linux__)
#include "modules/battery.hpp" #include "modules/battery.hpp"
#endif #endif
@ -205,6 +210,17 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name,
if (ref == "hyprland/workspaces") { if (ref == "hyprland/workspaces") {
return new waybar::modules::hyprland::Workspaces(id, bar_, config_[name]); 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 #endif
if (ref == "idle_inhibitor") { if (ref == "idle_inhibitor") {
return new waybar::modules::IdleInhibitor(id, bar_, config_[name]); return new waybar::modules::IdleInhibitor(id, bar_, config_[name]);

View File

@ -49,8 +49,8 @@ auto getBoolProperty(GDBusProxy* proxy, const char* property_name) -> bool {
return false; return false;
} }
auto getOptionalStringProperty(GDBusProxy* proxy, const char* property_name) auto getOptionalStringProperty(GDBusProxy* proxy,
-> std::optional<std::string> { const char* property_name) -> std::optional<std::string> {
auto gvar = g_dbus_proxy_get_cached_property(proxy, property_name); auto gvar = g_dbus_proxy_get_cached_property(proxy, property_name);
if (gvar) { if (gvar) {
std::string property_value = g_variant_get_string(gvar, NULL); 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( auto waybar::modules::Bluetooth::onInterfaceProxyPropertiesChanged(
GDBusObjectManagerClient* manager, GDBusObjectProxy* object_proxy, GDBusProxy* interface_proxy, GDBusObjectManagerClient* manager, GDBusObjectProxy* object_proxy, GDBusProxy* interface_proxy,
GVariant* changed_properties, const gchar* const* invalidated_properties, gpointer user_data) GVariant* changed_properties, const gchar* const* invalidated_properties,
-> void { gpointer user_data) -> void {
std::string interface_name = g_dbus_proxy_get_interface_name(interface_proxy); 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)); 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; return std::nullopt;
} }
auto waybar::modules::Bluetooth::getDeviceProperties(GDBusObject* object, DeviceInfo& device_info) auto waybar::modules::Bluetooth::getDeviceProperties(GDBusObject* object,
-> bool { DeviceInfo& device_info) -> bool {
GDBusProxy* proxy_device = G_DBUS_PROXY(g_dbus_object_get_interface(object, "org.bluez.Device1")); GDBusProxy* proxy_device = G_DBUS_PROXY(g_dbus_object_get_interface(object, "org.bluez.Device1"));
if (proxy_device != NULL) { if (proxy_device != NULL) {
@ -462,9 +462,8 @@ auto waybar::modules::Bluetooth::findCurController() -> std::optional<Controller
return controller_info; return controller_info;
} }
auto waybar::modules::Bluetooth::findConnectedDevices(const std::string& cur_controller_path, auto waybar::modules::Bluetooth::findConnectedDevices(
std::vector<DeviceInfo>& connected_devices) const std::string& cur_controller_path, std::vector<DeviceInfo>& connected_devices) -> void {
-> void {
GList* objects = g_dbus_object_manager_get_objects(manager_.get()); GList* objects = g_dbus_object_manager_get_objects(manager_.get());
for (GList* l = objects; l != NULL; l = l->next) { for (GList* l = objects; l != NULL; l = l->next) {
GDBusObject* object = G_DBUS_OBJECT(l->data); GDBusObject* object = G_DBUS_OBJECT(l->data);

View File

@ -198,8 +198,8 @@ const unsigned cldRowsInMonth(const year_month& ym, const weekday& firstdow) {
return 2u + ceil<weeks>((weekday{ym / 1} - firstdow) + ((ym / last).day() - day{0})).count(); return 2u + ceil<weeks>((weekday{ym / 1} - firstdow) + ((ym / last).day() - day{0})).count();
} }
auto cldGetWeekForLine(const year_month& ym, const weekday& firstdow, const unsigned line) auto cldGetWeekForLine(const year_month& ym, const weekday& firstdow,
-> const year_month_weekday { const unsigned line) -> const year_month_weekday {
unsigned index{line - 2}; unsigned index{line - 2};
if (weekday{ym / 1} == firstdow) ++index; if (weekday{ym / 1} == firstdow) ++index;
return ym / firstdow[index]; return ym / firstdow[index];

View File

@ -53,7 +53,7 @@ static void set_layout(void *data, zdwl_ipc_output_v2 *zdwl_output_v2, uint32_t
// Intentionally empty // Intentionally empty
} }
static void appid(void *data, zdwl_ipc_output_v2 *zdwl_output_v2, const char *appid){ static void appid(void *data, zdwl_ipc_output_v2 *zdwl_output_v2, const char *appid) {
// Intentionally empty // Intentionally empty
}; };

View File

@ -590,8 +590,8 @@ auto Workspaces::populateIconsMap(const Json::Value &formatIcons) -> void {
m_iconsMap.emplace("", ""); m_iconsMap.emplace("", "");
} }
auto Workspaces::populateBoolConfig(const Json::Value &config, const std::string &key, bool &member) auto Workspaces::populateBoolConfig(const Json::Value &config, const std::string &key,
-> void { bool &member) -> void {
const auto &configValue = config[key]; const auto &configValue = config[key];
if (configValue.isBool()) { if (configValue.isBool()) {
member = configValue.asBool(); member = configValue.asBool();

View File

@ -42,7 +42,6 @@ void waybar::modules::Image::refresh(int sig) {
} }
auto waybar::modules::Image::update() -> void { auto waybar::modules::Image::update() -> void {
Glib::RefPtr<Gdk::Pixbuf> pixbuf;
if (config_["path"].isString()) { if (config_["path"].isString()) {
path_ = config_["path"].asString(); path_ = config_["path"].asString();
} else if (config_["exec"].isString()) { } else if (config_["exec"].isString()) {
@ -51,19 +50,24 @@ auto waybar::modules::Image::update() -> void {
} else { } else {
path_ = ""; 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<Gdk::Pixbuf> 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 (tooltipEnabled() && !tooltip_.empty()) {
if (box_.get_tooltip_markup() != tooltip_) { if (box_.get_tooltip_markup() != tooltip_) {
box_.set_tooltip_markup(tooltip_); box_.set_tooltip_markup(tooltip_);
} }
} }
image_.set(pixbuf);
image_.show();
box_.get_style_context()->remove_class("empty"); box_.get_style_context()->remove_class("empty");
} else { } else {
image_.clear(); image_.clear();

View File

@ -0,0 +1,261 @@
#include "modules/niri/backend.hpp"
#include <netdb.h>
#include <netinet/in.h>
#include <spdlog/spdlog.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/un.h>
#include <unistd.h>
#include <iostream>
#include <string>
#include <thread>
#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<Json::StreamWriter> 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

View File

@ -0,0 +1,136 @@
#include "modules/niri/language.hpp"
#include <spdlog/spdlog.h>
#include <xkbcommon/xkbcommon.h>
#include <xkbcommon/xkbregistry.h>
#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<IPC>();
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<std::mutex> lock(mutex_);
}
void Language::updateFromIPC() {
std::lock_guard<std::mutex> 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<std::mutex> 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<std::mutex> 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

109
src/modules/niri/window.cpp Normal file
View File

@ -0,0 +1,109 @@
#include "modules/niri/window.hpp"
#include <gtkmm/button.h>
#include <gtkmm/label.h>
#include <spdlog/spdlog.h>
#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<IPC>();
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<Json::Value>::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

View File

@ -0,0 +1,186 @@
#include "modules/niri/workspaces.hpp"
#include <gtkmm/button.h>
#include <gtkmm/label.h>
#include <spdlog/spdlog.h>
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<IPC>();
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<Json::Value> 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<Gtk::Label *>(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

View File

@ -204,7 +204,7 @@ void PowerProfilesDaemon::setPropCb(Glib::RefPtr<Gio::AsyncResult>& r) {
auto _ = powerProfilesProxy_->call_finish(r); auto _ = powerProfilesProxy_->call_finish(r);
dp.emit(); dp.emit();
} catch (const std::exception& e) { } 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) { } catch (const Glib::Error& e) {
spdlog::error("Failed to set the active power profile: {}", std::string(e.what())); spdlog::error("Failed to set the active power profile: {}", std::string(e.what()));
} }

View File

@ -104,9 +104,11 @@ void Item::proxyReady(Glib::RefPtr<Gio::AsyncResult>& result) {
this->updateImage(); this->updateImage();
} catch (const Glib::Error& err) { } 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) { } 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<ToolTip>(const Glib::VariantBase& value) {
result.text = get_variant<Glib::ustring>(container.get_child(2)); result.text = get_variant<Glib::ustring>(container.get_child(2));
auto description = get_variant<Glib::ustring>(container.get_child(3)); auto description = get_variant<Glib::ustring>(container.get_child(3));
if (!description.empty()) { if (!description.empty()) {
result.text = fmt::format("<b>{}</b>\n{}", result.text, description); result.text = fmt::format("<b>{}</b>\n{}", std::string(result.text), std::string(description));
} }
return result; return result;
} }
void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) { void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) {
try { 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<std::string>(value));
if (name == "Category") { if (name == "Category") {
category = get_variant<std::string>(value); category = get_variant<std::string>(value);
@ -176,10 +179,12 @@ void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) {
} }
} catch (const Glib::Error& err) { } catch (const Glib::Error& err) {
spdlog::warn("Failed to set tray item property: {}.{}, value = {}, 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<std::string>(value),
std::string(err.what()));
} catch (const std::exception& err) { } catch (const std::exception& err) {
spdlog::warn("Failed to set tray item property: {}.{}, value = {}, 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<std::string>(value),
std::string(err.what()));
} }
} }
@ -221,9 +226,9 @@ void Item::processUpdatedProperties(Glib::RefPtr<Gio::AsyncResult>& _result) {
this->updateImage(); this->updateImage();
} catch (const Glib::Error& err) { } 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) { } 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(); update_pending_.clear();
} }
@ -245,7 +250,7 @@ static const std::map<std::string_view, std::set<std::string_view>> signal2props
void Item::onSignal(const Glib::ustring& sender_name, const Glib::ustring& signal_name, void Item::onSignal(const Glib::ustring& sender_name, const Glib::ustring& signal_name,
const Glib::VariantContainerBase& arguments) { 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()); auto changed = signal2props.find(signal_name.raw());
if (changed != signal2props.end()) { if (changed != signal2props.end()) {
if (update_pending_.empty()) { if (update_pending_.empty()) {

View File

@ -384,10 +384,11 @@ void UPower::setDisplayDevice() {
displayDevice = upDevice; displayDevice = upDevice;
} }
} }
// Unref current upDevice // Unref current upDevice if it exists
if (displayDevice.upDevice != NULL) g_object_unref(thisPtr->upDevice_.upDevice); if (displayDevice.upDevice != NULL) {
// Reassign new upDevice if (thisPtr->upDevice_.upDevice != NULL) g_object_unref(thisPtr->upDevice_.upDevice);
thisPtr->upDevice_ = displayDevice; thisPtr->upDevice_ = displayDevice;
}
}, },
this); this);
} }

View File

@ -118,8 +118,8 @@ auto WorkspaceManager::sort_workspaces() -> void {
} }
} }
auto WorkspaceManager::register_manager(wl_registry *registry, uint32_t name, uint32_t version) auto WorkspaceManager::register_manager(wl_registry *registry, uint32_t name,
-> void { uint32_t version) -> void {
if (workspace_manager_) { if (workspace_manager_) {
spdlog::warn("Register workspace manager again although already registered!"); spdlog::warn("Register workspace manager again although already registered!");
return; return;

View File

@ -1,10 +1,10 @@
[wrap-file] [wrap-file]
directory = Catch2-3.5.1 directory = Catch2-3.7.0
source_url = https://github.com/catchorg/Catch2/archive/v3.5.1.tar.gz source_url = https://github.com/catchorg/Catch2/archive/v3.7.0.tar.gz
source_filename = Catch2-3.5.1.tar.gz source_filename = Catch2-3.7.0.tar.gz
source_hash = 49c3ca7a68f1c8ec71307736bc6ed14fec21631707e1be9af45daf4037e75a08 source_hash = 5b10cd536fa3818112a82820ce0787bd9f2a906c618429e7c4dea639983c8e88
# source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/catch2_3.4.0-1/Catch2-3.4.0.tar.gz source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/catch2_3.7.0-1/Catch2-3.7.0.tar.gz
# wrapdb_version = 3.4.0-1 wrapdb_version = 3.7.0-1
[provide] [provide]
catch2 = catch2_dep catch2 = catch2_dep

View File

@ -1,13 +1,13 @@
[wrap-file] [wrap-file]
directory = fmt-9.1.0 directory = fmt-11.0.1
source_url = https://github.com/fmtlib/fmt/archive/9.1.0.tar.gz source_url = https://github.com/fmtlib/fmt/archive/11.0.1.tar.gz
source_filename = fmt-9.1.0.tar.gz source_filename = fmt-11.0.1.tar.gz
source_hash = 5dea48d1fcddc3ec571ce2058e13910a0d4a6bab4cc09a809d8b1dd1c88ae6f2 source_hash = 7d009f7f89ac84c0a83f79ed602463d092fbf66763766a907c97fd02b100f5e9
patch_filename = fmt_9.1.0-2_patch.zip patch_filename = fmt_11.0.1-1_patch.zip
patch_url = https://wrapdb.mesonbuild.com/v2/fmt_9.1.0-2/get_patch patch_url = https://wrapdb.mesonbuild.com/v2/fmt_11.0.1-1/get_patch
patch_hash = 23e8c4829f3e63f509b5643fe6bb87cbed39eae9594c451b338475d14d051967 patch_hash = 0a8b93d1ee6d84a82d3872a9bfb4c3977d8a53f7f484d42d1f7ed63ed496d549
source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/fmt_9.1.0-2/fmt-9.1.0.tar.gz source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/fmt_11.0.1-1/fmt-11.0.1.tar.gz
wrapdb_version = 9.1.0-2 wrapdb_version = 11.0.1-1
[provide] [provide]
fmt = fmt_dep fmt = fmt_dep

View File

@ -1,5 +1,5 @@
[wrap-file] [wrap-file]
directory = gtk-layer-shell-0.8.2 directory = gtk-layer-shell-0.9.0
source_filename = gtk-layer-shell-0.8.2.tar.gz source_filename = gtk-layer-shell-0.9.0.tar.gz
source_hash = 254dd246303127c5d5236ea640f01a82e35d2d652a48d139dd669c832a0f0dce source_hash = 3809e5565d9ed02e44bb73787ff218523e8760fef65830afe60ea7322e22da1c
source_url = https://github.com/wmww/gtk-layer-shell/archive/v0.8.2/gtk-layer-shell-0.8.2.tar.gz source_url = https://github.com/wmww/gtk-layer-shell/archive/v0.9.0/gtk-layer-shell-0.9.0.tar.gz

View File

@ -1,13 +1,13 @@
[wrap-file] [wrap-file]
directory = spdlog-1.12.0 directory = spdlog-1.14.1
source_url = https://github.com/gabime/spdlog/archive/refs/tags/v1.12.0.tar.gz source_url = https://github.com/gabime/spdlog/archive/refs/tags/v1.14.1.tar.gz
source_filename = spdlog-1.12.0.tar.gz source_filename = spdlog-1.14.1.tar.gz
source_hash = 4dccf2d10f410c1e2feaff89966bfc49a1abb29ef6f08246335b110e001e09a9 source_hash = 1586508029a7d0670dfcb2d97575dcdc242d3868a259742b69f100801ab4e16b
patch_filename = spdlog_1.12.0-2_patch.zip patch_filename = spdlog_1.14.1-1_patch.zip
patch_url = https://wrapdb.mesonbuild.com/v2/spdlog_1.12.0-2/get_patch patch_url = https://wrapdb.mesonbuild.com/v2/spdlog_1.14.1-1/get_patch
patch_hash = 9596972d1eb2e0a69cea4a53273ca7bbbcb9b2fa872cd734864fc7232dc2d573 patch_hash = ae878e732330ea1048f90d7e117c40c0cd2a6fb8ae5492c7955818ce3aaade6c
source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/spdlog_1.12.0-2/spdlog-1.12.0.tar.gz source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/spdlog_1.14.1-1/spdlog-1.14.1.tar.gz
wrapdb_version = 1.12.0-2 wrapdb_version = 1.14.1-1
[provide] [provide]
spdlog = spdlog_dep spdlog = spdlog_dep

View File

@ -71,7 +71,7 @@ struct TestObject {
unsigned copied = 0; unsigned copied = 0;
unsigned moved = 0; unsigned moved = 0;
TestObject(const T& v) : value(v){}; TestObject(const T& v) : value(v) {};
~TestObject() = default; ~TestObject() = default;
TestObject(const TestObject& other) TestObject(const TestObject& other)