Merge branch 'master' into wayfire

This commit is contained in:
Alexis Rouillard
2025-06-22 08:53:02 +01:00
committed by GitHub
112 changed files with 1726 additions and 870 deletions

View File

@ -7,14 +7,16 @@ concurrency:
cancel-in-progress: true cancel-in-progress: true
jobs: jobs:
build: lint:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: DoozyX/clang-format-lint-action@v0.16.2 # TODO: bump to clang 19 release
# - uses: DoozyX/clang-format-lint-action@v0.18.2
- uses: DoozyX/clang-format-lint-action@558090054b3f39e3d6af24f0cd73b319535da809
name: clang-format name: clang-format
with: with:
source: "." source: "."
extensions: "hpp,h,cpp,c" extensions: "hpp,h,cpp,c"
style: "file:.clang-format" style: "file:.clang-format"
clangFormatVersion: 18 clangFormatVersion: 19

View File

@ -7,7 +7,7 @@ concurrency:
cancel-in-progress: true cancel-in-progress: true
jobs: jobs:
build: lint:
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: container:
image: alexays/waybar:debian image: alexays/waybar:debian

View File

@ -1,14 +1,15 @@
name: Build and Push Docker Image name: Build and Push Docker Image
on: on:
workflow_dispatch:
schedule: schedule:
# run every night at midnight # run monthly
- cron: '0 0 * * *' - cron: '0 0 1 * *'
jobs: jobs:
build-and-push: build-and-push:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.repository == 'Alexays/Waybar' if: github.event_name != 'schedule' || github.repository == 'Alexays/Waybar'
strategy: strategy:
fail-fast: false # don't fail the other jobs if one of the images fails to build fail-fast: false # don't fail the other jobs if one of the images fails to build
matrix: matrix:

View File

@ -7,7 +7,7 @@ concurrency:
cancel-in-progress: true cancel-in-progress: true
jobs: jobs:
clang: build:
# Run actions in a FreeBSD VM on the ubuntu runner # Run actions in a FreeBSD VM on the ubuntu runner
# https://github.com/actions/runner/issues/385 - for FreeBSD runner support # https://github.com/actions/runner/issues/385 - for FreeBSD runner support
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -21,7 +21,7 @@ jobs:
LDFLAGS: '-L/usr/local/lib' LDFLAGS: '-L/usr/local/lib'
with: with:
operating_system: freebsd operating_system: freebsd
version: "14.1" version: "14.3"
environment_variables: CPPFLAGS LDFLAGS environment_variables: CPPFLAGS LDFLAGS
sync_files: runner-to-vm sync_files: runner-to-vm
run: | run: |

View File

@ -1,6 +1,6 @@
# vim: ft=Dockerfile # vim: ft=Dockerfile
FROM debian:sid FROM debian:sid-slim
RUN apt update && \ RUN apt update && \
apt install --no-install-recommends --no-install-suggests -y \ apt install --no-install-recommends --no-install-suggests -y \

View File

@ -6,6 +6,6 @@ RUN export FEATURES="-ipc-sandbox -network-sandbox -pid-sandbox -sandbox -usersa
emerge --sync && \ emerge --sync && \
eselect news read --quiet new 1>/dev/null 2>&1 && \ eselect news read --quiet new 1>/dev/null 2>&1 && \
emerge --verbose --update --deep --with-bdeps=y --backtrack=30 --newuse @world && \ emerge --verbose --update --deep --with-bdeps=y --backtrack=30 --newuse @world && \
USE="wayland gtk3 gtk -doc X pulseaudio minimal" emerge dev-vcs/git dev-libs/wayland dev-libs/wayland-protocols =dev-cpp/gtkmm-3.24.6 x11-libs/libxkbcommon \ USE="wayland gtk3 gtk -doc X pulseaudio minimal" emerge dev-vcs/git dev-libs/wayland dev-libs/wayland-protocols dev-cpp/gtkmm:3.0 x11-libs/libxkbcommon \
x11-libs/gtk+:3 dev-libs/libdbusmenu dev-libs/libnl sys-power/upower media-libs/libpulse dev-libs/libevdev media-libs/libmpdclient \ x11-libs/gtk+:3 dev-libs/libdbusmenu dev-libs/libnl sys-power/upower media-libs/libpulse dev-libs/libevdev media-libs/libmpdclient \
media-sound/sndio gui-libs/gtk-layer-shell app-text/scdoc media-sound/playerctl dev-libs/iniparser sci-libs/fftw media-sound/sndio gui-libs/gtk-layer-shell app-text/scdoc media-sound/playerctl dev-libs/iniparser sci-libs/fftw

View File

@ -9,7 +9,7 @@
- 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) - 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://codeberg.org/dwl/dwl-patches/src/branch/main/patches/ipc)
- Tray [#21](https://github.com/Alexays/Waybar/issues/21) - Tray [#21](https://github.com/Alexays/Waybar/issues/21)
- Local time - Local time
- Battery - Battery

View File

@ -1,10 +1,9 @@
(import (import (
( let
let lock = builtins.fromJSON (builtins.readFile ./flake.lock); in lock = builtins.fromJSON (builtins.readFile ./flake.lock);
in
fetchTarball { fetchTarball {
url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz"; url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz";
sha256 = lock.nodes.flake-compat.locked.narHash; sha256 = lock.nodes.flake-compat.locked.narHash;
} }
) ) { src = ./.; }).defaultNix
{ src = ./.; }
).defaultNix

12
flake.lock generated
View File

@ -3,11 +3,11 @@
"flake-compat": { "flake-compat": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1733328505, "lastModified": 1747046372,
"narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=", "narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=",
"owner": "edolstra", "owner": "edolstra",
"repo": "flake-compat", "repo": "flake-compat",
"rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec", "rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -18,11 +18,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1735471104, "lastModified": 1748460289,
"narHash": "sha256-0q9NGQySwDQc7RhAV2ukfnu7Gxa5/ybJ2ANT8DQrQrs=", "narHash": "sha256-7doLyJBzCllvqX4gszYtmZUToxKvMUrg45EUWaUYmBg=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "88195a94f390381c6afcdaa933c2f6ff93959cb4", "rev": "96ec055edbe5ee227f28cdbc3f1ddf1df5965102",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@ -9,48 +9,97 @@
}; };
}; };
outputs = { self, nixpkgs, ... }: outputs =
{ self, nixpkgs, ... }:
let let
inherit (nixpkgs) lib; inherit (nixpkgs) lib;
genSystems = func: lib.genAttrs [ genSystems =
func:
lib.genAttrs
[
"x86_64-linux" "x86_64-linux"
"aarch64-linux" "aarch64-linux"
] ]
(system: func (import nixpkgs { (
system:
func (
import nixpkgs {
inherit system; inherit system;
overlays = with self.overlays; [ overlays = with self.overlays; [
waybar waybar
]; ];
})); }
)
);
mkDate = longDate: (lib.concatStringsSep "-" [ mkDate =
longDate:
(lib.concatStringsSep "-" [
(builtins.substring 0 4 longDate) (builtins.substring 0 4 longDate)
(builtins.substring 4 2 longDate) (builtins.substring 4 2 longDate)
(builtins.substring 6 2 longDate) (builtins.substring 6 2 longDate)
]); ]);
in in
{ {
devShells = genSystems devShells = genSystems (pkgs: {
(pkgs: default = pkgs.mkShell {
{
default =
pkgs.mkShell
{
name = "waybar-shell"; name = "waybar-shell";
# inherit attributes from upstream nixpkgs derivation # inherit attributes from upstream nixpkgs derivation
inherit (pkgs.waybar) buildInputs depsBuildBuild depsBuildBuildPropagated depsBuildTarget inherit (pkgs.waybar)
depsBuildTargetPropagated depsHostHost depsHostHostPropagated depsTargetTarget buildInputs
depsTargetTargetPropagated propagatedBuildInputs propagatedNativeBuildInputs strictDeps; depsBuildBuild
depsBuildBuildPropagated
depsBuildTarget
depsBuildTargetPropagated
depsHostHost
depsHostHostPropagated
depsTargetTarget
depsTargetTargetPropagated
propagatedBuildInputs
propagatedNativeBuildInputs
strictDeps
;
# overrides for local development # overrides for local development
nativeBuildInputs = pkgs.waybar.nativeBuildInputs ++ (with pkgs; [ nativeBuildInputs =
pkgs.waybar.nativeBuildInputs
++ (with pkgs; [
nixfmt-rfc-style
clang-tools clang-tools
gdb gdb
]); ]);
}; };
}); });
formatter = genSystems (
pkgs:
pkgs.treefmt.withConfig {
settings = [
{
formatter = {
clang-format = {
options = [ "-i" ];
command = lib.getExe' pkgs.clang-tools "clang-format";
excludes = [ ];
includes = [
"*.c"
"*.cpp"
"*.h"
"*.hpp"
];
};
nixfmt = {
command = lib.getExe pkgs.nixfmt-rfc-style;
includes = [ "*.nix" ];
};
};
tree-root-file = ".git/index";
}
];
}
);
overlays = { overlays = {
default = self.overlays.waybar; default = self.overlays.waybar;
waybar = final: prev: { waybar = final: prev: {
@ -58,11 +107,15 @@
waybar = prev.waybar; waybar = prev.waybar;
# take the first "version: '...'" from meson.build # take the first "version: '...'" from meson.build
version = version =
(builtins.head (builtins.split "'" (builtins.head (
(builtins.elemAt builtins.split "'" (
(builtins.split " version: '" (builtins.readFile ./meson.build)) builtins.elemAt (builtins.split " version: '" (builtins.readFile ./meson.build)) 2
2))) )
+ "+date=" + (mkDate (self.lastModifiedDate or "19700101")) + "_" + (self.shortRev or "dirty"); ))
+ "+date="
+ (mkDate (self.lastModifiedDate or "19700101"))
+ "_"
+ (self.shortRev or "dirty");
}; };
}; };
}; };

View File

@ -36,6 +36,7 @@ class AModule : public IModule {
SCROLL_DIR getScrollDir(GdkEventScroll *e); SCROLL_DIR getScrollDir(GdkEventScroll *e);
bool tooltipEnabled() const; bool tooltipEnabled() const;
std::vector<int> pid_children_;
const std::string name_; const std::string name_;
const Json::Value &config_; const Json::Value &config_;
Gtk::EventBox event_box_; Gtk::EventBox event_box_;
@ -54,7 +55,6 @@ class AModule : public IModule {
const bool isTooltip; const bool isTooltip;
const bool isExpand; const bool isExpand;
bool hasUserEvents_; bool hasUserEvents_;
std::vector<int> pid_;
gdouble distance_scrolled_y_; gdouble distance_scrolled_y_;
gdouble distance_scrolled_x_; gdouble distance_scrolled_x_;
std::map<std::string, std::string> eventActionMap_; std::map<std::string, std::string> eventActionMap_;

View File

@ -20,7 +20,7 @@ class Config {
static std::optional<std::string> findConfigPath( static std::optional<std::string> findConfigPath(
const std::vector<std::string> &names, const std::vector<std::string> &dirs = CONFIG_DIRS); const std::vector<std::string> &names, const std::vector<std::string> &dirs = CONFIG_DIRS);
static std::optional<std::string> tryExpandPath(const std::string &base, static std::vector<std::string> tryExpandPath(const std::string &base,
const std::string &filename); const std::string &filename);
Config() = default; Config() = default;

View File

@ -5,7 +5,16 @@
namespace cava { namespace cava {
extern "C" { extern "C" {
// Need sdl_glsl output feature to be enabled on libcava
#ifndef SDL_GLSL
#define SDL_GLSL
#endif
#include <cava/common.h> #include <cava/common.h>
#ifdef SDL_GLSL
#undef SDL_GLSL
#endif
} }
} // namespace cava } // namespace cava
@ -23,10 +32,10 @@ class Cava final : public ALabel {
util::SleeperThread thread_; util::SleeperThread thread_;
util::SleeperThread thread_fetch_input_; util::SleeperThread thread_fetch_input_;
struct cava::error_s error_ {}; // cava errors struct cava::error_s error_{}; // cava errors
struct cava::config_params prm_ {}; // cava parameters struct cava::config_params prm_{}; // cava parameters
struct cava::audio_raw audio_raw_ {}; // cava handled raw audio data(is based on audio_data) struct cava::audio_raw audio_raw_{}; // cava handled raw audio data(is based on audio_data)
struct cava::audio_data audio_data_ {}; // cava audio data struct cava::audio_data audio_data_{}; // cava audio data
struct cava::cava_plan* plan_; //{new cava_plan{}}; struct cava::cava_plan* plan_; //{new cava_plan{}};
// Cava API to read audio source // Cava API to read audio source
cava::ptr input_source_; cava::ptr input_source_;
@ -44,7 +53,7 @@ class Cava final : public ALabel {
// Cava method // Cava method
void pause_resume(); void pause_resume();
// ModuleActionMap // ModuleActionMap
static inline std::map<const std::string, void (waybar::modules::Cava::*const)()> actionMap_{ static inline std::map<const std::string, void (waybar::modules::Cava::* const)()> actionMap_{
{"mode", &waybar::modules::Cava::pause_resume}}; {"mode", &waybar::modules::Cava::pause_resume}};
}; };
} // namespace waybar::modules } // namespace waybar::modules

View File

@ -42,24 +42,24 @@ class Clock final : public ALabel {
int cldWnLen_{3}; // calendar week number length int cldWnLen_{3}; // calendar week number length
const int cldMonColLen_{20}; // calendar month column length const int cldMonColLen_{20}; // calendar month column length
WS cldWPos_{WS::HIDDEN}; // calendar week side to print WS cldWPos_{WS::HIDDEN}; // calendar week side to print
months cldCurrShift_{0}; // calendar months shift date::months cldCurrShift_{0}; // calendar months shift
int cldShift_{1}; // calendar months shift factor int cldShift_{1}; // calendar months shift factor
year_month_day cldYearShift_; // calendar Year mode. Cached ymd date::year_month_day cldYearShift_; // calendar Year mode. Cached ymd
std::string cldYearCached_; // calendar Year mode. Cached calendar std::string cldYearCached_; // calendar Year mode. Cached calendar
year_month cldMonShift_; // calendar Month mode. Cached ym date::year_month cldMonShift_; // calendar Month mode. Cached ym
std::string cldMonCached_; // calendar Month mode. Cached calendar std::string cldMonCached_; // calendar Month mode. Cached calendar
day cldBaseDay_{0}; // calendar Cached day. Is used when today is changing(midnight) date::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, auto get_calendar(const date::year_month_day& today, const date::year_month_day& ymd,
const time_zone* tz) -> const std::string; const date::time_zone* tz) -> const std::string;
// get local time zone // get local time zone
auto local_zone() -> const time_zone*; auto local_zone() -> const date::time_zone*;
// time zoned time in tooltip // time zoned time in tooltip
const bool tzInTooltip_; // if need to print time zones text const bool tzInTooltip_; // if need to print time zones text
std::vector<const time_zone*> tzList_; // time zones list std::vector<const date::time_zone*> tzList_; // time zones list
int tzCurrIdx_; // current time zone index for tzList_ int tzCurrIdx_; // current time zone index for tzList_
std::string tzText_{""}; // time zones text to print std::string tzText_{""}; // time zones text to print
util::SleeperThread thread_; util::SleeperThread thread_;
@ -67,10 +67,10 @@ class Clock final : public ALabel {
// ordinal date in tooltip // ordinal date in tooltip
const bool ordInTooltip_; const bool ordInTooltip_;
std::string ordText_{""}; std::string ordText_{""};
auto get_ordinal_date(const year_month_day& today) -> std::string; auto get_ordinal_date(const date::year_month_day& today) -> std::string;
auto getTZtext(sys_seconds now) -> std::string; auto getTZtext(date::sys_seconds now) -> std::string;
auto first_day_of_week() -> weekday; auto first_day_of_week() -> date::weekday;
// Module actions // Module actions
void cldModeSwitch(); void cldModeSwitch();
void cldShift_up(); void cldShift_up();
@ -79,7 +79,7 @@ class Clock final : public ALabel {
void tz_up(); void tz_up();
void tz_down(); void tz_down();
// Module Action Map // Module Action Map
static inline std::map<const std::string, void (waybar::modules::Clock::*const)()> actionMap_{ static inline std::map<const std::string, void (waybar::modules::Clock::* const)()> actionMap_{
{"mode", &waybar::modules::Clock::cldModeSwitch}, {"mode", &waybar::modules::Clock::cldModeSwitch},
{"shift_up", &waybar::modules::Clock::cldShift_up}, {"shift_up", &waybar::modules::Clock::cldShift_up},
{"shift_down", &waybar::modules::Clock::cldShift_down}, {"shift_down", &waybar::modules::Clock::cldShift_down},

View File

@ -2,9 +2,9 @@
#include <filesystem> #include <filesystem>
#include <list> #include <list>
#include <memory>
#include <mutex> #include <mutex>
#include <string> #include <string>
#include <thread>
#include <utility> #include <utility>
#include "util/json.hpp" #include "util/json.hpp"
@ -19,7 +19,9 @@ class EventHandler {
class IPC { class IPC {
public: public:
IPC() { startIPC(); } IPC();
~IPC();
static IPC& inst();
void registerForIPC(const std::string& ev, EventHandler* ev_handler); void registerForIPC(const std::string& ev, EventHandler* ev_handler);
void unregisterForIPC(EventHandler* handler); void unregisterForIPC(EventHandler* handler);
@ -32,14 +34,16 @@ class IPC {
static std::filesystem::path socketFolder_; static std::filesystem::path socketFolder_;
private: private:
void startIPC(); void socketListener();
void parseIPC(const std::string&); void parseIPC(const std::string&);
std::thread ipcThread_;
std::mutex callbackMutex_; std::mutex callbackMutex_;
util::JsonParser parser_; util::JsonParser parser_;
std::list<std::pair<std::string, EventHandler*>> callbacks_; std::list<std::pair<std::string, EventHandler*>> callbacks_;
int socketfd_; // the hyprland socket file descriptor
bool running_ = true;
}; };
inline std::unique_ptr<IPC> gIPC;
inline bool modulesReady = false; inline bool modulesReady = false;
}; // namespace waybar::modules::hyprland }; // namespace waybar::modules::hyprland

View File

@ -37,6 +37,8 @@ class Language : public waybar::ALabel, public EventHandler {
util::JsonParser parser_; util::JsonParser parser_;
Layout layout_; Layout layout_;
IPC& m_ipc;
}; };
} // namespace waybar::modules::hyprland } // namespace waybar::modules::hyprland

View File

@ -28,6 +28,8 @@ class Submap : public waybar::ALabel, public EventHandler {
std::string submap_; std::string submap_;
bool always_on_ = false; bool always_on_ = false;
std::string default_submap_ = "Default"; std::string default_submap_ = "Default";
IPC& m_ipc;
}; };
} // namespace waybar::modules::hyprland } // namespace waybar::modules::hyprland

View File

@ -60,6 +60,8 @@ class Window : public waybar::AAppIconLabel, public EventHandler {
bool swallowing_; bool swallowing_;
bool fullscreen_; bool fullscreen_;
bool focused_; bool focused_;
IPC& m_ipc;
}; };
} // namespace waybar::modules::hyprland } // namespace waybar::modules::hyprland

View File

@ -42,7 +42,7 @@ class WindowCreationPayload {
std::string getWorkspaceName() const { return m_workspaceName; } std::string getWorkspaceName() const { return m_workspaceName; }
WindowAddress getAddress() const { return m_windowAddress; } WindowAddress getAddress() const { return m_windowAddress; }
void moveToWorksace(std::string& new_workspace_name); void moveToWorkspace(std::string& new_workspace_name);
private: private:
void clearAddr(); void clearAddr();

View File

@ -55,11 +55,11 @@ class Workspace {
void setName(std::string const& value) { m_name = value; }; void setName(std::string const& value) { m_name = value; };
void setOutput(std::string const& value) { m_output = value; }; void setOutput(std::string const& value) { m_output = value; };
bool containsWindow(WindowAddress const& addr) const { return m_windowMap.contains(addr); } bool containsWindow(WindowAddress const& addr) const { return m_windowMap.contains(addr); }
void insertWindow(WindowCreationPayload create_window_paylod); void insertWindow(WindowCreationPayload create_window_payload);
std::string removeWindow(WindowAddress const& addr); std::string removeWindow(WindowAddress const& addr);
void initializeWindowMap(const Json::Value& clients_data); void initializeWindowMap(const Json::Value& clients_data);
bool onWindowOpened(WindowCreationPayload const& create_window_paylod); bool onWindowOpened(WindowCreationPayload const& create_window_payload);
std::optional<std::string> closeWindow(WindowAddress const& addr); std::optional<std::string> closeWindow(WindowAddress const& addr);
void update(const std::string& format, const std::string& icon); void update(const std::string& format, const std::string& icon);
@ -83,6 +83,7 @@ class Workspace {
Gtk::Button m_button; Gtk::Button m_button;
Gtk::Box m_content; Gtk::Box m_content;
Gtk::Label m_label; Gtk::Label m_label;
IPC& m_ipc;
}; };
} // namespace waybar::modules::hyprland } // namespace waybar::modules::hyprland

View File

@ -7,6 +7,7 @@
#include <cstdint> #include <cstdint>
#include <map> #include <map>
#include <memory> #include <memory>
#include <optional>
#include <regex> #include <regex>
#include <string> #include <string>
#include <vector> #include <vector>
@ -36,6 +37,7 @@ class Workspaces : public AModule, public EventHandler {
auto showSpecial() const -> bool { return m_showSpecial; } auto showSpecial() const -> bool { return m_showSpecial; }
auto activeOnly() const -> bool { return m_activeOnly; } auto activeOnly() const -> bool { return m_activeOnly; }
auto specialVisibleOnly() const -> bool { return m_specialVisibleOnly; } auto specialVisibleOnly() const -> bool { return m_specialVisibleOnly; }
auto persistentOnly() const -> bool { return m_persistentOnly; }
auto moveToMonitor() const -> bool { return m_moveToMonitor; } auto moveToMonitor() const -> bool { return m_moveToMonitor; }
auto getBarOutput() const -> std::string { return m_bar.output->name; } auto getBarOutput() const -> std::string { return m_bar.output->name; }
@ -55,14 +57,14 @@ class Workspaces : public AModule, public EventHandler {
static Json::Value createMonitorWorkspaceData(std::string const& name, static Json::Value createMonitorWorkspaceData(std::string const& name,
std::string const& monitor); std::string const& monitor);
void removeWorkspace(std::string const& name); void removeWorkspace(std::string const& workspaceString);
void setUrgentWorkspace(std::string const& windowaddress); void setUrgentWorkspace(std::string const& windowaddress);
// 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, static auto populateBoolConfig(const Json::Value& config, const std::string& key, bool& member)
bool& member) -> void; -> 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;
@ -74,10 +76,11 @@ class Workspaces : public AModule, public EventHandler {
void onWorkspaceActivated(std::string const& payload); void onWorkspaceActivated(std::string const& payload);
void onSpecialWorkspaceActivated(std::string const& payload); void onSpecialWorkspaceActivated(std::string const& payload);
void onWorkspaceDestroyed(std::string const& payload); void onWorkspaceDestroyed(std::string const& payload);
void onWorkspaceCreated(std::string const& workspaceName, void onWorkspaceCreated(std::string const& payload,
Json::Value const& clientsData = Json::Value::nullRef); Json::Value const& clientsData = Json::Value::nullRef);
void onWorkspaceMoved(std::string const& payload); void onWorkspaceMoved(std::string const& payload);
void onWorkspaceRenamed(std::string const& payload); void onWorkspaceRenamed(std::string const& payload);
static std::optional<int> parseWorkspaceId(std::string const& workspaceIdStr);
// monitor events // monitor events
void onMonitorFocused(std::string const& payload); void onMonitorFocused(std::string const& payload);
@ -93,11 +96,18 @@ class Workspaces : public AModule, public EventHandler {
int windowRewritePriorityFunction(std::string const& window_rule); int windowRewritePriorityFunction(std::string const& window_rule);
// event payload management
template <typename... Args>
static std::string makePayload(Args const&... args);
static std::pair<std::string, std::string> splitDoublePayload(std::string const& payload);
static std::tuple<std::string, std::string, std::string> splitTriplePayload(
std::string const& payload);
// Update methods // Update methods
void doUpdate(); void doUpdate();
void removeWorkspacesToRemove(); void removeWorkspacesToRemove();
void createWorkspacesToCreate(); void createWorkspacesToCreate();
static std::vector<std::string> getVisibleWorkspaces(); static std::vector<int> getVisibleWorkspaces();
void updateWorkspaceStates(); void updateWorkspaceStates();
bool updateWindowsToCreate(); bool updateWindowsToCreate();
@ -113,12 +123,13 @@ class Workspaces : public AModule, public EventHandler {
bool m_showSpecial = false; bool m_showSpecial = false;
bool m_activeOnly = false; bool m_activeOnly = false;
bool m_specialVisibleOnly = false; bool m_specialVisibleOnly = false;
bool m_persistentOnly = false;
bool m_moveToMonitor = false; bool m_moveToMonitor = false;
Json::Value m_persistentWorkspaceConfig; Json::Value m_persistentWorkspaceConfig;
// Map for windows stored in workspaces not present in the current bar. // Map for windows stored in workspaces not present in the current bar.
// This happens when the user has multiple monitors (hence, multiple bars) // This happens when the user has multiple monitors (hence, multiple bars)
// and doesn't share windows accross bars (a.k.a `all-outputs` = false) // and doesn't share windows across bars (a.k.a `all-outputs` = false)
std::map<WindowAddress, std::string> m_orphanWindowMap; std::map<WindowAddress, std::string> m_orphanWindowMap;
enum class SortMethod { ID, NAME, NUMBER, DEFAULT }; enum class SortMethod { ID, NAME, NUMBER, DEFAULT };
@ -138,7 +149,7 @@ class Workspaces : public AModule, public EventHandler {
bool m_withIcon; bool m_withIcon;
uint64_t m_monitorId; uint64_t m_monitorId;
std::string m_activeWorkspaceName; int m_activeWorkspaceId;
std::string m_activeSpecialWorkspaceName; std::string m_activeSpecialWorkspaceName;
std::vector<std::unique_ptr<Workspace>> m_workspaces; std::vector<std::unique_ptr<Workspace>> m_workspaces;
std::vector<std::pair<Json::Value, Json::Value>> m_workspacesToCreate; std::vector<std::pair<Json::Value, Json::Value>> m_workspacesToCreate;
@ -150,6 +161,7 @@ class Workspaces : public AModule, public EventHandler {
std::mutex m_mutex; std::mutex m_mutex;
const Bar& m_bar; const Bar& m_bar;
Gtk::Box m_box; Gtk::Box m_box;
IPC& m_ipc;
}; };
} // namespace waybar::modules::hyprland } // namespace waybar::modules::hyprland

View File

@ -16,6 +16,8 @@
#include "util/rfkill.hpp" #include "util/rfkill.hpp"
#endif #endif
enum ip_addr_pref : uint8_t { IPV4, IPV6, IPV4_6 };
namespace waybar::modules { namespace waybar::modules {
class Network : public ALabel { class Network : public ALabel {
@ -50,6 +52,7 @@ class Network : public ALabel {
std::optional<std::pair<unsigned long long, unsigned long long>> readBandwidthUsage(); std::optional<std::pair<unsigned long long, unsigned long long>> readBandwidthUsage();
int ifid_; int ifid_;
ip_addr_pref addr_pref_;
struct sockaddr_nl nladdr_ = {0}; struct sockaddr_nl nladdr_ = {0};
struct nl_sock* sock_ = nullptr; struct nl_sock* sock_ = nullptr;
struct nl_sock* ev_sock_ = nullptr; struct nl_sock* ev_sock_ = nullptr;
@ -73,9 +76,12 @@ class Network : public ALabel {
bool carrier_; bool carrier_;
std::string ifname_; std::string ifname_;
std::string ipaddr_; std::string ipaddr_;
std::string ipaddr6_;
std::string gwaddr_; std::string gwaddr_;
std::string netmask_; std::string netmask_;
std::string netmask6_;
int cidr_; int cidr_;
int cidr6_;
int32_t signal_strength_dbm_; int32_t signal_strength_dbm_;
uint8_t signal_strength_; uint8_t signal_strength_;
std::string signal_strength_app_; std::string signal_strength_app_;

View File

@ -13,7 +13,7 @@ namespace waybar::modules::privacy {
class Privacy : public AModule { class Privacy : public AModule {
public: public:
Privacy(const std::string &, const Json::Value &, const std::string &pos); Privacy(const std::string &, const Json::Value &, Gtk::Orientation, const std::string &pos);
auto update() -> void override; auto update() -> void override;
void onPrivacyNodesChanged(); void onPrivacyNodesChanged();
@ -31,6 +31,8 @@ class Privacy : public AModule {
uint iconSpacing = 4; uint iconSpacing = 4;
uint iconSize = 20; uint iconSize = 20;
uint transition_duration = 250; uint transition_duration = 250;
std::set<std::pair<PrivacyNodeType, std::string>> ignore;
bool ignore_monitor = true;
std::shared_ptr<util::PipewireBackend::PipewireBackend> backend = nullptr; std::shared_ptr<util::PipewireBackend::PipewireBackend> backend = nullptr;
}; };

View File

@ -17,8 +17,8 @@ namespace waybar::modules::privacy {
class PrivacyItem : public Gtk::Revealer { class PrivacyItem : public Gtk::Revealer {
public: public:
PrivacyItem(const Json::Value &config_, enum PrivacyNodeType privacy_type_, PrivacyItem(const Json::Value &config_, enum PrivacyNodeType privacy_type_,
std::list<PrivacyNodeInfo *> *nodes, const std::string &pos, const uint icon_size, std::list<PrivacyNodeInfo *> *nodes, Gtk::Orientation orientation,
const uint transition_duration); const std::string &pos, const uint icon_size, const uint transition_duration);
enum PrivacyNodeType privacy_type; enum PrivacyNodeType privacy_type;

View File

@ -0,0 +1,43 @@
#pragma once
#include <json/json.h>
#include <spdlog/spdlog.h>
#include <string>
#include <unordered_map>
class IconManager {
public:
static IconManager& instance() {
static IconManager instance;
return instance;
}
void setIconsConfig(const Json::Value& icons_config) {
if (icons_config.isObject()) {
for (const auto& key : icons_config.getMemberNames()) {
std::string app_name = key;
const Json::Value& icon_value = icons_config[key];
if (icon_value.isString()) {
std::string icon_path = icon_value.asString();
icons_map_[app_name] = icon_path;
}
}
} else {
spdlog::warn("Invalid icon config format.");
}
}
std::string getIconForApp(const std::string& app_name) const {
auto it = icons_map_.find(app_name);
if (it != icons_map_.end()) {
return it->second;
}
return "";
}
private:
IconManager() = default;
std::unordered_map<std::string, std::string> icons_map_;
};

View File

@ -62,6 +62,7 @@ class Item : public sigc::trackable {
void proxyReady(Glib::RefPtr<Gio::AsyncResult>& result); void proxyReady(Glib::RefPtr<Gio::AsyncResult>& result);
void setProperty(const Glib::ustring& name, Glib::VariantBase& value); void setProperty(const Glib::ustring& name, Glib::VariantBase& value);
void setStatus(const Glib::ustring& value); void setStatus(const Glib::ustring& value);
void setCustomIcon(const std::string& id);
void getUpdatedProperties(); void getUpdatedProperties();
void processUpdatedProperties(Glib::RefPtr<Gio::AsyncResult>& result); void processUpdatedProperties(Glib::RefPtr<Gio::AsyncResult>& result);
void onSignal(const Glib::ustring& sender_name, const Glib::ustring& signal_name, void onSignal(const Glib::ustring& sender_name, const Glib::ustring& signal_name,

View File

@ -21,7 +21,7 @@ class Language : public ALabel, public sigc::trackable {
auto update() -> void override; auto update() -> void override;
private: private:
enum class DispayedShortFlag { None = 0, ShortName = 1, ShortDescription = 1 << 1 }; enum class DisplayedShortFlag { None = 0, ShortName = 1, ShortDescription = 1 << 1 };
struct Layout { struct Layout {
std::string full_name; std::string full_name;
@ -58,7 +58,7 @@ class Language : public ALabel, public sigc::trackable {
std::map<std::string, Layout> layouts_map_; std::map<std::string, Layout> layouts_map_;
bool hide_single_; bool hide_single_;
bool is_variant_displayed; bool is_variant_displayed;
std::byte displayed_short_flag = static_cast<std::byte>(DispayedShortFlag::None); std::byte displayed_short_flag = static_cast<std::byte>(DisplayedShortFlag::None);
util::JsonParser parser_; util::JsonParser parser_;
std::mutex mutex_; std::mutex mutex_;

View File

@ -19,10 +19,11 @@ class Window : public AAppIconLabel, public sigc::trackable {
auto update() -> void override; auto update() -> void override;
private: private:
void setClass(std::string classname, bool enable); void setClass(const std::string& classname, bool enable);
void onEvent(const struct Ipc::ipc_response&); void onEvent(const struct Ipc::ipc_response&);
void onCmd(const struct Ipc::ipc_response&); void onCmd(const struct Ipc::ipc_response&);
std::tuple<std::size_t, int, int, std::string, std::string, std::string, std::string, std::string> std::tuple<std::size_t, int, int, std::string, std::string, std::string, std::string, std::string,
std::string>
getFocusedNode(const Json::Value& nodes, std::string& output); getFocusedNode(const Json::Value& nodes, std::string& output);
void getTree(); void getTree();
@ -35,6 +36,7 @@ class Window : public AAppIconLabel, public sigc::trackable {
std::string old_app_id_; std::string old_app_id_;
std::size_t app_nb_; std::size_t app_nb_;
std::string shell_; std::string shell_;
std::string marks_;
int floating_count_; int floating_count_;
util::JsonParser parser_; util::JsonParser parser_;
std::mutex mutex_; std::mutex mutex_;

View File

@ -48,7 +48,7 @@ class Workspaces : public AModule, public sigc::trackable {
std::vector<std::string> high_priority_named_; std::vector<std::string> high_priority_named_;
std::vector<std::string> workspaces_order_; std::vector<std::string> workspaces_order_;
Gtk::Box box_; Gtk::Box box_;
std::string m_formatWindowSeperator; std::string m_formatWindowSeparator;
util::RegexCollection m_windowRewriteRules; util::RegexCollection m_windowRewriteRules;
util::JsonParser parser_; util::JsonParser parser_;
std::unordered_map<std::string, Gtk::Button> buttons_; std::unordered_map<std::string, Gtk::Button> buttons_;

View File

@ -19,12 +19,15 @@ class SystemdFailedUnits : public ALabel {
std::string format_ok; std::string format_ok;
bool update_pending; bool update_pending;
uint32_t nr_failed_system, nr_failed_user; std::string system_state, user_state, overall_state;
uint32_t nr_failed_system, nr_failed_user, nr_failed;
std::string last_status; std::string last_status;
Glib::RefPtr<Gio::DBus::Proxy> system_proxy, user_proxy; Glib::RefPtr<Gio::DBus::Proxy> system_proxy, user_proxy;
void notify_cb(const Glib::ustring &sender_name, const Glib::ustring &signal_name, void notify_cb(const Glib::ustring &sender_name, const Glib::ustring &signal_name,
const Glib::VariantContainerBase &arguments); const Glib::VariantContainerBase &arguments);
void RequestFailedUnits();
void RequestSystemState();
void updateData(); void updateData();
}; };

View File

@ -18,7 +18,7 @@ class Wireplumber : public ALabel {
private: private:
void asyncLoadRequiredApiModules(); void asyncLoadRequiredApiModules();
void prepare(); void prepare(waybar::modules::Wireplumber* self);
void activatePlugins(); void activatePlugins();
static void updateVolume(waybar::modules::Wireplumber* self, uint32_t id); static void updateVolume(waybar::modules::Wireplumber* self, uint32_t id);
static void updateNodeName(waybar::modules::Wireplumber* self, uint32_t id); static void updateNodeName(waybar::modules::Wireplumber* self, uint32_t id);
@ -32,6 +32,8 @@ class Wireplumber : public ALabel {
bool handleScroll(GdkEventScroll* e) override; bool handleScroll(GdkEventScroll* e) override;
static std::list<waybar::modules::Wireplumber*> modules;
WpCore* wp_core_; WpCore* wp_core_;
GPtrArray* apis_; GPtrArray* apis_;
WpObjectManager* om_; WpObjectManager* om_;
@ -44,6 +46,7 @@ class Wireplumber : public ALabel {
double min_step_; double min_step_;
uint32_t node_id_{0}; uint32_t node_id_{0};
std::string node_name_; std::string node_name_;
gchar* type_;
}; };
} // namespace waybar::modules } // namespace waybar::modules

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, inline auto convertInto(std::string const &source, CLARA_CONFIG_OPTIONAL_TYPE<T> &target)
CLARA_CONFIG_OPTIONAL_TYPE<T> &target) -> ParserResult { -> 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, virtual auto parse(std::string const &exeName, TokenStream const &tokens) const
TokenStream const &tokens) const -> InternalParseResult = 0; -> 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, auto parse(std::string const &exeName, TokenStream const &tokens) const
TokenStream const &tokens) const -> InternalParseResult override { -> InternalParseResult override {
struct ParserInfo { struct ParserInfo {
ParserBase const *parser = nullptr; ParserBase const *parser = nullptr;
size_t count = 0; size_t count = 0;

View File

@ -15,7 +15,7 @@
namespace date { namespace date {
#if HAVE_CHRONO_TIMEZONES #if HAVE_CHRONO_TIMEZONES
using namespace std::chrono; using namespace std::chrono;
using namespace std; using std::format;
#else #else
using system_clock = std::chrono::system_clock; using system_clock = std::chrono::system_clock;
@ -73,5 +73,3 @@ struct fmt::formatter<date::zoned_time<Duration, TimeZonePtr>> {
} }
}; };
#endif #endif
using namespace date;

View File

@ -10,5 +10,7 @@ class DefaultGtkIconThemeWrapper {
public: public:
static bool has_icon(const std::string&); static bool has_icon(const std::string&);
static Glib::RefPtr<Gdk::Pixbuf> load_icon(const char*, int, Gtk::IconLookupFlags); static Glib::RefPtr<Gdk::Pixbuf> load_icon(
const char*, int, Gtk::IconLookupFlags,
Glib::RefPtr<Gtk::StyleContext> style = Glib::RefPtr<Gtk::StyleContext>());
}; };

View File

@ -25,6 +25,7 @@ class PrivacyNodeInfo {
std::string media_name; std::string media_name;
std::string node_name; std::string node_name;
std::string application_name; std::string application_name;
bool is_monitor = false;
std::string pipewire_access_portal_app_id; std::string pipewire_access_portal_app_id;
std::string application_icon_name; std::string application_icon_name;

View File

@ -6,14 +6,12 @@
namespace waybar { namespace waybar {
using namespace Gio;
enum class Appearance { enum class Appearance {
UNKNOWN = 0, UNKNOWN = 0,
DARK = 1, DARK = 1,
LIGHT = 2, LIGHT = 2,
}; };
class Portal : private DBus::Proxy { class Portal : private Gio::DBus::Proxy {
public: public:
Portal(); Portal();
void refreshAppearance(); void refreshAppearance();

View File

@ -4,6 +4,6 @@
namespace waybar::util { namespace waybar::util {
// Get a signal emited with value true when entering sleep, and false when exiting // Get a signal emitted with value true when entering sleep, and false when exiting
SafeSignal<bool>& prepare_for_sleep(); SafeSignal<bool>& prepare_for_sleep();
} // namespace waybar::util } // namespace waybar::util

View File

@ -40,7 +40,7 @@ The brightness can be controlled by dragging the slider across the bar or clicki
``` ```
"modules-right": [ "modules-right": [
"backlight-slider", "backlight/slider",
], ],
"backlight/slider": { "backlight/slider": {
"min": 0, "min": 0,

View File

@ -117,7 +117,7 @@ View all valid format options in *strftime(3)* or have a look https://en.cpprefe
:[ 3 :[ 3
:[ Relevant for *mode=year*. Count of months per row :[ Relevant for *mode=year*. Count of months per row
|[ *weeks-pos* |[ *weeks-pos*
:[ integer :[ string
:[ :[
:[ The position where week numbers should be displayed. Disabled when is empty. :[ The position where week numbers should be displayed. Disabled when is empty.
Possible values: left|right Possible values: left|right

View File

@ -59,9 +59,9 @@ Addressed by *hyprland/language*
``` ```
"hyprland/language": { "hyprland/language": {
"format": "Lang: {long}" "format": "Lang: {long}",
"format-en": "AMERICA, HELL YEAH!" "format-en": "AMERICA, HELL YEAH!",
"format-tr": "As bayrakları" "format-tr": "As bayrakları",
"keyboard-name": "at-translated-set-2-keyboard" "keyboard-name": "at-translated-set-2-keyboard"
} }
``` ```

View File

@ -48,6 +48,11 @@ Addressed by *hyprland/workspaces*
default: false ++ default: false ++
If this and show-special are to true, special workspaces will be shown only if visible. If this and show-special are to true, special workspaces will be shown only if visible.
*persistent-only*: ++
typeof: bool ++
default: false ++
If set to true, only persistent workspaces will be shown on bar.
*all-outputs*: ++ *all-outputs*: ++
typeof: bool ++ typeof: bool ++
default: false ++ default: false ++

View File

@ -125,3 +125,9 @@ screensaver, also known as "presentation mode".
"timeout": 30.5 "timeout": 30.5
} }
``` ```
# STYLE
- *#idle_inhibitor*
- *#idle_inhibitor.activated*
- *#idle_inhibitor.deactivated*

View File

@ -120,6 +120,8 @@ Addressed by *memory*
*{swapAvail}*: Amount of available swap in GiB. *{swapAvail}*: Amount of available swap in GiB.
*{swapState}*: Signals if swap is activated or not
# EXAMPLES # EXAMPLES
``` ```

View File

@ -7,7 +7,7 @@ waybar - menu property
# OVERVIEW # OVERVIEW
Some modules support a 'menu', which allows to have a popup menu whan a defined Some modules support a 'menu', which allows to have a popup menu when a defined
click is done over the module. click is done over the module.
# PROPERTIES # PROPERTIES

View File

@ -24,7 +24,7 @@ Addressed by *network*
*family*: ++ *family*: ++
typeof: string ++ typeof: string ++
default: *ipv4* ++ default: *ipv4* ++
The address family that is used for the format replacement {ipaddr} and to determine if a network connection is present. The address family that is used for the format replacement {ipaddr} and to determine if a network connection is present. Set it to ipv4_6 to display both.
*format*: ++ *format*: ++
typeof: string ++ typeof: string ++
@ -155,9 +155,13 @@ Addressed by *network*
*{gwaddr}*: The default gateway for the interface *{gwaddr}*: The default gateway for the interface
*{netmask}*: The subnetmask corresponding to the IP. *{netmask}*: The subnetmask corresponding to the IP(V4).
*{cidr}*: The subnetmask corresponding to the IP in CIDR notation. *{netmask6}*: The subnetmask corresponding to the IP(V6).
*{cidr}*: The subnetmask corresponding to the IP(V4) in CIDR notation.
*{cidr6}*: The subnetmask corresponding to the IP(V6) in CIDR notation.
*{essid}*: Name (SSID) of the wireless network. *{essid}*: Name (SSID) of the wireless network.
@ -167,7 +171,7 @@ Addressed by *network*
*{signaldBm}*: Signal strength of the wireless network in dBm. *{signaldBm}*: Signal strength of the wireless network in dBm.
*{frequency}*: Frequency of the wireless network in MHz. *{frequency}*: Frequency of the wireless network in GHz.
*{bandwidthUpBits}*: Instant up speed in bits/seconds. *{bandwidthUpBits}*: Instant up speed in bits/seconds.

View File

@ -70,6 +70,7 @@ Additional to workspace name matching, the following *format-icons* can be set.
- *default*: Will be shown, when no string matches are found. - *default*: Will be shown, when no string matches are found.
- *focused*: Will be shown, when workspace is focused. - *focused*: Will be shown, when workspace is focused.
- *active*: Will be shown, when workspace is active on its output. - *active*: Will be shown, when workspace is active on its output.
- *empty*: Will be shown, when workspace is empty.
# EXAMPLES # EXAMPLES

View File

@ -37,6 +37,17 @@ the screen or playing audio.
default: false ++ default: false ++
Enables this module to consume all left over space dynamically. Enables this module to consume all left over space dynamically.
*ignore-monitor* ++
typeof: bool ++
default: true ++
Ignore streams with *stream.monitor* property.
*ignore* ++
typeof: array of objects ++
default: [] ++
Additional streams to be ignored. See *IGNORE CONFIGURATION* for++
more information.
# MODULES CONFIGURATION # MODULES CONFIGURATION
*type*: ++ *type*: ++
@ -54,6 +65,14 @@ the screen or playing audio.
default: 24 ++ default: 24 ++
The size of each icon in the tooltip. The size of each icon in the tooltip.
# IGNORE CONFIGURATION
*type*: ++
typeof: string
*name*: ++
typeof: string
# EXAMPLES # EXAMPLES
``` ```
@ -77,6 +96,17 @@ the screen or playing audio.
"tooltip": true, "tooltip": true,
"tooltip-icon-size": 24 "tooltip-icon-size": 24
} }
],
"ignore-monitor": true,
"ignore": [
{
"type": "audio-in",
"name": "cava"
},
{
"type": "screenshare",
"name": "obs"
}
] ]
}, },
``` ```

View File

@ -31,6 +31,11 @@ Addressed by *river/tags*
default: false ++ default: false ++
Enables this module to consume all left over space dynamically. Enables this module to consume all left over space dynamically.
*hide-vacant*: ++
typeof: bool ++
default: false ++
Only show relevant tags: tags that are either focused or have a window on them.
# EXAMPLE # EXAMPLE
``` ```

View File

@ -89,6 +89,11 @@ Addressed by *sway/window*
default: false ++ default: false ++
If the workspace itself is focused and the workspace contains nodes or floating_nodes, show the workspace name. If not set, text remains empty but styles according to nodes in the workspace are still applied. If the workspace itself is focused and the workspace contains nodes or floating_nodes, show the workspace name. If not set, text remains empty but styles according to nodes in the workspace are still applied.
*show-hidden-marks*: ++
typeof: bool ++
default: false ++
For the *{marks}* format replacement, include hidden marks that start with an underscore.
*rewrite*: ++ *rewrite*: ++
typeof: object ++ typeof: object ++
Rules to rewrite the module format output. See *rewrite rules*. Rules to rewrite the module format output. See *rewrite rules*.
@ -117,6 +122,8 @@ Addressed by *sway/window*
*{shell}*: The shell of the focused window. It's 'xwayland' when the window is *{shell}*: The shell of the focused window. It's 'xwayland' when the window is
running through xwayland, otherwise, it's 'xdg-shell'. running through xwayland, otherwise, it's 'xdg-shell'.
*{marks}*: Marks of the window.
# REWRITE RULES # REWRITE RULES
*rewrite* is an object where keys are regular expressions and values are *rewrite* is an object where keys are regular expressions and values are

View File

@ -62,6 +62,12 @@ Addressed by *systemd-failed-units*
*{nr_failed}*: Number of total failed units. *{nr_failed}*: Number of total failed units.
*{systemd_state}:* State of the systemd system session
*{user_state}:* State of the systemd user session
*{overall_state}:* Overall state of the systemd and user session. ("Ok" or "Degraded")
# EXAMPLES # EXAMPLES
``` ```

View File

@ -47,7 +47,11 @@ Addressed by *tray*
``` ```
"tray": { "tray": {
"icon-size": 21, "icon-size": 21,
"spacing": 10 "spacing": 10,
"icons": {
"blueman": "bluetooth",
"TelegramDesktop": "$HOME/.local/share/icons/hicolor/16x16/apps/telegram.png"
}
} }
``` ```

View File

@ -19,6 +19,11 @@ The *wireplumber* module displays the current volume reported by WirePlumber.
typeof: string ++ typeof: string ++
This format is used when the sound is muted. This format is used when the sound is muted.
*node-type*: ++
typeof: string ++
default: *Audio/Sink* ++
The WirePlumber node type to attach to. Use *Audio/Source* to manage microphones etc.
*tooltip*: ++ *tooltip*: ++
typeof: bool ++ typeof: bool ++
default: *true* ++ default: *true* ++
@ -108,6 +113,8 @@ The *wireplumber* module displays the current volume reported by WirePlumber.
# EXAMPLES # EXAMPLES
## Basic:
``` ```
"wireplumber": { "wireplumber": {
"format": "{volume}%", "format": "{volume}%",
@ -116,6 +123,26 @@ The *wireplumber* module displays the current volume reported by WirePlumber.
} }
``` ```
## Separate Sink and Source Widgets
```
"wireplumber#sink": {
"format": "{volume}% {icon}",
"format-muted": "",
"format-icons": ["", "", ""],
"on-click": "helvum",
"on-click-right": "wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle",
"scroll-step": 5
},
"wireplumber#source": {
"node-type": "Audio/Source",
"format": "{volume}% ",
"format-muted": "",
"on-click-right": "wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle",
"scroll-step": 5
}
```
# STYLE # STYLE
- *#wireplumber* - *#wireplumber*

View File

@ -86,7 +86,7 @@ The visual display elements for waybar use a CSS stylesheet, see *waybar-styles(
*no-center* ++ *no-center* ++
typeof: bool ++ typeof: bool ++
default: false ++ default: false ++
Option to disable the center modules fully usefull together with expand-\*. Option to disable the center modules fully useful together with expand-\*.
*spacing* ++ *spacing* ++
typeof: integer ++ typeof: integer ++
@ -272,6 +272,17 @@ When positioning Waybar on the left or right side of the screen, sometimes it's
Valid options for the "rotate" property are: 0, 90, 180, and 270. Valid options for the "rotate" property are: 0, 90, 180, and 270.
## Swapping icon and label
If a module displays both a label and an icon, it might be desirable to swap them (for instance, for panels on the left or right of the screen, or for user adopting a right-to-left script). This can be achieved with the "swap-icon-label" property, taking a boolean. Example:
```
{
"sway/window": {
"swap-icon-label": true
}
}
```
## Grouping modules ## Grouping modules
Module groups allow stacking modules in any direction. By default, when the bar is positioned on the top or bottom of the screen, modules in a group are stacked vertically. Likewise, when positioned on the left or right, modules in a group are stacked horizontally. This can be changed with the "orientation" property. Module groups allow stacking modules in any direction. By default, when the bar is positioned on the top or bottom of the screen, modules in a group are stacked vertically. Likewise, when positioned on the left or right, modules in a group are stacked horizontally. This can be changed with the "orientation" property.

View File

@ -1,6 +1,6 @@
project( project(
'waybar', 'cpp', 'c', 'waybar', 'cpp', 'c',
version: '0.11.0', version: '0.12.0',
license: 'MIT', license: 'MIT',
meson_version: '>= 0.59.0', meson_version: '>= 0.59.0',
default_options : [ default_options : [
@ -342,6 +342,10 @@ if true
) )
endif endif
if get_option('login-proxy')
add_project_arguments('-DHAVE_LOGIN_PROXY', language: 'cpp')
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')
@ -491,7 +495,7 @@ if get_option('experimental')
endif endif
cava = dependency('cava', cava = dependency('cava',
version : '>=0.10.3', version : '>=0.10.4',
required: get_option('cava'), required: get_option('cava'),
fallback : ['cava', 'cava_dep'], fallback : ['cava', 'cava_dep'],
not_found_message: 'cava is not found. Building waybar without cava') not_found_message: 'cava is not found. Building waybar without cava')

View File

@ -20,3 +20,4 @@ option('jack', type: 'feature', value: 'auto', description: 'Enable support for
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') option('niri', type: 'boolean', description: 'Enable support for niri')
option('login-proxy', type: 'boolean', description: 'Enable interfacing with dbus login interface')

View File

@ -1,21 +1,21 @@
{ lib {
, pkgs lib,
, waybar pkgs,
, version waybar,
version,
}: }:
let let
libcava = rec { libcava = rec {
version = "0.10.3"; version = "0.10.4";
src = pkgs.fetchFromGitHub { src = pkgs.fetchFromGitHub {
owner = "LukashonakV"; owner = "LukashonakV";
repo = "cava"; repo = "cava";
rev = version; tag = version;
hash = "sha256-ZDFbI69ECsUTjbhlw2kHRufZbQMu+FQSMmncCJ5pagg="; hash = "sha256-9eTDqM+O1tA/3bEfd1apm8LbEcR9CVgELTIspSVPMKM=";
}; };
}; };
in in
(waybar.overrideAttrs ( (waybar.overrideAttrs (oldAttrs: {
oldAttrs: {
inherit version; inherit version;
src = lib.cleanSourceWith { src = lib.cleanSourceWith {
@ -26,7 +26,9 @@ in
mesonFlags = lib.remove "-Dgtk-layer-shell=enabled" oldAttrs.mesonFlags; mesonFlags = lib.remove "-Dgtk-layer-shell=enabled" oldAttrs.mesonFlags;
# downstream patch should not affect upstream # downstream patch should not affect upstream
patches = []; patches = [ ];
# nixpkgs checks version, no need when building locally
nativeInstallCheckInputs = [ ];
buildInputs = (builtins.filter (p: p.pname != "wireplumber") oldAttrs.buildInputs) ++ [ buildInputs = (builtins.filter (p: p.pname != "wireplumber") oldAttrs.buildInputs) ++ [
pkgs.wireplumber pkgs.wireplumber
@ -38,5 +40,4 @@ in
patchShebangs . patchShebangs .
popd popd
''; '';
} }))
))

View File

@ -104,7 +104,11 @@
}, },
"tray": { "tray": {
// "icon-size": 21, // "icon-size": 21,
"spacing": 10 "spacing": 10,
// "icons": {
// "blueman": "bluetooth",
// "TelegramDesktop": "$HOME/.local/share/icons/hicolor/16x16/apps/telegram.png"
// }
}, },
"clock": { "clock": {
// "timezone": "America/New_York", // "timezone": "America/New_York",

View File

@ -17,7 +17,7 @@ void onclicked(GtkButton* button) {
} }
// You must // You must
const size_t wbcffi_version = 1; const size_t wbcffi_version = 2;
void* wbcffi_init(const wbcffi_init_info* init_info, const wbcffi_config_entry* config_entries, void* wbcffi_init(const wbcffi_init_info* init_info, const wbcffi_config_entry* config_entries,
size_t config_entries_len) { size_t config_entries_len) {

View File

@ -7,7 +7,7 @@
extern "C" { extern "C" {
#endif #endif
/// Waybar ABI version. 1 is the latest version /// Waybar ABI version. 2 is the latest version
extern const size_t wbcffi_version; extern const size_t wbcffi_version;
/// Private Waybar CFFI module /// Private Waybar CFFI module
@ -35,7 +35,13 @@ typedef struct {
typedef struct { typedef struct {
/// Entry key /// Entry key
const char* key; const char* key;
/// Entry value as string. JSON object and arrays are serialized. /// Entry value
///
/// In ABI version 1, this may be either a bare string if the value is a
/// string, or the JSON representation of any other JSON object as a string.
///
/// From ABI version 2 onwards, this is always the JSON representation of the
/// value as a string.
const char* value; const char* value;
} wbcffi_config_entry; } wbcffi_config_entry;

View File

@ -112,6 +112,7 @@ class PlayerManager:
logger.debug(f"Metadata changed for player {player.props.player_name}") logger.debug(f"Metadata changed for player {player.props.player_name}")
player_name = player.props.player_name player_name = player.props.player_name
artist = player.get_artist() artist = player.get_artist()
artist = artist.replace("&", "&amp;")
title = player.get_title() title = player.get_title()
title = title.replace("&", "&amp;") title = title.replace("&", "&amp;")

View File

@ -1,5 +1,5 @@
[Unit] [Unit]
Description=Highly customizable Wayland bar for Sway and Wlroots based compositors. Description=Highly customizable Wayland bar for Sway and Wlroots based compositors
Documentation=https://github.com/Alexays/Waybar/wiki/ Documentation=https://github.com/Alexays/Waybar/wiki/
PartOf=graphical-session.target PartOf=graphical-session.target
After=graphical-session.target After=graphical-session.target

View File

@ -1,6 +1,7 @@
#include "AIconLabel.hpp" #include "AIconLabel.hpp"
#include <gdkmm/pixbuf.h> #include <gdkmm/pixbuf.h>
#include <spdlog/spdlog.h>
namespace waybar { namespace waybar {
@ -17,14 +18,36 @@ AIconLabel::AIconLabel(const Json::Value &config, const std::string &name, const
box_.get_style_context()->add_class(id); box_.get_style_context()->add_class(id);
} }
int rot = 0;
if (config_["rotate"].isUInt()) {
rot = config["rotate"].asUInt() % 360;
if ((rot % 90) != 00) rot = 0;
rot /= 90;
}
if ((rot % 2) == 0)
box_.set_orientation(Gtk::Orientation::ORIENTATION_HORIZONTAL); box_.set_orientation(Gtk::Orientation::ORIENTATION_HORIZONTAL);
else
box_.set_orientation(Gtk::Orientation::ORIENTATION_VERTICAL);
box_.set_name(name); box_.set_name(name);
int spacing = config_["icon-spacing"].isInt() ? config_["icon-spacing"].asInt() : 8; int spacing = config_["icon-spacing"].isInt() ? config_["icon-spacing"].asInt() : 8;
box_.set_spacing(spacing); box_.set_spacing(spacing);
bool swap_icon_label = false;
if (not config_["swap-icon-label"].isBool())
spdlog::warn("'swap-icon-label' must be a bool.");
else
swap_icon_label = config_["swap-icon-label"].asBool();
if ((rot == 0 || rot == 3) ^ swap_icon_label) {
box_.add(image_); box_.add(image_);
box_.add(label_); box_.add(label_);
} else {
box_.add(label_);
box_.add(image_);
}
event_box_.add(box_); event_box_.add(box_);
} }

View File

@ -68,11 +68,11 @@ ALabel::ALabel(const Json::Value& config, const std::string& name, const std::st
// there might be "~" or "$HOME" in original path, try to expand it. // there might be "~" or "$HOME" in original path, try to expand it.
auto result = Config::tryExpandPath(menuFile, ""); auto result = Config::tryExpandPath(menuFile, "");
if (!result.has_value()) { if (result.empty()) {
throw std::runtime_error("Failed to expand file: " + menuFile); throw std::runtime_error("Failed to expand file: " + menuFile);
} }
menuFile = result.value(); menuFile = result.front();
// Read the menu descriptor file // Read the menu descriptor file
std::ifstream file(menuFile); std::ifstream file(menuFile);
if (!file.is_open()) { if (!file.is_open()) {

View File

@ -83,7 +83,7 @@ AModule::AModule(const Json::Value& config, const std::string& name, const std::
} }
AModule::~AModule() { AModule::~AModule() {
for (const auto& pid : pid_) { for (const auto& pid : pid_children_) {
if (pid != -1) { if (pid != -1) {
killpg(pid, SIGTERM); killpg(pid, SIGTERM);
} }
@ -93,15 +93,15 @@ AModule::~AModule() {
auto AModule::update() -> void { auto AModule::update() -> void {
// Run user-provided update handler if configured // Run user-provided update handler if configured
if (config_["on-update"].isString()) { if (config_["on-update"].isString()) {
pid_.push_back(util::command::forkExec(config_["on-update"].asString())); pid_children_.push_back(util::command::forkExec(config_["on-update"].asString()));
} }
} }
// Get mapping between event name and module action name // Get mapping between event name and module action name
// Then call overrided doAction in order to call appropriate module action // Then call overridden doAction in order to call appropriate module action
auto AModule::doAction(const std::string& name) -> void { auto AModule::doAction(const std::string& name) -> void {
if (!name.empty()) { if (!name.empty()) {
const std::map<std::string, std::string>::const_iterator& recA{eventActionMap_.find(name)}; const std::map<std::string, std::string>::const_iterator& recA{eventActionMap_.find(name)};
// Call overrided action if derrived class has implemented it // Call overridden action if derived class has implemented it
if (recA != eventActionMap_.cend() && name != recA->second) this->doAction(recA->second); if (recA != eventActionMap_.cend() && name != recA->second) this->doAction(recA->second);
} }
} }
@ -182,7 +182,7 @@ bool AModule::handleUserEvent(GdkEventButton* const& e) {
format.clear(); format.clear();
} }
if (!format.empty()) { if (!format.empty()) {
pid_.push_back(util::command::forkExec(format)); pid_children_.push_back(util::command::forkExec(format));
} }
dp.emit(); dp.emit();
return true; return true;
@ -267,7 +267,7 @@ bool AModule::handleScroll(GdkEventScroll* e) {
this->AModule::doAction(eventName); this->AModule::doAction(eventName);
// Second call user scripts // Second call user scripts
if (config_[eventName].isString()) if (config_[eventName].isString())
pid_.push_back(util::command::forkExec(config_[eventName].asString())); pid_children_.push_back(util::command::forkExec(config_[eventName].asString()));
dp.emit(); dp.emit();
return true; return true;

View File

@ -545,7 +545,6 @@ auto waybar::Bar::setupWidgets() -> void {
if (config["fixed-center"].isBool() ? config["fixed-center"].asBool() : true) { if (config["fixed-center"].isBool() ? config["fixed-center"].asBool() : true) {
box_.set_center_widget(center_); box_.set_center_widget(center_);
} else { } else {
spdlog::error("No fixed center_");
box_.pack_start(center_, true, expand_center); box_.pack_start(center_, true, expand_center);
} }
} }
@ -569,13 +568,13 @@ auto waybar::Bar::setupWidgets() -> void {
if (!no_center) { if (!no_center) {
for (auto const& module : modules_center_) { for (auto const& module : modules_center_) {
center_.pack_start(*module, false, false); center_.pack_start(*module, module->expandEnabled(), module->expandEnabled());
} }
} }
std::reverse(modules_right_.begin(), modules_right_.end()); std::reverse(modules_right_.begin(), modules_right_.end());
for (auto const& module : modules_right_) { for (auto const& module : modules_right_) {
right_.pack_end(*module, false, false); right_.pack_end(*module, module->expandEnabled(), module->expandEnabled());
} }
} }

View File

@ -86,7 +86,7 @@ void waybar::Client::handleOutputDone(void *data, struct zxdg_output_v1 * /*xdg_
} }
} }
} catch (const std::exception &e) { } catch (const std::exception &e) {
std::cerr << e.what() << '\n'; spdlog::warn("caught exception in zxdg_output_v1_listener::done: {}", e.what());
} }
} }
@ -97,7 +97,7 @@ void waybar::Client::handleOutputName(void *data, struct zxdg_output_v1 * /*xdg_
auto &output = client->getOutput(data); auto &output = client->getOutput(data);
output.name = name; output.name = name;
} catch (const std::exception &e) { } catch (const std::exception &e) {
std::cerr << e.what() << '\n'; spdlog::warn("caught exception in zxdg_output_v1_listener::name: {}", e.what());
} }
} }
@ -106,13 +106,13 @@ void waybar::Client::handleOutputDescription(void *data, struct zxdg_output_v1 *
auto *client = waybar::Client::inst(); auto *client = waybar::Client::inst();
try { try {
auto &output = client->getOutput(data); auto &output = client->getOutput(data);
const char *open_paren = strrchr(description, '(');
// Description format: "identifier (name)" // Description format: "identifier (name)"
size_t identifier_length = open_paren - description; auto s = std::string(description);
output.identifier = std::string(description, identifier_length - 1); auto pos = s.find(" (");
output.identifier = pos != std::string::npos ? s.substr(0, pos) : s;
} catch (const std::exception &e) { } catch (const std::exception &e) {
std::cerr << e.what() << '\n'; spdlog::warn("caught exception in zxdg_output_v1_listener::description: {}", e.what());
} }
} }

View File

@ -21,7 +21,7 @@ const std::vector<std::string> Config::CONFIG_DIRS = {
const char *Config::CONFIG_PATH_ENV = "WAYBAR_CONFIG_DIR"; const char *Config::CONFIG_PATH_ENV = "WAYBAR_CONFIG_DIR";
std::optional<std::string> Config::tryExpandPath(const std::string &base, std::vector<std::string> Config::tryExpandPath(const std::string &base,
const std::string &filename) { const std::string &filename) {
fs::path path; fs::path path;
@ -33,33 +33,35 @@ std::optional<std::string> Config::tryExpandPath(const std::string &base,
spdlog::debug("Try expanding: {}", path.string()); spdlog::debug("Try expanding: {}", path.string());
std::vector<std::string> results;
wordexp_t p; wordexp_t p;
if (wordexp(path.c_str(), &p, 0) == 0) { if (wordexp(path.c_str(), &p, 0) == 0) {
if (access(*p.we_wordv, F_OK) == 0) { for (size_t i = 0; i < p.we_wordc; i++) {
std::string result = *p.we_wordv; if (access(p.we_wordv[i], F_OK) == 0) {
wordfree(&p); results.emplace_back(p.we_wordv[i]);
spdlog::debug("Found config file: {}", path.string()); spdlog::debug("Found config file: {}", p.we_wordv[i]);
return result; }
} }
wordfree(&p); wordfree(&p);
} }
return std::nullopt;
return results;
} }
std::optional<std::string> Config::findConfigPath(const std::vector<std::string> &names, std::optional<std::string> Config::findConfigPath(const std::vector<std::string> &names,
const std::vector<std::string> &dirs) { const std::vector<std::string> &dirs) {
if (const char *dir = std::getenv(Config::CONFIG_PATH_ENV)) { if (const char *dir = std::getenv(Config::CONFIG_PATH_ENV)) {
for (const auto &name : names) { for (const auto &name : names) {
if (auto res = tryExpandPath(dir, name); res) { if (auto res = tryExpandPath(dir, name); !res.empty()) {
return res; return res.front();
} }
} }
} }
for (const auto &dir : dirs) { for (const auto &dir : dirs) {
for (const auto &name : names) { for (const auto &name : names) {
if (auto res = tryExpandPath(dir, name); res) { if (auto res = tryExpandPath(dir, name); !res.empty()) {
return res; return res.front();
} }
} }
} }
@ -92,11 +94,15 @@ void Config::resolveConfigIncludes(Json::Value &config, int depth) {
if (includes.isArray()) { if (includes.isArray()) {
for (const auto &include : includes) { for (const auto &include : includes) {
spdlog::info("Including resource file: {}", include.asString()); spdlog::info("Including resource file: {}", include.asString());
setupConfig(config, tryExpandPath(include.asString(), "").value_or(""), ++depth); for (const auto &match : tryExpandPath(include.asString(), "")) {
setupConfig(config, match, depth + 1);
}
} }
} else if (includes.isString()) { } else if (includes.isString()) {
spdlog::info("Including resource file: {}", includes.asString()); spdlog::info("Including resource file: {}", includes.asString());
setupConfig(config, tryExpandPath(includes.asString(), "").value_or(""), ++depth); for (const auto &match : tryExpandPath(includes.asString(), "")) {
setupConfig(config, match, depth + 1);
}
} }
} }

View File

@ -144,7 +144,7 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name,
#endif #endif
#ifdef HAVE_PIPEWIRE #ifdef HAVE_PIPEWIRE
if (ref == "privacy") { if (ref == "privacy") {
return new waybar::modules::privacy::Privacy(id, config_[name], pos); return new waybar::modules::privacy::Privacy(id, config_[name], bar_.orientation, pos);
} }
#endif #endif
#ifdef HAVE_MPRIS #ifdef HAVE_MPRIS

View File

@ -1,3 +1,4 @@
#include <fcntl.h>
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/wait.h> #include <sys/wait.h>
@ -7,26 +8,99 @@
#include <mutex> #include <mutex>
#include "client.hpp" #include "client.hpp"
#include "util/SafeSignal.hpp"
std::mutex reap_mtx; std::mutex reap_mtx;
std::list<pid_t> reap; std::list<pid_t> reap;
volatile bool reload;
void* signalThread(void* args) { static int signal_pipe_write_fd;
int err;
int signum; // Write a single signal to `signal_pipe_write_fd`.
sigset_t mask; // This function is set as a signal handler, so it must be async-signal-safe.
sigemptyset(&mask); static void writeSignalToPipe(int signum) {
sigaddset(&mask, SIGCHLD); ssize_t amt = write(signal_pipe_write_fd, &signum, sizeof(int));
// There's not much we can safely do inside of a signal handler.
// Let's just ignore any errors.
(void)amt;
}
// This initializes `signal_pipe_write_fd`, and sets up signal handlers.
//
// This function will run forever, emitting every `SIGUSR1`, `SIGUSR2`,
// `SIGINT`, `SIGCHLD`, and `SIGRTMIN + 1`...`SIGRTMAX` signal received
// to `signal_handler`.
static void catchSignals(waybar::SafeSignal<int>& signal_handler) {
int fd[2];
pipe(fd);
int signal_pipe_read_fd = fd[0];
signal_pipe_write_fd = fd[1];
// This pipe should be able to buffer ~thousands of signals. If it fills up,
// we'll drop signals instead of blocking.
// We can't allow the write end to block because we'll be writing to it in a
// signal handler, which could interrupt the loop that's reading from it and
// deadlock.
fcntl(signal_pipe_write_fd, F_SETFL, O_NONBLOCK);
std::signal(SIGUSR1, writeSignalToPipe);
std::signal(SIGUSR2, writeSignalToPipe);
std::signal(SIGINT, writeSignalToPipe);
std::signal(SIGCHLD, writeSignalToPipe);
for (int sig = SIGRTMIN + 1; sig <= SIGRTMAX; ++sig) {
std::signal(sig, writeSignalToPipe);
}
while (true) { while (true) {
err = sigwait(&mask, &signum); int signum;
if (err != 0) { ssize_t amt = read(signal_pipe_read_fd, &signum, sizeof(int));
spdlog::error("sigwait failed: {}", strerror(errno)); if (amt < 0) {
spdlog::error("read from signal pipe failed with error {}, closing thread", strerror(errno));
break;
}
if (amt != sizeof(int)) {
continue; continue;
} }
signal_handler.emit(signum);
}
}
// Must be called on the main thread.
//
// If this signal should restart or close the bar, this function will write
// `true` or `false`, respectively, into `reload`.
static void handleSignalMainThread(int signum, bool& reload) {
if (signum >= SIGRTMIN + 1 && signum <= SIGRTMAX) {
for (auto& bar : waybar::Client::inst()->bars) {
bar->handleSignal(signum);
}
return;
}
switch (signum) { switch (signum) {
case SIGUSR1:
spdlog::debug("Visibility toggled");
for (auto& bar : waybar::Client::inst()->bars) {
bar->toggle();
}
break;
case SIGUSR2:
spdlog::info("Reloading...");
reload = true;
waybar::Client::inst()->reset();
break;
case SIGINT:
spdlog::info("Quitting.");
reload = false;
waybar::Client::inst()->reset();
break;
case SIGCHLD: case SIGCHLD:
spdlog::debug("Received SIGCHLD in signalThread"); spdlog::debug("Received SIGCHLD in signalThread");
if (!reap.empty()) { if (!reap.empty()) {
@ -44,62 +118,22 @@ void* signalThread(void* args) {
spdlog::debug("Received signal with number {}, but not handling", signum); spdlog::debug("Received signal with number {}, but not handling", signum);
break; break;
} }
}
}
void startSignalThread() {
int err;
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGCHLD);
// Block SIGCHLD so it can be handled by the signal thread
// Any threads created by this one (the main thread) should not
// modify their signal mask to unblock SIGCHLD
err = pthread_sigmask(SIG_BLOCK, &mask, nullptr);
if (err != 0) {
spdlog::error("pthread_sigmask failed in startSignalThread: {}", strerror(err));
exit(1);
}
pthread_t thread_id;
err = pthread_create(&thread_id, nullptr, signalThread, nullptr);
if (err != 0) {
spdlog::error("pthread_create failed in startSignalThread: {}", strerror(err));
exit(1);
}
} }
int main(int argc, char* argv[]) { int main(int argc, char* argv[]) {
try { try {
auto* client = waybar::Client::inst(); auto* client = waybar::Client::inst();
std::signal(SIGUSR1, [](int /*signal*/) { bool reload;
for (auto& bar : waybar::Client::inst()->bars) {
bar->toggle();
}
});
std::signal(SIGUSR2, [](int /*signal*/) { waybar::SafeSignal<int> posix_signal_received;
spdlog::info("Reloading..."); posix_signal_received.connect([&](int signum) { handleSignalMainThread(signum, reload); });
reload = true;
waybar::Client::inst()->reset();
});
std::signal(SIGINT, [](int /*signal*/) { std::thread signal_thread([&]() { catchSignals(posix_signal_received); });
spdlog::info("Quitting.");
reload = false;
waybar::Client::inst()->reset();
});
for (int sig = SIGRTMIN + 1; sig <= SIGRTMAX; ++sig) { // Every `std::thread` must be joined or detached.
std::signal(sig, [](int sig) { // This thread should run forever, so detach it.
for (auto& bar : waybar::Client::inst()->bars) { signal_thread.detach();
bar->handleSignal(sig);
}
});
}
startSignalThread();
auto ret = 0; auto ret = 0;
do { do {

View File

@ -273,14 +273,18 @@ waybar::modules::Battery::getInfos() {
// Scale these by the voltage to get μW/μWh. // Scale these by the voltage to get μW/μWh.
uint32_t current_now = 0; uint32_t current_now = 0;
int32_t _current_now_int = 0;
bool current_now_exists = false; bool current_now_exists = false;
if (fs::exists(bat / "current_now")) { if (fs::exists(bat / "current_now")) {
current_now_exists = true; current_now_exists = true;
std::ifstream(bat / "current_now") >> current_now; std::ifstream(bat / "current_now") >> _current_now_int;
} else if (fs::exists(bat / "current_avg")) { } else if (fs::exists(bat / "current_avg")) {
current_now_exists = true; current_now_exists = true;
std::ifstream(bat / "current_avg") >> current_now; std::ifstream(bat / "current_avg") >> _current_now_int;
} }
// Documentation ABI allows a negative value when discharging, positive
// value when charging.
current_now = std::abs(_current_now_int);
if (fs::exists(bat / "time_to_empty_now")) { if (fs::exists(bat / "time_to_empty_now")) {
time_to_empty_now_exists = true; time_to_empty_now_exists = true;
@ -324,11 +328,15 @@ waybar::modules::Battery::getInfos() {
} }
uint32_t power_now = 0; uint32_t power_now = 0;
int32_t _power_now_int = 0;
bool power_now_exists = false; bool power_now_exists = false;
if (fs::exists(bat / "power_now")) { if (fs::exists(bat / "power_now")) {
power_now_exists = true; power_now_exists = true;
std::ifstream(bat / "power_now") >> power_now; std::ifstream(bat / "power_now") >> _power_now_int;
} }
// Some drivers (example: Qualcomm) exposes use a negative value when
// discharging, positive value when charging.
power_now = std::abs(_power_now_int);
uint32_t energy_now = 0; uint32_t energy_now = 0;
bool energy_now_exists = false; bool energy_now_exists = false;

View File

@ -49,8 +49,8 @@ auto getBoolProperty(GDBusProxy* proxy, const char* property_name) -> bool {
return false; return false;
} }
auto getOptionalStringProperty(GDBusProxy* proxy, auto getOptionalStringProperty(GDBusProxy* proxy, const char* property_name)
const char* property_name) -> std::optional<std::string> { -> 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, GVariant* changed_properties, const gchar* const* invalidated_properties, gpointer user_data)
gpointer user_data) -> void { -> 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, auto waybar::modules::Bluetooth::getDeviceProperties(GDBusObject* object, DeviceInfo& device_info)
DeviceInfo& device_info) -> bool { -> 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,8 +462,9 @@ auto waybar::modules::Bluetooth::findCurController() -> std::optional<Controller
return controller_info; return controller_info;
} }
auto waybar::modules::Bluetooth::findConnectedDevices( auto waybar::modules::Bluetooth::findConnectedDevices(const std::string& cur_controller_path,
const std::string& cur_controller_path, std::vector<DeviceInfo>& connected_devices) -> void { std::vector<DeviceInfo>& connected_devices)
-> 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

@ -139,7 +139,7 @@ auto waybar::modules::Cava::update() -> void {
} }
} }
if (silence_ && prm_.sleep_timer) { if (silence_ && prm_.sleep_timer != 0) {
if (sleep_counter_ <= if (sleep_counter_ <=
(int)(std::chrono::milliseconds(prm_.sleep_timer * 1s) / frame_time_milsec_)) { (int)(std::chrono::milliseconds(prm_.sleep_timer * 1s) / frame_time_milsec_)) {
++sleep_counter_; ++sleep_counter_;
@ -147,7 +147,7 @@ auto waybar::modules::Cava::update() -> void {
} }
} }
if (!silence_) { if (!silence_ || prm_.sleep_timer == 0) {
downThreadDelay(frame_time_milsec_, suspend_silence_delay_); downThreadDelay(frame_time_milsec_, suspend_silence_delay_);
// Process: execute cava // Process: execute cava
pthread_mutex_lock(&audio_data_.lock); pthread_mutex_lock(&audio_data_.lock);

View File

@ -28,7 +28,7 @@ CFFI::CFFI(const std::string& name, const std::string& id, const Json::Value& co
} }
// Fetch functions // Fetch functions
if (*wbcffi_version == 1) { if (*wbcffi_version == 1 || *wbcffi_version == 2) {
// Mandatory functions // Mandatory functions
hooks_.init = reinterpret_cast<InitFn*>(dlsym(handle, "wbcffi_init")); hooks_.init = reinterpret_cast<InitFn*>(dlsym(handle, "wbcffi_init"));
if (!hooks_.init) { if (!hooks_.init) {
@ -58,10 +58,14 @@ CFFI::CFFI(const std::string& name, const std::string& id, const Json::Value& co
const auto& keys = config.getMemberNames(); const auto& keys = config.getMemberNames();
for (size_t i = 0; i < keys.size(); i++) { for (size_t i = 0; i < keys.size(); i++) {
const auto& value = config[keys[i]]; const auto& value = config[keys[i]];
if (*wbcffi_version == 1) {
if (value.isConvertibleTo(Json::ValueType::stringValue)) { if (value.isConvertibleTo(Json::ValueType::stringValue)) {
config_entries_stringstor.push_back(config[keys[i]].asString()); config_entries_stringstor.push_back(value.asString());
} else { } else {
config_entries_stringstor.push_back(config[keys[i]].toStyledString()); config_entries_stringstor.push_back(value.toStyledString());
}
} else {
config_entries_stringstor.push_back(value.toStyledString());
} }
} }

View File

@ -1,5 +1,6 @@
#include "modules/clock.hpp" #include "modules/clock.hpp"
#include <glib.h>
#include <gtkmm/tooltip.h> #include <gtkmm/tooltip.h>
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
@ -16,6 +17,7 @@
#include <clocale> #include <clocale>
#endif #endif
using namespace date;
namespace fmt_lib = waybar::util::date::format; namespace fmt_lib = waybar::util::date::format;
waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config) waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
@ -25,6 +27,7 @@ waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
m_tooltip_{new Gtk::Label()}, m_tooltip_{new Gtk::Label()},
cldInTooltip_{m_tlpFmt_.find("{" + kCldPlaceholder + "}") != std::string::npos}, cldInTooltip_{m_tlpFmt_.find("{" + kCldPlaceholder + "}") != std::string::npos},
cldYearShift_{January / 1 / 1900}, cldYearShift_{January / 1 / 1900},
cldMonShift_{year(1900) / January},
tzInTooltip_{m_tlpFmt_.find("{" + kTZPlaceholder + "}") != std::string::npos}, tzInTooltip_{m_tlpFmt_.find("{" + kTZPlaceholder + "}") != std::string::npos},
tzCurrIdx_{0}, tzCurrIdx_{0},
ordInTooltip_{m_tlpFmt_.find("{" + kOrdPlaceholder + "}") != std::string::npos} { ordInTooltip_{m_tlpFmt_.find("{" + kOrdPlaceholder + "}") != std::string::npos} {
@ -199,8 +202,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, auto cldGetWeekForLine(const year_month& ym, const weekday& firstdow, const unsigned line)
const unsigned line) -> const year_month_weekday { -> 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];
@ -348,9 +351,9 @@ auto waybar::modules::Clock::get_calendar(const year_month_day& today, const yea
m_locale_, fmtMap_[4], m_locale_, fmtMap_[4],
fmt_lib::make_format_args( fmt_lib::make_format_args(
(line == 2) (line == 2)
? static_cast<const date::zoned_seconds&&>( ? static_cast<const zoned_seconds&&>(
zoned_seconds{tz, local_days{ymTmp / 1}}) zoned_seconds{tz, local_days{ymTmp / 1}})
: static_cast<const date::zoned_seconds&&>(zoned_seconds{ : static_cast<const zoned_seconds&&>(zoned_seconds{
tz, local_days{cldGetWeekForLine(ymTmp, firstdow, line)}}))) tz, local_days{cldGetWeekForLine(ymTmp, firstdow, line)}})))
<< ' '; << ' ';
} else } else
@ -358,10 +361,23 @@ auto waybar::modules::Clock::get_calendar(const year_month_day& today, const yea
} }
} }
os << Glib::ustring::format((cldWPos_ != WS::LEFT || line == 0) ? std::left : std::right, // Count wide characters to avoid extra padding
std::setfill(L' '), size_t wideCharCount = 0;
std::setw(cldMonColLen_ + ((line < 2) ? cldWnLen_ : 0)), std::string calendarLine = getCalendarLine(today, ymTmp, line, firstdow, &m_locale_);
getCalendarLine(today, ymTmp, line, firstdow, &m_locale_)); if (line < 2) {
for (gchar *data = calendarLine.data(), *end = data + calendarLine.size();
data != nullptr;) {
gunichar c = g_utf8_get_char_validated(data, end - data);
if (g_unichar_iswide(c)) {
wideCharCount++;
}
data = g_utf8_find_next_char(data, end);
}
}
os << Glib::ustring::format(
(cldWPos_ != WS::LEFT || line == 0) ? std::left : std::right, std::setfill(L' '),
std::setw(cldMonColLen_ + ((line < 2) ? cldWnLen_ - wideCharCount : 0)),
calendarLine);
// Week numbers on the right // Week numbers on the right
if (cldWPos_ == WS::RIGHT && line > 0) { if (cldWPos_ == WS::RIGHT && line > 0) {
@ -371,9 +387,9 @@ auto waybar::modules::Clock::get_calendar(const year_month_day& today, const yea
<< fmt_lib::vformat( << fmt_lib::vformat(
m_locale_, fmtMap_[4], m_locale_, fmtMap_[4],
fmt_lib::make_format_args( fmt_lib::make_format_args(
(line == 2) ? static_cast<const date::zoned_seconds&&>( (line == 2) ? static_cast<const zoned_seconds&&>(
zoned_seconds{tz, local_days{ymTmp / 1}}) zoned_seconds{tz, local_days{ymTmp / 1}})
: static_cast<const date::zoned_seconds&&>( : static_cast<const zoned_seconds&&>(
zoned_seconds{tz, local_days{cldGetWeekForLine( zoned_seconds{tz, local_days{cldGetWeekForLine(
ymTmp, firstdow, line)}}))); ymTmp, firstdow, line)}})));
else else

View File

@ -35,6 +35,13 @@ waybar::modules::Custom::~Custom() {
void waybar::modules::Custom::delayWorker() { void waybar::modules::Custom::delayWorker() {
thread_ = [this] { thread_ = [this] {
for (int i: this->pid_children_) {
int status;
waitpid(i, &status, 0);
}
this->pid_children_.clear();
bool can_update = true; bool can_update = true;
if (config_["exec-if"].isString()) { if (config_["exec-if"].isString()) {
output_ = util::command::execNoRead(config_["exec-if"].asString()); output_ = util::command::execNoRead(config_["exec-if"].asString());
@ -62,7 +69,7 @@ void waybar::modules::Custom::continuousWorker() {
} }
thread_ = [this, cmd] { thread_ = [this, cmd] {
char* buff = nullptr; char* buff = nullptr;
waybar::util::ScopeGuard buff_deleter([buff]() { waybar::util::ScopeGuard buff_deleter([&buff]() {
if (buff) { if (buff) {
free(buff); free(buff);
} }

View File

@ -98,13 +98,9 @@ Window::~Window() {
} }
} }
void Window::handle_title(const char *title) { void Window::handle_title(const char *title) { title_ = Glib::Markup::escape_text(title); }
title_ = Glib::Markup::escape_text(title);
}
void Window::handle_appid(const char *appid) { void Window::handle_appid(const char *appid) { appid_ = Glib::Markup::escape_text(appid); }
appid_ = Glib::Markup::escape_text(appid);
}
void Window::handle_layout_symbol(const char *layout_symbol) { void Window::handle_layout_symbol(const char *layout_symbol) {
layout_symbol_ = Glib::Markup::escape_text(layout_symbol); layout_symbol_ = Glib::Markup::escape_text(layout_symbol);

View File

@ -11,7 +11,6 @@
#include <filesystem> #include <filesystem>
#include <string> #include <string>
#include <thread>
namespace waybar::modules::hyprland { namespace waybar::modules::hyprland {
@ -44,10 +43,33 @@ std::filesystem::path IPC::getSocketFolder(const char* instanceSig) {
return socketFolder_; return socketFolder_;
} }
void IPC::startIPC() { IPC::IPC() {
// will start IPC and relay events to parseIPC // will start IPC and relay events to parseIPC
ipcThread_ = std::thread([this]() { socketListener(); });
}
std::thread([&]() { IPC::~IPC() {
running_ = false;
spdlog::info("Hyprland IPC stopping...");
if (socketfd_ != -1) {
spdlog::trace("Shutting down socket");
if (shutdown(socketfd_, SHUT_RDWR) == -1) {
spdlog::error("Hyprland IPC: Couldn't shutdown socket");
}
spdlog::trace("Closing socket");
if (close(socketfd_) == -1) {
spdlog::error("Hyprland IPC: Couldn't close socket");
}
}
ipcThread_.join();
}
IPC& IPC::inst() {
static IPC ipc;
return ipc;
}
void IPC::socketListener() {
// check for hyprland // check for hyprland
const char* his = getenv("HYPRLAND_INSTANCE_SIGNATURE"); const char* his = getenv("HYPRLAND_INSTANCE_SIGNATURE");
@ -61,9 +83,9 @@ void IPC::startIPC() {
spdlog::info("Hyprland IPC starting"); spdlog::info("Hyprland IPC starting");
struct sockaddr_un addr; struct sockaddr_un addr;
int socketfd = socket(AF_UNIX, SOCK_STREAM, 0); socketfd_ = socket(AF_UNIX, SOCK_STREAM, 0);
if (socketfd == -1) { if (socketfd_ == -1) {
spdlog::error("Hyprland IPC: socketfd failed"); spdlog::error("Hyprland IPC: socketfd failed");
return; return;
} }
@ -77,14 +99,16 @@ void IPC::startIPC() {
int l = sizeof(struct sockaddr_un); int l = sizeof(struct sockaddr_un);
if (connect(socketfd, (struct sockaddr*)&addr, l) == -1) { if (connect(socketfd_, (struct sockaddr*)&addr, l) == -1) {
spdlog::error("Hyprland IPC: Unable to connect?"); spdlog::error("Hyprland IPC: Unable to connect?");
return; return;
} }
auto* file = fdopen(socketfd_, "r");
auto* file = fdopen(socketfd, "r"); if (file == nullptr) {
spdlog::error("Hyprland IPC: Couldn't open file descriptor");
while (true) { return;
}
while (running_) {
std::array<char, 1024> buffer; // Hyprland socket2 events are max 1024 bytes std::array<char, 1024> buffer; // Hyprland socket2 events are max 1024 bytes
auto* receivedCharPtr = fgets(buffer.data(), buffer.size(), file); auto* receivedCharPtr = fgets(buffer.data(), buffer.size(), file);
@ -108,7 +132,7 @@ void IPC::startIPC() {
std::this_thread::sleep_for(std::chrono::milliseconds(1)); std::this_thread::sleep_for(std::chrono::milliseconds(1));
} }
}).detach(); spdlog::debug("Hyprland IPC stopped");
} }
void IPC::parseIPC(const std::string& ev) { void IPC::parseIPC(const std::string& ev) {

View File

@ -10,13 +10,9 @@
namespace waybar::modules::hyprland { namespace waybar::modules::hyprland {
Language::Language(const std::string& id, const Bar& bar, const Json::Value& config) Language::Language(const std::string& id, const Bar& bar, const Json::Value& config)
: ALabel(config, "language", id, "{}", 0, true), bar_(bar) { : ALabel(config, "language", id, "{}", 0, true), bar_(bar), m_ipc(IPC::inst()) {
modulesReady = true; modulesReady = true;
if (!gIPC) {
gIPC = std::make_unique<IPC>();
}
// get the active layout when open // get the active layout when open
initLanguage(); initLanguage();
@ -24,11 +20,11 @@ Language::Language(const std::string& id, const Bar& bar, const Json::Value& con
update(); update();
// register for hyprland ipc // register for hyprland ipc
gIPC->registerForIPC("activelayout", this); m_ipc.registerForIPC("activelayout", this);
} }
Language::~Language() { Language::~Language() {
gIPC->unregisterForIPC(this); m_ipc.unregisterForIPC(this);
// wait for possible event handler to finish // wait for possible event handler to finish
std::lock_guard<std::mutex> lg(mutex_); std::lock_guard<std::mutex> lg(mutex_);
} }
@ -85,7 +81,7 @@ void Language::onEvent(const std::string& ev) {
} }
void Language::initLanguage() { void Language::initLanguage() {
const auto inputDevices = gIPC->getSocket1Reply("devices"); const auto inputDevices = m_ipc.getSocket1Reply("devices");
const auto kbName = config_["keyboard-name"].asString(); const auto kbName = config_["keyboard-name"].asString();

View File

@ -7,32 +7,28 @@
namespace waybar::modules::hyprland { namespace waybar::modules::hyprland {
Submap::Submap(const std::string& id, const Bar& bar, const Json::Value& config) Submap::Submap(const std::string& id, const Bar& bar, const Json::Value& config)
: ALabel(config, "submap", id, "{}", 0, true), bar_(bar) { : ALabel(config, "submap", id, "{}", 0, true), bar_(bar), m_ipc(IPC::inst()) {
modulesReady = true; modulesReady = true;
parseConfig(config); parseConfig(config);
if (!gIPC) {
gIPC = std::make_unique<IPC>();
}
label_.hide(); label_.hide();
ALabel::update(); ALabel::update();
// Displays widget immediately if always_on_ assuming default submap // Displays widget immediately if always_on_ assuming default submap
// Needs an actual way to retrive current submap on startup // Needs an actual way to retrieve current submap on startup
if (always_on_) { if (always_on_) {
submap_ = default_submap_; submap_ = default_submap_;
label_.get_style_context()->add_class(submap_); label_.get_style_context()->add_class(submap_);
} }
// register for hyprland ipc // register for hyprland ipc
gIPC->registerForIPC("submap", this); m_ipc.registerForIPC("submap", this);
dp.emit(); dp.emit();
} }
Submap::~Submap() { Submap::~Submap() {
gIPC->unregisterForIPC(this); m_ipc.unregisterForIPC(this);
// wait for possible event handler to finish // wait for possible event handler to finish
std::lock_guard<std::mutex> lg(mutex_); std::lock_guard<std::mutex> lg(mutex_);
} }
@ -72,8 +68,7 @@ void Submap::onEvent(const std::string& ev) {
return; return;
} }
auto submapName = ev.substr(ev.find_last_of('>') + 1); auto submapName = ev.substr(ev.find_first_of('>') + 2);
submapName = waybar::util::sanitize_string(submapName);
if (!submap_.empty()) { if (!submap_.empty()) {
label_.get_style_context()->remove_class(submap_); label_.get_style_context()->remove_class(submap_);

View File

@ -6,37 +6,30 @@
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
#include <algorithm> #include <algorithm>
#include <shared_mutex>
#include <vector> #include <vector>
#include "modules/hyprland/backend.hpp" #include "modules/hyprland/backend.hpp"
#include "util/rewrite_string.hpp" #include "util/rewrite_string.hpp"
#include "util/sanitize_str.hpp" #include "util/sanitize_str.hpp"
#include <shared_mutex>
#include <thread>
namespace waybar::modules::hyprland { namespace waybar::modules::hyprland {
std::shared_mutex windowIpcSmtx; std::shared_mutex windowIpcSmtx;
Window::Window(const std::string& id, const Bar& bar, const Json::Value& config) Window::Window(const std::string& id, const Bar& bar, const Json::Value& config)
: AAppIconLabel(config, "window", id, "{title}", 0, true), bar_(bar) { : AAppIconLabel(config, "window", id, "{title}", 0, true), bar_(bar), m_ipc(IPC::inst()) {
std::unique_lock<std::shared_mutex> windowIpcUniqueLock(windowIpcSmtx); std::unique_lock<std::shared_mutex> windowIpcUniqueLock(windowIpcSmtx);
modulesReady = true; modulesReady = true;
separateOutputs_ = config["separate-outputs"].asBool(); separateOutputs_ = config["separate-outputs"].asBool();
if (!gIPC) {
gIPC = std::make_unique<IPC>();
}
// register for hyprland ipc // register for hyprland ipc
gIPC->registerForIPC("activewindow", this); m_ipc.registerForIPC("activewindow", this);
gIPC->registerForIPC("closewindow", this); m_ipc.registerForIPC("closewindow", this);
gIPC->registerForIPC("movewindow", this); m_ipc.registerForIPC("movewindow", this);
gIPC->registerForIPC("changefloatingmode", this); m_ipc.registerForIPC("changefloatingmode", this);
gIPC->registerForIPC("fullscreen", this); m_ipc.registerForIPC("fullscreen", this);
windowIpcUniqueLock.unlock(); windowIpcUniqueLock.unlock();
@ -47,11 +40,10 @@ Window::Window(const std::string& id, const Bar& bar, const Json::Value& config)
Window::~Window() { Window::~Window() {
std::unique_lock<std::shared_mutex> windowIpcUniqueLock(windowIpcSmtx); std::unique_lock<std::shared_mutex> windowIpcUniqueLock(windowIpcSmtx);
gIPC->unregisterForIPC(this); m_ipc.unregisterForIPC(this);
} }
auto Window::update() -> void { auto Window::update() -> void {
std::shared_lock<std::shared_mutex> windowIpcShareLock(windowIpcSmtx); std::shared_lock<std::shared_mutex> windowIpcShareLock(windowIpcSmtx);
std::string windowName = waybar::util::sanitize_string(workspace_.last_window_title); std::string windowName = waybar::util::sanitize_string(workspace_.last_window_title);
@ -59,18 +51,36 @@ auto Window::update() -> void {
windowData_.title = windowName; windowData_.title = windowName;
std::string label_text;
if (!format_.empty()) { if (!format_.empty()) {
label_.show(); label_.show();
label_.set_markup(waybar::util::rewriteString( label_text = waybar::util::rewriteString(
fmt::format(fmt::runtime(format_), fmt::arg("title", windowName), fmt::format(fmt::runtime(format_), fmt::arg("title", windowName),
fmt::arg("initialTitle", windowData_.initial_title), fmt::arg("initialTitle", windowData_.initial_title),
fmt::arg("class", windowData_.class_name), fmt::arg("class", windowData_.class_name),
fmt::arg("initialClass", windowData_.initial_class_name)), fmt::arg("initialClass", windowData_.initial_class_name)),
config_["rewrite"])); config_["rewrite"]);
label_.set_markup(label_text);
} else { } else {
label_.hide(); label_.hide();
} }
if (tooltipEnabled()) {
std::string tooltip_format;
if (config_["tooltip-format"].isString()) {
tooltip_format = config_["tooltip-format"].asString();
}
if (!tooltip_format.empty()) {
label_.set_tooltip_text(
fmt::format(fmt::runtime(tooltip_format), fmt::arg("title", windowName),
fmt::arg("initialTitle", windowData_.initial_title),
fmt::arg("class", windowData_.class_name),
fmt::arg("initialClass", windowData_.initial_class_name)));
} else if (!label_text.empty()) {
label_.set_tooltip_text(label_text);
}
}
if (focused_) { if (focused_) {
image_.show(); image_.show();
} else { } else {
@ -100,7 +110,7 @@ auto Window::update() -> void {
} }
auto Window::getActiveWorkspace() -> Workspace { auto Window::getActiveWorkspace() -> Workspace {
const auto workspace = gIPC->getSocket1JsonReply("activeworkspace"); const auto workspace = IPC::inst().getSocket1JsonReply("activeworkspace");
if (workspace.isObject()) { if (workspace.isObject()) {
return Workspace::parse(workspace); return Workspace::parse(workspace);
@ -110,24 +120,33 @@ auto Window::getActiveWorkspace() -> Workspace {
} }
auto Window::getActiveWorkspace(const std::string& monitorName) -> Workspace { auto Window::getActiveWorkspace(const std::string& monitorName) -> Workspace {
const auto monitors = gIPC->getSocket1JsonReply("monitors"); const auto monitors = IPC::inst().getSocket1JsonReply("monitors");
if (monitors.isArray()) { if (monitors.isArray()) {
auto monitor = std::find_if(monitors.begin(), monitors.end(), [&](Json::Value monitor) { auto monitor = std::ranges::find_if(
return monitor["name"] == monitorName; monitors, [&](Json::Value monitor) { return monitor["name"] == monitorName; });
});
if (monitor == std::end(monitors)) { if (monitor == std::end(monitors)) {
spdlog::warn("Monitor not found: {}", monitorName); spdlog::warn("Monitor not found: {}", monitorName);
return Workspace{-1, 0, "", ""}; return Workspace{
.id = -1,
.windows = 0,
.last_window = "",
.last_window_title = "",
};
} }
const int id = (*monitor)["activeWorkspace"]["id"].asInt(); const int id = (*monitor)["activeWorkspace"]["id"].asInt();
const auto workspaces = gIPC->getSocket1JsonReply("workspaces"); const auto workspaces = IPC::inst().getSocket1JsonReply("workspaces");
if (workspaces.isArray()) { if (workspaces.isArray()) {
auto workspace = std::find_if(workspaces.begin(), workspaces.end(), auto workspace = std::ranges::find_if(
[&](Json::Value workspace) { return workspace["id"] == id; }); workspaces, [&](Json::Value workspace) { return workspace["id"] == id; });
if (workspace == std::end(workspaces)) { if (workspace == std::end(workspaces)) {
spdlog::warn("No workspace with id {}", id); spdlog::warn("No workspace with id {}", id);
return Workspace{-1, 0, "", ""}; return Workspace{
.id = -1,
.windows = 0,
.last_window = "",
.last_window_title = "",
};
} }
return Workspace::parse(*workspace); return Workspace::parse(*workspace);
}; };
@ -138,22 +157,25 @@ auto Window::getActiveWorkspace(const std::string& monitorName) -> Workspace {
auto Window::Workspace::parse(const Json::Value& value) -> Window::Workspace { auto Window::Workspace::parse(const Json::Value& value) -> Window::Workspace {
return Workspace{ return Workspace{
value["id"].asInt(), .id = value["id"].asInt(),
value["windows"].asInt(), .windows = value["windows"].asInt(),
value["lastwindow"].asString(), .last_window = value["lastwindow"].asString(),
value["lastwindowtitle"].asString(), .last_window_title = value["lastwindowtitle"].asString(),
}; };
} }
auto Window::WindowData::parse(const Json::Value& value) -> Window::WindowData { auto Window::WindowData::parse(const Json::Value& value) -> Window::WindowData {
return WindowData{value["floating"].asBool(), value["monitor"].asInt(), return WindowData{.floating = value["floating"].asBool(),
value["class"].asString(), value["initialClass"].asString(), .monitor = value["monitor"].asInt(),
value["title"].asString(), value["initialTitle"].asString(), .class_name = value["class"].asString(),
value["fullscreen"].asBool(), !value["grouped"].empty()}; .initial_class_name = value["initialClass"].asString(),
.title = value["title"].asString(),
.initial_title = value["initialTitle"].asString(),
.fullscreen = value["fullscreen"].asBool(),
.grouped = !value["grouped"].empty()};
} }
void Window::queryActiveWorkspace() { void Window::queryActiveWorkspace() {
std::shared_lock<std::shared_mutex> windowIpcShareLock(windowIpcSmtx); std::shared_lock<std::shared_mutex> windowIpcShareLock(windowIpcSmtx);
if (separateOutputs_) { if (separateOutputs_) {
@ -164,11 +186,10 @@ void Window::queryActiveWorkspace() {
focused_ = true; focused_ = true;
if (workspace_.windows > 0) { if (workspace_.windows > 0) {
const auto clients = gIPC->getSocket1JsonReply("clients"); const auto clients = m_ipc.getSocket1JsonReply("clients");
if (clients.isArray()) { if (clients.isArray()) {
auto activeWindow = std::find_if(clients.begin(), clients.end(), [&](Json::Value window) { auto activeWindow = std::ranges::find_if(
return window["address"] == workspace_.last_window; clients, [&](Json::Value window) { return window["address"] == workspace_.last_window; });
});
if (activeWindow == std::end(clients)) { if (activeWindow == std::end(clients)) {
focused_ = false; focused_ = false;
@ -178,22 +199,19 @@ void Window::queryActiveWorkspace() {
windowData_ = WindowData::parse(*activeWindow); windowData_ = WindowData::parse(*activeWindow);
updateAppIconName(windowData_.class_name, windowData_.initial_class_name); updateAppIconName(windowData_.class_name, windowData_.initial_class_name);
std::vector<Json::Value> workspaceWindows; std::vector<Json::Value> workspaceWindows;
std::copy_if(clients.begin(), clients.end(), std::back_inserter(workspaceWindows), std::ranges::copy_if(clients, std::back_inserter(workspaceWindows), [&](Json::Value window) {
[&](Json::Value window) {
return window["workspace"]["id"] == workspace_.id && window["mapped"].asBool(); return window["workspace"]["id"] == workspace_.id && window["mapped"].asBool();
}); });
swallowing_ = swallowing_ = std::ranges::any_of(workspaceWindows, [&](Json::Value window) {
std::any_of(workspaceWindows.begin(), workspaceWindows.end(), [&](Json::Value window) {
return !window["swallowing"].isNull() && window["swallowing"].asString() != "0x0"; return !window["swallowing"].isNull() && window["swallowing"].asString() != "0x0";
}); });
std::vector<Json::Value> visibleWindows; std::vector<Json::Value> visibleWindows;
std::copy_if(workspaceWindows.begin(), workspaceWindows.end(), std::ranges::copy_if(workspaceWindows, std::back_inserter(visibleWindows),
std::back_inserter(visibleWindows),
[&](Json::Value window) { return !window["hidden"].asBool(); }); [&](Json::Value window) { return !window["hidden"].asBool(); });
solo_ = 1 == std::count_if(visibleWindows.begin(), visibleWindows.end(), solo_ = 1 == std::count_if(visibleWindows.begin(), visibleWindows.end(),
[&](Json::Value window) { return !window["floating"].asBool(); }); [&](Json::Value window) { return !window["floating"].asBool(); });
allFloating_ = std::all_of(visibleWindows.begin(), visibleWindows.end(), allFloating_ = std::ranges::all_of(
[&](Json::Value window) { return window["floating"].asBool(); }); visibleWindows, [&](Json::Value window) { return window["floating"].asBool(); });
fullscreen_ = windowData_.fullscreen; fullscreen_ = windowData_.fullscreen;
// Fullscreen windows look like they are solo // Fullscreen windows look like they are solo
@ -206,7 +224,7 @@ void Window::queryActiveWorkspace() {
} else { } else {
soloClass_ = ""; soloClass_ = "";
} }
}; }
} else { } else {
focused_ = false; focused_ = false;
windowData_ = WindowData{}; windowData_ = WindowData{};

View File

@ -88,7 +88,7 @@ bool WindowCreationPayload::isEmpty(Workspaces &workspace_manager) {
int WindowCreationPayload::incrementTimeSpentUncreated() { return m_timeSpentUncreated++; } int WindowCreationPayload::incrementTimeSpentUncreated() { return m_timeSpentUncreated++; }
void WindowCreationPayload::moveToWorksace(std::string &new_workspace_name) { void WindowCreationPayload::moveToWorkspace(std::string &new_workspace_name) {
m_workspaceName = new_workspace_name; m_workspaceName = new_workspace_name;
} }

View File

@ -18,7 +18,8 @@ Workspace::Workspace(const Json::Value &workspace_data, Workspaces &workspace_ma
m_windows(workspace_data["windows"].asInt()), m_windows(workspace_data["windows"].asInt()),
m_isActive(true), m_isActive(true),
m_isPersistentRule(workspace_data["persistent-rule"].asBool()), m_isPersistentRule(workspace_data["persistent-rule"].asBool()),
m_isPersistentConfig(workspace_data["persistent-config"].asBool()) { m_isPersistentConfig(workspace_data["persistent-config"].asBool()),
m_ipc(IPC::inst()) {
if (m_name.starts_with("name:")) { if (m_name.starts_with("name:")) {
m_name = m_name.substr(5); m_name = m_name.substr(5);
} else if (m_name.starts_with("special")) { } else if (m_name.starts_with("special")) {
@ -58,20 +59,20 @@ bool Workspace::handleClicked(GdkEventButton *bt) const {
try { try {
if (id() > 0) { // normal if (id() > 0) { // normal
if (m_workspaceManager.moveToMonitor()) { if (m_workspaceManager.moveToMonitor()) {
gIPC->getSocket1Reply("dispatch focusworkspaceoncurrentmonitor " + std::to_string(id())); m_ipc.getSocket1Reply("dispatch focusworkspaceoncurrentmonitor " + std::to_string(id()));
} else { } else {
gIPC->getSocket1Reply("dispatch workspace " + std::to_string(id())); m_ipc.getSocket1Reply("dispatch workspace " + std::to_string(id()));
} }
} else if (!isSpecial()) { // named (this includes persistent) } else if (!isSpecial()) { // named (this includes persistent)
if (m_workspaceManager.moveToMonitor()) { if (m_workspaceManager.moveToMonitor()) {
gIPC->getSocket1Reply("dispatch focusworkspaceoncurrentmonitor name:" + name()); m_ipc.getSocket1Reply("dispatch focusworkspaceoncurrentmonitor name:" + name());
} else { } else {
gIPC->getSocket1Reply("dispatch workspace name:" + name()); m_ipc.getSocket1Reply("dispatch workspace name:" + name());
} }
} else if (id() != -99) { // named special } else if (id() != -99) { // named special
gIPC->getSocket1Reply("dispatch togglespecialworkspace " + name()); m_ipc.getSocket1Reply("dispatch togglespecialworkspace " + name());
} else { // special } else { // special
gIPC->getSocket1Reply("dispatch togglespecialworkspace"); m_ipc.getSocket1Reply("dispatch togglespecialworkspace");
} }
return true; return true;
} catch (const std::exception &e) { } catch (const std::exception &e) {
@ -90,19 +91,19 @@ void Workspace::initializeWindowMap(const Json::Value &clients_data) {
} }
} }
void Workspace::insertWindow(WindowCreationPayload create_window_paylod) { void Workspace::insertWindow(WindowCreationPayload create_window_payload) {
if (!create_window_paylod.isEmpty(m_workspaceManager)) { if (!create_window_payload.isEmpty(m_workspaceManager)) {
auto repr = create_window_paylod.repr(m_workspaceManager); auto repr = create_window_payload.repr(m_workspaceManager);
if (!repr.empty()) { if (!repr.empty()) {
m_windowMap[create_window_paylod.getAddress()] = repr; m_windowMap[create_window_payload.getAddress()] = repr;
} }
} }
}; };
bool Workspace::onWindowOpened(WindowCreationPayload const &create_window_paylod) { bool Workspace::onWindowOpened(WindowCreationPayload const &create_window_payload) {
if (create_window_paylod.getWorkspaceName() == name()) { if (create_window_payload.getWorkspaceName() == name()) {
insertWindow(create_window_paylod); insertWindow(create_window_payload);
return true; return true;
} }
return false; return false;
@ -172,6 +173,10 @@ std::string &Workspace::selectIcon(std::map<std::string, std::string> &icons_map
} }
void Workspace::update(const std::string &format, const std::string &icon) { void Workspace::update(const std::string &format, const std::string &icon) {
if (this->m_workspaceManager.persistentOnly() && !this->isPersistent()) {
m_button.hide();
return;
}
// clang-format off // clang-format off
if (this->m_workspaceManager.activeOnly() && \ if (this->m_workspaceManager.activeOnly() && \
!this->isActive() && \ !this->isActive() && \

View File

@ -5,6 +5,7 @@
#include <algorithm> #include <algorithm>
#include <memory> #include <memory>
#include <sstream>
#include <string> #include <string>
#include <utility> #include <utility>
@ -13,7 +14,10 @@
namespace waybar::modules::hyprland { namespace waybar::modules::hyprland {
Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value &config) Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value &config)
: AModule(config, "workspaces", id, false, false), m_bar(bar), m_box(bar.orientation, 0) { : AModule(config, "workspaces", id, false, false),
m_bar(bar),
m_box(bar.orientation, 0),
m_ipc(IPC::inst()) {
modulesReady = true; modulesReady = true;
parseConfig(config); parseConfig(config);
@ -24,23 +28,19 @@ Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value
m_box.get_style_context()->add_class(MODULE_CLASS); m_box.get_style_context()->add_class(MODULE_CLASS);
event_box_.add(m_box); event_box_.add(m_box);
if (!gIPC) {
gIPC = std::make_unique<IPC>();
}
setCurrentMonitorId(); setCurrentMonitorId();
init(); init();
registerIpc(); registerIpc();
} }
Workspaces::~Workspaces() { Workspaces::~Workspaces() {
gIPC->unregisterForIPC(this); m_ipc.unregisterForIPC(this);
// wait for possible event handler to finish // wait for possible event handler to finish
std::lock_guard<std::mutex> lg(m_mutex); std::lock_guard<std::mutex> lg(m_mutex);
} }
void Workspaces::init() { void Workspaces::init() {
m_activeWorkspaceName = (gIPC->getSocket1JsonReply("activeworkspace"))["name"].asString(); m_activeWorkspaceId = m_ipc.getSocket1JsonReply("activeworkspace")["id"].asInt();
initializeWorkspaces(); initializeWorkspaces();
dp.emit(); dp.emit();
@ -50,13 +50,12 @@ Json::Value Workspaces::createMonitorWorkspaceData(std::string const &name,
std::string const &monitor) { std::string const &monitor) {
spdlog::trace("Creating persistent workspace: {} on monitor {}", name, monitor); spdlog::trace("Creating persistent workspace: {} on monitor {}", name, monitor);
Json::Value workspaceData; Json::Value workspaceData;
try {
// numbered persistent workspaces get the name as ID auto workspaceId = parseWorkspaceId(name);
workspaceData["id"] = name == "special" ? -99 : std::stoi(name); if (!workspaceId.has_value()) {
} catch (const std::exception &e) { workspaceId = 0;
// named persistent workspaces start with ID=0
workspaceData["id"] = 0;
} }
workspaceData["id"] = *workspaceId;
workspaceData["name"] = name; workspaceData["name"] = name;
workspaceData["monitor"] = monitor; workspaceData["monitor"] = monitor;
workspaceData["windows"] = 0; workspaceData["windows"] = 0;
@ -69,9 +68,8 @@ void Workspaces::createWorkspace(Json::Value const &workspace_data,
spdlog::debug("Creating workspace {}", workspaceName); spdlog::debug("Creating workspace {}", workspaceName);
// avoid recreating existing workspaces // avoid recreating existing workspaces
auto workspace = std::find_if( auto workspace =
m_workspaces.begin(), m_workspaces.end(), std::ranges::find_if(m_workspaces, [workspaceName](std::unique_ptr<Workspace> const &w) {
[workspaceName](std::unique_ptr<Workspace> const &w) {
return (workspaceName.starts_with("special:") && workspaceName.substr(8) == w->name()) || return (workspaceName.starts_with("special:") && workspaceName.substr(8) == w->name()) ||
workspaceName == w->name(); workspaceName == w->name();
}); });
@ -81,14 +79,14 @@ void Workspaces::createWorkspace(Json::Value const &workspace_data,
const auto keys = workspace_data.getMemberNames(); const auto keys = workspace_data.getMemberNames();
const auto *k = "persistent-rule"; const auto *k = "persistent-rule";
if (std::find(keys.begin(), keys.end(), k) != keys.end()) { if (std::ranges::find(keys, k) != keys.end()) {
spdlog::debug("Set dynamic persistency of workspace {} to: {}", workspaceName, spdlog::debug("Set dynamic persistency of workspace {} to: {}", workspaceName,
workspace_data[k].asBool() ? "true" : "false"); workspace_data[k].asBool() ? "true" : "false");
(*workspace)->setPersistentRule(workspace_data[k].asBool()); (*workspace)->setPersistentRule(workspace_data[k].asBool());
} }
k = "persistent-config"; k = "persistent-config";
if (std::find(keys.begin(), keys.end(), k) != keys.end()) { if (std::ranges::find(keys, k) != keys.end()) {
spdlog::debug("Set config persistency of workspace {} to: {}", workspaceName, spdlog::debug("Set config persistency of workspace {} to: {}", workspaceName,
workspace_data[k].asBool() ? "true" : "false"); workspace_data[k].asBool() ? "true" : "false");
(*workspace)->setPersistentConfig(workspace_data[k].asBool()); (*workspace)->setPersistentConfig(workspace_data[k].asBool());
@ -159,18 +157,18 @@ std::string Workspaces::getRewrite(std::string window_class, std::string window_
fmt::arg("title", window_title)); fmt::arg("title", window_title));
} }
std::vector<std::string> Workspaces::getVisibleWorkspaces() { std::vector<int> Workspaces::getVisibleWorkspaces() {
std::vector<std::string> visibleWorkspaces; std::vector<int> visibleWorkspaces;
auto monitors = gIPC->getSocket1JsonReply("monitors"); auto monitors = IPC::inst().getSocket1JsonReply("monitors");
for (const auto &monitor : monitors) { for (const auto &monitor : monitors) {
auto ws = monitor["activeWorkspace"]; auto ws = monitor["activeWorkspace"];
if (ws.isObject() && ws["name"].isString()) { if (ws.isObject() && ws["id"].isInt()) {
visibleWorkspaces.push_back(ws["name"].asString()); visibleWorkspaces.push_back(ws["id"].asInt());
} }
auto sws = monitor["specialWorkspace"]; auto sws = monitor["specialWorkspace"];
auto name = sws["name"].asString(); auto name = sws["name"].asString();
if (sws.isObject() && sws["name"].isString() && !name.empty()) { if (sws.isObject() && sws["id"].isInt() && !name.empty()) {
visibleWorkspaces.push_back(!name.starts_with("special:") ? name : name.substr(8)); visibleWorkspaces.push_back(sws["id"].asInt());
} }
} }
return visibleWorkspaces; return visibleWorkspaces;
@ -181,12 +179,12 @@ void Workspaces::initializeWorkspaces() {
// if the workspace rules changed since last initialization, make sure we reset everything: // if the workspace rules changed since last initialization, make sure we reset everything:
for (auto &workspace : m_workspaces) { for (auto &workspace : m_workspaces) {
m_workspacesToRemove.push_back(workspace->name()); m_workspacesToRemove.push_back(std::to_string(workspace->id()));
} }
// get all current workspaces // get all current workspaces
auto const workspacesJson = gIPC->getSocket1JsonReply("workspaces"); auto const workspacesJson = m_ipc.getSocket1JsonReply("workspaces");
auto const clientsJson = gIPC->getSocket1JsonReply("clients"); auto const clientsJson = m_ipc.getSocket1JsonReply("clients");
for (Json::Value workspaceJson : workspacesJson) { for (Json::Value workspaceJson : workspacesJson) {
std::string workspaceName = workspaceJson["name"].asString(); std::string workspaceName = workspaceJson["name"].asString();
@ -233,7 +231,7 @@ void Workspaces::loadPersistentWorkspacesFromConfig(Json::Value const &clientsJs
std::vector<std::string> persistentWorkspacesToCreate; std::vector<std::string> persistentWorkspacesToCreate;
const std::string currentMonitor = m_bar.output->name; const std::string currentMonitor = m_bar.output->name;
const bool monitorInConfig = std::find(keys.begin(), keys.end(), currentMonitor) != keys.end(); const bool monitorInConfig = std::ranges::find(keys, currentMonitor) != keys.end();
for (const std::string &key : keys) { for (const std::string &key : keys) {
// only add if either: // only add if either:
// 1. key is the current monitor name // 1. key is the current monitor name
@ -248,7 +246,7 @@ void Workspaces::loadPersistentWorkspacesFromConfig(Json::Value const &clientsJs
int amount = value.asInt(); int amount = value.asInt();
spdlog::debug("Creating {} persistent workspaces for monitor {}", amount, currentMonitor); spdlog::debug("Creating {} persistent workspaces for monitor {}", amount, currentMonitor);
for (int i = 0; i < amount; i++) { for (int i = 0; i < amount; i++) {
persistentWorkspacesToCreate.emplace_back(std::to_string(m_monitorId * amount + i + 1)); persistentWorkspacesToCreate.emplace_back(std::to_string((m_monitorId * amount) + i + 1));
} }
} }
} else if (value.isArray() && !value.empty()) { } else if (value.isArray() && !value.empty()) {
@ -285,7 +283,7 @@ void Workspaces::loadPersistentWorkspacesFromConfig(Json::Value const &clientsJs
void Workspaces::loadPersistentWorkspacesFromWorkspaceRules(const Json::Value &clientsJson) { void Workspaces::loadPersistentWorkspacesFromWorkspaceRules(const Json::Value &clientsJson) {
spdlog::info("Loading persistent workspaces from Hyprland workspace rules"); spdlog::info("Loading persistent workspaces from Hyprland workspace rules");
auto const workspaceRules = gIPC->getSocket1JsonReply("workspacerules"); auto const workspaceRules = m_ipc.getSocket1JsonReply("workspacerules");
for (Json::Value const &rule : workspaceRules) { for (Json::Value const &rule : workspaceRules) {
if (!rule["workspaceString"].isString()) { if (!rule["workspaceString"].isString()) {
spdlog::warn("Workspace rules: invalid workspaceString, skipping: {}", rule); spdlog::warn("Workspace rules: invalid workspaceString, skipping: {}", rule);
@ -294,7 +292,8 @@ void Workspaces::loadPersistentWorkspacesFromWorkspaceRules(const Json::Value &c
if (!rule["persistent"].asBool()) { if (!rule["persistent"].asBool()) {
continue; continue;
} }
auto const &workspace = rule["workspaceString"].asString(); auto const &workspace = rule.isMember("defaultName") ? rule["defaultName"].asString()
: rule["workspaceString"].asString();
auto const &monitor = rule["monitor"].asString(); auto const &monitor = rule["monitor"].asString();
// create this workspace persistently if: // create this workspace persistently if:
// 1. the allOutputs config option is enabled // 1. the allOutputs config option is enabled
@ -306,6 +305,7 @@ void Workspaces::loadPersistentWorkspacesFromWorkspaceRules(const Json::Value &c
workspaceData["persistent-rule"] = true; workspaceData["persistent-rule"] = true;
m_workspacesToCreate.emplace_back(workspaceData, clientsJson); m_workspacesToCreate.emplace_back(workspaceData, clientsJson);
} else { } else {
// This can be any workspace selector.
m_workspacesToRemove.emplace_back(workspace); m_workspacesToRemove.emplace_back(workspace);
} }
} }
@ -316,29 +316,29 @@ void Workspaces::onEvent(const std::string &ev) {
std::string eventName(begin(ev), begin(ev) + ev.find_first_of('>')); std::string eventName(begin(ev), begin(ev) + ev.find_first_of('>'));
std::string payload = ev.substr(eventName.size() + 2); std::string payload = ev.substr(eventName.size() + 2);
if (eventName == "workspace") { if (eventName == "workspacev2") {
onWorkspaceActivated(payload); onWorkspaceActivated(payload);
} else if (eventName == "activespecial") { } else if (eventName == "activespecial") {
onSpecialWorkspaceActivated(payload); onSpecialWorkspaceActivated(payload);
} else if (eventName == "destroyworkspace") { } else if (eventName == "destroyworkspacev2") {
onWorkspaceDestroyed(payload); onWorkspaceDestroyed(payload);
} else if (eventName == "createworkspace") { } else if (eventName == "createworkspacev2") {
onWorkspaceCreated(payload); onWorkspaceCreated(payload);
} else if (eventName == "focusedmon") { } else if (eventName == "focusedmonv2") {
onMonitorFocused(payload); onMonitorFocused(payload);
} else if (eventName == "moveworkspace") { } else if (eventName == "moveworkspacev2") {
onWorkspaceMoved(payload); onWorkspaceMoved(payload);
} else if (eventName == "openwindow") { } else if (eventName == "openwindow") {
onWindowOpened(payload); onWindowOpened(payload);
} else if (eventName == "closewindow") { } else if (eventName == "closewindow") {
onWindowClosed(payload); onWindowClosed(payload);
} else if (eventName == "movewindow") { } else if (eventName == "movewindowv2") {
onWindowMoved(payload); onWindowMoved(payload);
} else if (eventName == "urgent") { } else if (eventName == "urgent") {
setUrgentWorkspace(payload); setUrgentWorkspace(payload);
} else if (eventName == "renameworkspace") { } else if (eventName == "renameworkspace") {
onWorkspaceRenamed(payload); onWorkspaceRenamed(payload);
} else if (eventName == "windowtitle") { } else if (eventName == "windowtitlev2") {
onWindowTitleEvent(payload); onWindowTitleEvent(payload);
} else if (eventName == "configreloaded") { } else if (eventName == "configreloaded") {
onConfigReloaded(); onConfigReloaded();
@ -348,7 +348,11 @@ void Workspaces::onEvent(const std::string &ev) {
} }
void Workspaces::onWorkspaceActivated(std::string const &payload) { void Workspaces::onWorkspaceActivated(std::string const &payload) {
m_activeWorkspaceName = payload; const auto [workspaceIdStr, workspaceName] = splitDoublePayload(payload);
const auto workspaceId = parseWorkspaceId(workspaceIdStr);
if (workspaceId.has_value()) {
m_activeWorkspaceId = *workspaceId;
}
} }
void Workspaces::onSpecialWorkspaceActivated(std::string const &payload) { void Workspaces::onSpecialWorkspaceActivated(std::string const &payload) {
@ -357,25 +361,44 @@ void Workspaces::onSpecialWorkspaceActivated(std::string const &payload) {
} }
void Workspaces::onWorkspaceDestroyed(std::string const &payload) { void Workspaces::onWorkspaceDestroyed(std::string const &payload) {
if (!isDoubleSpecial(payload)) { const auto [workspaceId, workspaceName] = splitDoublePayload(payload);
m_workspacesToRemove.push_back(payload); if (!isDoubleSpecial(workspaceName)) {
m_workspacesToRemove.push_back(workspaceId);
} }
} }
void Workspaces::onWorkspaceCreated(std::string const &workspaceName, void Workspaces::onWorkspaceCreated(std::string const &payload, Json::Value const &clientsData) {
Json::Value const &clientsData) { spdlog::debug("Workspace created: {}", payload);
spdlog::debug("Workspace created: {}", workspaceName);
auto const workspacesJson = gIPC->getSocket1JsonReply("workspaces"); const auto [workspaceIdStr, _] = splitDoublePayload(payload);
const auto workspaceId = parseWorkspaceId(workspaceIdStr);
if (!workspaceId.has_value()) {
return;
}
auto const workspaceRules = m_ipc.getSocket1JsonReply("workspacerules");
auto const workspacesJson = m_ipc.getSocket1JsonReply("workspaces");
if (!isWorkspaceIgnored(workspaceName)) {
auto const workspaceRules = gIPC->getSocket1JsonReply("workspacerules");
for (Json::Value workspaceJson : workspacesJson) { for (Json::Value workspaceJson : workspacesJson) {
std::string name = workspaceJson["name"].asString(); const auto currentId = workspaceJson["id"].asInt();
if (name == workspaceName) { if (currentId == *workspaceId) {
std::string workspaceName = workspaceJson["name"].asString();
// This workspace name is more up-to-date than the one in the event payload.
if (isWorkspaceIgnored(workspaceName)) {
spdlog::trace("Not creating workspace because it is ignored: id={} name={}", *workspaceId,
workspaceName);
break;
}
if ((allOutputs() || m_bar.output->name == workspaceJson["monitor"].asString()) && if ((allOutputs() || m_bar.output->name == workspaceJson["monitor"].asString()) &&
(showSpecial() || !name.starts_with("special")) && !isDoubleSpecial(workspaceName)) { (showSpecial() || !workspaceName.starts_with("special")) &&
!isDoubleSpecial(workspaceName)) {
for (Json::Value const &rule : workspaceRules) { for (Json::Value const &rule : workspaceRules) {
if (rule["workspaceString"].asString() == workspaceName) { auto ruleWorkspaceName = rule.isMember("defaultName")
? rule["defaultName"].asString()
: rule["workspaceString"].asString();
if (ruleWorkspaceName == workspaceName) {
workspaceJson["persistent-rule"] = rule["persistent"].asBool(); workspaceJson["persistent-rule"] = rule["persistent"].asBool();
break; break;
} }
@ -385,44 +408,43 @@ void Workspaces::onWorkspaceCreated(std::string const &workspaceName,
break; break;
} }
} else { } else {
extendOrphans(workspaceJson["id"].asInt(), clientsData); extendOrphans(*workspaceId, clientsData);
} }
} }
} else {
spdlog::trace("Not creating workspace because it is ignored: {}", workspaceName);
}
} }
void Workspaces::onWorkspaceMoved(std::string const &payload) { void Workspaces::onWorkspaceMoved(std::string const &payload) {
spdlog::debug("Workspace moved: {}", payload); spdlog::debug("Workspace moved: {}", payload);
// Update active workspace // Update active workspace
m_activeWorkspaceName = (gIPC->getSocket1JsonReply("activeworkspace"))["name"].asString(); m_activeWorkspaceId = (m_ipc.getSocket1JsonReply("activeworkspace"))["id"].asInt();
if (allOutputs()) return; if (allOutputs()) return;
std::string workspaceName = payload.substr(0, payload.find(',')); const auto [workspaceIdStr, workspaceName, monitorName] = splitTriplePayload(payload);
std::string monitorName = payload.substr(payload.find(',') + 1);
const auto subPayload = makePayload(workspaceIdStr, workspaceName);
if (m_bar.output->name == monitorName) { if (m_bar.output->name == monitorName) {
Json::Value clientsData = gIPC->getSocket1JsonReply("clients"); Json::Value clientsData = m_ipc.getSocket1JsonReply("clients");
onWorkspaceCreated(workspaceName, clientsData); onWorkspaceCreated(subPayload, clientsData);
} else { } else {
spdlog::debug("Removing workspace because it was moved to another monitor: {}"); spdlog::debug("Removing workspace because it was moved to another monitor: {}", subPayload);
onWorkspaceDestroyed(workspaceName); onWorkspaceDestroyed(subPayload);
} }
} }
void Workspaces::onWorkspaceRenamed(std::string const &payload) { void Workspaces::onWorkspaceRenamed(std::string const &payload) {
spdlog::debug("Workspace renamed: {}", payload); spdlog::debug("Workspace renamed: {}", payload);
std::string workspaceIdStr = payload.substr(0, payload.find(',')); const auto [workspaceIdStr, newName] = splitDoublePayload(payload);
int workspaceId = workspaceIdStr == "special" ? -99 : std::stoi(workspaceIdStr);
std::string newName = payload.substr(payload.find(',') + 1); const auto workspaceId = parseWorkspaceId(workspaceIdStr);
for (auto &workspace : m_workspaces) { if (!workspaceId.has_value()) {
if (workspace->id() == workspaceId) { return;
if (workspace->name() == m_activeWorkspaceName) {
m_activeWorkspaceName = newName;
} }
for (auto &workspace : m_workspaces) {
if (workspace->id() == *workspaceId) {
workspace->setName(newName); workspace->setName(newName);
break; break;
} }
@ -432,11 +454,19 @@ void Workspaces::onWorkspaceRenamed(std::string const &payload) {
void Workspaces::onMonitorFocused(std::string const &payload) { void Workspaces::onMonitorFocused(std::string const &payload) {
spdlog::trace("Monitor focused: {}", payload); spdlog::trace("Monitor focused: {}", payload);
m_activeWorkspaceName = payload.substr(payload.find(',') + 1);
for (Json::Value &monitor : gIPC->getSocket1JsonReply("monitors")) { const auto [monitorName, workspaceIdStr] = splitDoublePayload(payload);
if (monitor["name"].asString() == payload.substr(0, payload.find(','))) {
auto name = monitor["specialWorkspace"]["name"].asString(); const auto workspaceId = parseWorkspaceId(workspaceIdStr);
if (!workspaceId.has_value()) {
return;
}
m_activeWorkspaceId = *workspaceId;
for (Json::Value &monitor : m_ipc.getSocket1JsonReply("monitors")) {
if (monitor["name"].asString() == monitorName) {
const auto name = monitor["specialWorkspace"]["name"].asString();
m_activeSpecialWorkspaceName = !name.starts_with("special:") ? name : name.substr(8); m_activeSpecialWorkspaceName = !name.starts_with("special:") ? name : name.substr(8);
} }
} }
@ -475,11 +505,7 @@ void Workspaces::onWindowClosed(std::string const &addr) {
void Workspaces::onWindowMoved(std::string const &payload) { void Workspaces::onWindowMoved(std::string const &payload) {
spdlog::trace("Window moved: {}", payload); spdlog::trace("Window moved: {}", payload);
updateWindowCount(); updateWindowCount();
size_t lastCommaIdx = 0; auto [windowAddress, _, workspaceName] = splitTriplePayload(payload);
size_t nextCommaIdx = payload.find(',');
std::string windowAddress = payload.substr(lastCommaIdx, nextCommaIdx - lastCommaIdx);
std::string workspaceName = payload.substr(nextCommaIdx + 1, payload.length() - nextCommaIdx);
std::string windowRepr; std::string windowRepr;
@ -487,7 +513,7 @@ void Workspaces::onWindowMoved(std::string const &payload) {
// and exit // and exit
for (auto &window : m_windowsToCreate) { for (auto &window : m_windowsToCreate) {
if (window.getAddress() == windowAddress) { if (window.getAddress() == windowAddress) {
window.moveToWorksace(workspaceName); window.moveToWorkspace(workspaceName);
return; return;
} }
} }
@ -515,13 +541,15 @@ void Workspaces::onWindowTitleEvent(std::string const &payload) {
spdlog::trace("Window title changed: {}", payload); spdlog::trace("Window title changed: {}", payload);
std::optional<std::function<void(WindowCreationPayload)>> inserter; std::optional<std::function<void(WindowCreationPayload)>> inserter;
const auto [windowAddress, _] = splitDoublePayload(payload);
// If the window was an orphan, rename it at the orphan's vector // If the window was an orphan, rename it at the orphan's vector
if (m_orphanWindowMap.contains(payload)) { if (m_orphanWindowMap.contains(windowAddress)) {
inserter = [this](WindowCreationPayload wcp) { this->registerOrphanWindow(std::move(wcp)); }; inserter = [this](WindowCreationPayload wcp) { this->registerOrphanWindow(std::move(wcp)); };
} else { } else {
auto windowWorkspace = auto windowWorkspace = std::ranges::find_if(m_workspaces, [windowAddress](auto &workspace) {
std::find_if(m_workspaces.begin(), m_workspaces.end(), return workspace->containsWindow(windowAddress);
[payload](auto &workspace) { return workspace->containsWindow(payload); }); });
// If the window exists on a workspace, rename it at the workspace's window // If the window exists on a workspace, rename it at the workspace's window
// map // map
@ -530,9 +558,9 @@ void Workspaces::onWindowTitleEvent(std::string const &payload) {
(*windowWorkspace)->insertWindow(std::move(wcp)); (*windowWorkspace)->insertWindow(std::move(wcp));
}; };
} else { } else {
auto queuedWindow = std::find_if( auto queuedWindow = std::ranges::find_if(m_windowsToCreate, [payload](auto &windowPayload) {
m_windowsToCreate.begin(), m_windowsToCreate.end(), return windowPayload.getAddress() == payload;
[payload](auto &windowPayload) { return windowPayload.getAddress() == payload; }); });
// If the window was queued, rename it in the queue // If the window was queued, rename it in the queue
if (queuedWindow != m_windowsToCreate.end()) { if (queuedWindow != m_windowsToCreate.end()) {
@ -542,15 +570,14 @@ void Workspaces::onWindowTitleEvent(std::string const &payload) {
} }
if (inserter.has_value()) { if (inserter.has_value()) {
Json::Value clientsData = gIPC->getSocket1JsonReply("clients"); Json::Value clientsData = m_ipc.getSocket1JsonReply("clients");
std::string jsonWindowAddress = fmt::format("0x{}", payload); std::string jsonWindowAddress = fmt::format("0x{}", payload);
auto client = auto client = std::ranges::find_if(clientsData, [jsonWindowAddress](auto &client) {
std::find_if(clientsData.begin(), clientsData.end(), [jsonWindowAddress](auto &client) {
return client["address"].asString() == jsonWindowAddress; return client["address"].asString() == jsonWindowAddress;
}); });
if (!client->empty()) { if (client != clientsData.end() && !client->empty()) {
(*inserter)({*client}); (*inserter)({*client});
} }
} }
@ -573,6 +600,7 @@ auto Workspaces::parseConfig(const Json::Value &config) -> void {
populateBoolConfig(config, "all-outputs", m_allOutputs); populateBoolConfig(config, "all-outputs", m_allOutputs);
populateBoolConfig(config, "show-special", m_showSpecial); populateBoolConfig(config, "show-special", m_showSpecial);
populateBoolConfig(config, "special-visible-only", m_specialVisibleOnly); populateBoolConfig(config, "special-visible-only", m_specialVisibleOnly);
populateBoolConfig(config, "persistent-only", m_persistentOnly);
populateBoolConfig(config, "active-only", m_activeOnly); populateBoolConfig(config, "active-only", m_activeOnly);
populateBoolConfig(config, "move-to-monitor", m_moveToMonitor); populateBoolConfig(config, "move-to-monitor", m_moveToMonitor);
@ -590,8 +618,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, auto Workspaces::populateBoolConfig(const Json::Value &config, const std::string &key, bool &member)
bool &member) -> void { -> void {
const auto &configValue = config[key]; const auto &configValue = config[key];
if (configValue.isBool()) { if (configValue.isBool()) {
member = configValue.asBool(); member = configValue.asBool();
@ -660,39 +688,57 @@ void Workspaces::registerOrphanWindow(WindowCreationPayload create_window_payloa
} }
auto Workspaces::registerIpc() -> void { auto Workspaces::registerIpc() -> void {
gIPC->registerForIPC("workspace", this); m_ipc.registerForIPC("workspacev2", this);
gIPC->registerForIPC("activespecial", this); m_ipc.registerForIPC("activespecial", this);
gIPC->registerForIPC("createworkspace", this); m_ipc.registerForIPC("createworkspacev2", this);
gIPC->registerForIPC("destroyworkspace", this); m_ipc.registerForIPC("destroyworkspacev2", this);
gIPC->registerForIPC("focusedmon", this); m_ipc.registerForIPC("focusedmonv2", this);
gIPC->registerForIPC("moveworkspace", this); m_ipc.registerForIPC("moveworkspacev2", this);
gIPC->registerForIPC("renameworkspace", this); m_ipc.registerForIPC("renameworkspace", this);
gIPC->registerForIPC("openwindow", this); m_ipc.registerForIPC("openwindow", this);
gIPC->registerForIPC("closewindow", this); m_ipc.registerForIPC("closewindow", this);
gIPC->registerForIPC("movewindow", this); m_ipc.registerForIPC("movewindowv2", this);
gIPC->registerForIPC("urgent", this); m_ipc.registerForIPC("urgent", this);
gIPC->registerForIPC("configreloaded", this); m_ipc.registerForIPC("configreloaded", this);
if (windowRewriteConfigUsesTitle()) { if (windowRewriteConfigUsesTitle()) {
spdlog::info( spdlog::info(
"Registering for Hyprland's 'windowtitle' events because a user-defined window " "Registering for Hyprland's 'windowtitlev2' events because a user-defined window "
"rewrite rule uses the 'title' field."); "rewrite rule uses the 'title' field.");
gIPC->registerForIPC("windowtitle", this); m_ipc.registerForIPC("windowtitlev2", this);
} }
} }
void Workspaces::removeWorkspacesToRemove() { void Workspaces::removeWorkspacesToRemove() {
for (const auto &workspaceName : m_workspacesToRemove) { for (const auto &workspaceString : m_workspacesToRemove) {
removeWorkspace(workspaceName); removeWorkspace(workspaceString);
} }
m_workspacesToRemove.clear(); m_workspacesToRemove.clear();
} }
void Workspaces::removeWorkspace(std::string const &name) { void Workspaces::removeWorkspace(std::string const &workspaceString) {
spdlog::debug("Removing workspace {}", name); spdlog::debug("Removing workspace {}", workspaceString);
auto workspace =
std::find_if(m_workspaces.begin(), m_workspaces.end(), [&](std::unique_ptr<Workspace> &x) { // If this succeeds, we have a workspace ID.
return (name.starts_with("special:") && name.substr(8) == x->name()) || name == x->name(); const auto workspaceId = parseWorkspaceId(workspaceString);
std::string name;
// TODO: At some point we want to support all workspace selectors
// This is just a subset.
// https://wiki.hyprland.org/Configuring/Workspace-Rules/#workspace-selectors
if (workspaceString.starts_with("special:")) {
name = workspaceString.substr(8);
} else if (workspaceString.starts_with("name:")) {
name = workspaceString.substr(5);
} else {
name = workspaceString;
}
const auto workspace = std::ranges::find_if(m_workspaces, [&](std::unique_ptr<Workspace> &x) {
if (workspaceId.has_value()) {
return *workspaceId == x->id();
}
return name == x->name();
}); });
if (workspace == m_workspaces.end()) { if (workspace == m_workspaces.end()) {
@ -701,7 +747,8 @@ void Workspaces::removeWorkspace(std::string const &name) {
} }
if ((*workspace)->isPersistentConfig()) { if ((*workspace)->isPersistentConfig()) {
spdlog::trace("Not removing config persistent workspace {}", name); spdlog::trace("Not removing config persistent workspace id={} name={}", (*workspace)->id(),
(*workspace)->name());
return; return;
} }
@ -712,10 +759,10 @@ void Workspaces::removeWorkspace(std::string const &name) {
void Workspaces::setCurrentMonitorId() { void Workspaces::setCurrentMonitorId() {
// get monitor ID from name (used by persistent workspaces) // get monitor ID from name (used by persistent workspaces)
m_monitorId = 0; m_monitorId = 0;
auto monitors = gIPC->getSocket1JsonReply("monitors"); auto monitors = m_ipc.getSocket1JsonReply("monitors");
auto currentMonitor = std::find_if( auto currentMonitor = std::ranges::find_if(monitors, [this](const Json::Value &m) {
monitors.begin(), monitors.end(), return m["name"].asString() == m_bar.output->name;
[this](const Json::Value &m) { return m["name"].asString() == m_bar.output->name; }); });
if (currentMonitor == monitors.end()) { if (currentMonitor == monitors.end()) {
spdlog::error("Monitor '{}' does not have an ID? Using 0", m_bar.output->name); spdlog::error("Monitor '{}' does not have an ID? Using 0", m_bar.output->name);
} else { } else {
@ -725,8 +772,8 @@ void Workspaces::setCurrentMonitorId() {
} }
void Workspaces::sortWorkspaces() { void Workspaces::sortWorkspaces() {
std::sort(m_workspaces.begin(), m_workspaces.end(), std::ranges::sort( //
[&](std::unique_ptr<Workspace> &a, std::unique_ptr<Workspace> &b) { m_workspaces, [&](std::unique_ptr<Workspace> &a, std::unique_ptr<Workspace> &b) {
// Helper comparisons // Helper comparisons
auto isIdLess = a->id() < b->id(); auto isIdLess = a->id() < b->id();
auto isNameLess = a->name() < b->name(); auto isNameLess = a->name() < b->name();
@ -769,7 +816,8 @@ void Workspaces::sortWorkspaces() {
if (a->id() == -99 || b->id() == -99) { if (a->id() == -99 || b->id() == -99) {
return b->id() == -99; return b->id() == -99;
} }
// both are 0 (not yet named persistents) / named specials (-98 <= ID <= -1) // both are 0 (not yet named persistents) / named specials
// (-98 <= ID <= -1)
return isNameLess; return isNameLess;
} }
@ -788,7 +836,7 @@ void Workspaces::sortWorkspaces() {
} }
void Workspaces::setUrgentWorkspace(std::string const &windowaddress) { void Workspaces::setUrgentWorkspace(std::string const &windowaddress) {
const Json::Value clientsJson = gIPC->getSocket1JsonReply("clients"); const Json::Value clientsJson = m_ipc.getSocket1JsonReply("clients");
int workspaceId = -1; int workspaceId = -1;
for (Json::Value clientJson : clientsJson) { for (Json::Value clientJson : clientsJson) {
@ -798,9 +846,9 @@ void Workspaces::setUrgentWorkspace(std::string const &windowaddress) {
} }
} }
auto workspace = auto workspace = std::ranges::find_if(m_workspaces, [workspaceId](std::unique_ptr<Workspace> &x) {
std::find_if(m_workspaces.begin(), m_workspaces.end(), return x->id() == workspaceId;
[workspaceId](std::unique_ptr<Workspace> &x) { return x->id() == workspaceId; }); });
if (workspace != m_workspaces.end()) { if (workspace != m_workspaces.end()) {
workspace->get()->setUrgent(); workspace->get()->setUrgent();
} }
@ -812,10 +860,9 @@ auto Workspaces::update() -> void {
} }
void Workspaces::updateWindowCount() { void Workspaces::updateWindowCount() {
const Json::Value workspacesJson = gIPC->getSocket1JsonReply("workspaces"); const Json::Value workspacesJson = m_ipc.getSocket1JsonReply("workspaces");
for (auto &workspace : m_workspaces) { for (auto &workspace : m_workspaces) {
auto workspaceJson = auto workspaceJson = std::ranges::find_if(workspacesJson, [&](Json::Value const &x) {
std::find_if(workspacesJson.begin(), workspacesJson.end(), [&](Json::Value const &x) {
return x["name"].asString() == workspace->name() || return x["name"].asString() == workspace->name() ||
(workspace->isSpecial() && x["name"].asString() == "special:" + workspace->name()); (workspace->isSpecial() && x["name"].asString() == "special:" + workspace->name());
}); });
@ -858,22 +905,22 @@ bool Workspaces::updateWindowsToCreate() {
} }
void Workspaces::updateWorkspaceStates() { void Workspaces::updateWorkspaceStates() {
const std::vector<std::string> visibleWorkspaces = getVisibleWorkspaces(); const std::vector<int> visibleWorkspaces = getVisibleWorkspaces();
auto updatedWorkspaces = gIPC->getSocket1JsonReply("workspaces"); auto updatedWorkspaces = m_ipc.getSocket1JsonReply("workspaces");
for (auto &workspace : m_workspaces) { for (auto &workspace : m_workspaces) {
workspace->setActive(workspace->name() == m_activeWorkspaceName || workspace->setActive(
workspace->name() == m_activeSpecialWorkspaceName); workspace->id() == m_activeWorkspaceId ||
if (workspace->name() == m_activeWorkspaceName && workspace->isUrgent()) { (workspace->isSpecial() && workspace->name() == m_activeSpecialWorkspaceName));
if (workspace->isActive() && workspace->isUrgent()) {
workspace->setUrgent(false); workspace->setUrgent(false);
} }
workspace->setVisible(std::find(visibleWorkspaces.begin(), visibleWorkspaces.end(), workspace->setVisible(std::ranges::find(visibleWorkspaces, workspace->id()) !=
workspace->name()) != visibleWorkspaces.end()); visibleWorkspaces.end());
std::string &workspaceIcon = m_iconsMap[""]; std::string &workspaceIcon = m_iconsMap[""];
if (m_withIcon) { if (m_withIcon) {
workspaceIcon = workspace->selectIcon(m_iconsMap); workspaceIcon = workspace->selectIcon(m_iconsMap);
} }
auto updatedWorkspace = std::find_if( auto updatedWorkspace = std::ranges::find_if(updatedWorkspaces, [&workspace](const auto &w) {
updatedWorkspaces.begin(), updatedWorkspaces.end(), [&workspace](const auto &w) {
auto wNameRaw = w["name"].asString(); auto wNameRaw = w["name"].asString();
auto wName = wNameRaw.starts_with("special:") ? wNameRaw.substr(8) : wNameRaw; auto wName = wNameRaw.starts_with("special:") ? wNameRaw.substr(8) : wNameRaw;
return wName == workspace->name(); return wName == workspace->name();
@ -905,4 +952,39 @@ int Workspaces::windowRewritePriorityFunction(std::string const &window_rule) {
return 0; return 0;
} }
template <typename... Args>
std::string Workspaces::makePayload(Args const &...args) {
std::ostringstream result;
bool first = true;
((result << (first ? "" : ",") << args, first = false), ...);
return result.str();
}
std::pair<std::string, std::string> Workspaces::splitDoublePayload(std::string const &payload) {
const std::string part1 = payload.substr(0, payload.find(','));
const std::string part2 = payload.substr(part1.size() + 1);
return {part1, part2};
}
std::tuple<std::string, std::string, std::string> Workspaces::splitTriplePayload(
std::string const &payload) {
const size_t firstComma = payload.find(',');
const size_t secondComma = payload.find(',', firstComma + 1);
const std::string part1 = payload.substr(0, firstComma);
const std::string part2 = payload.substr(firstComma + 1, secondComma - (firstComma + 1));
const std::string part3 = payload.substr(secondComma + 1);
return {part1, part2, part3};
}
std::optional<int> Workspaces::parseWorkspaceId(std::string const &workspaceIdStr) {
try {
return workspaceIdStr == "special" ? -99 : std::stoi(workspaceIdStr);
} catch (std::exception const &e) {
spdlog::error("Failed to parse workspace ID: {}", e.what());
return std::nullopt;
}
}
} // namespace waybar::modules::hyprland } // namespace waybar::modules::hyprland

View File

@ -60,6 +60,7 @@ auto waybar::modules::Memory::update() -> void {
fmt::arg("icon", getIcon(used_ram_percentage, icons)), fmt::arg("icon", getIcon(used_ram_percentage, icons)),
fmt::arg("total", total_ram_gigabytes), fmt::arg("swapTotal", total_swap_gigabytes), fmt::arg("total", total_ram_gigabytes), fmt::arg("swapTotal", total_swap_gigabytes),
fmt::arg("percentage", used_ram_percentage), fmt::arg("percentage", used_ram_percentage),
fmt::arg("swapState", swaptotal == 0 ? "Off" : "On"),
fmt::arg("swapPercentage", used_swap_percentage), fmt::arg("used", used_ram_gigabytes), fmt::arg("swapPercentage", used_swap_percentage), fmt::arg("used", used_ram_gigabytes),
fmt::arg("swapUsed", used_swap_gigabytes), fmt::arg("avail", available_ram_gigabytes), fmt::arg("swapUsed", used_swap_gigabytes), fmt::arg("avail", available_ram_gigabytes),
fmt::arg("swapAvail", available_swap_gigabytes))); fmt::arg("swapAvail", available_swap_gigabytes)));
@ -72,6 +73,7 @@ auto waybar::modules::Memory::update() -> void {
fmt::runtime(tooltip_format), used_ram_percentage, fmt::runtime(tooltip_format), used_ram_percentage,
fmt::arg("total", total_ram_gigabytes), fmt::arg("swapTotal", total_swap_gigabytes), fmt::arg("total", total_ram_gigabytes), fmt::arg("swapTotal", total_swap_gigabytes),
fmt::arg("percentage", used_ram_percentage), fmt::arg("percentage", used_ram_percentage),
fmt::arg("swapState", swaptotal == 0 ? "Off" : "On"),
fmt::arg("swapPercentage", used_swap_percentage), fmt::arg("used", used_ram_gigabytes), fmt::arg("swapPercentage", used_swap_percentage), fmt::arg("used", used_ram_gigabytes),
fmt::arg("swapUsed", used_swap_gigabytes), fmt::arg("avail", available_ram_gigabytes), fmt::arg("swapUsed", used_swap_gigabytes), fmt::arg("avail", available_ram_gigabytes),
fmt::arg("swapAvail", available_swap_gigabytes))); fmt::arg("swapAvail", available_swap_gigabytes)));

View File

@ -14,7 +14,6 @@ extern "C" {
#include <glib.h> #include <glib.h>
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
namespace waybar::modules::mpris { namespace waybar::modules::mpris {
const std::string DEFAULT_FORMAT = "{player} ({status}): {dynamic}"; const std::string DEFAULT_FORMAT = "{player} ({status}): {dynamic}";
@ -425,9 +424,11 @@ auto Mpris::onPlayerNameVanished(PlayerctlPlayerManager* manager, PlayerctlPlaye
auto* mpris = static_cast<Mpris*>(data); auto* mpris = static_cast<Mpris*>(data);
if (!mpris) return; if (!mpris) return;
spdlog::debug("mpris: player-vanished callback: {}", player_name->name); spdlog::debug("mpris: name-vanished callback: {}", player_name->name);
if (std::string(player_name->name) == mpris->player_) { if (mpris->player_ == "playerctld") {
mpris->dp.emit();
} else if (mpris->player_ == player_name->name) {
mpris->player = nullptr; mpris->player = nullptr;
mpris->event_box_.set_visible(false); mpris->event_box_.set_visible(false);
mpris->dp.emit(); mpris->dp.emit();
@ -498,7 +499,10 @@ auto Mpris::getPlayerInfo() -> std::optional<PlayerInfo> {
// > get the list of players [..] in order of activity // > get the list of players [..] in order of activity
// https://github.com/altdesktop/playerctl/blob/b19a71cb9dba635df68d271bd2b3f6a99336a223/playerctl/playerctl-common.c#L248-L249 // https://github.com/altdesktop/playerctl/blob/b19a71cb9dba635df68d271bd2b3f6a99336a223/playerctl/playerctl-common.c#L248-L249
players = g_list_first(players); players = g_list_first(players);
if (players) player_name = static_cast<PlayerctlPlayerName*>(players->data)->name; if (players)
player_name = static_cast<PlayerctlPlayerName*>(players->data)->name;
else
return std::nullopt; // no players found, hide the widget
} }
if (std::any_of(ignored_players_.begin(), ignored_players_.end(), if (std::any_of(ignored_players_.begin(), ignored_players_.end(),
@ -584,38 +588,45 @@ errorexit:
} }
bool Mpris::handleToggle(GdkEventButton* const& e) { bool Mpris::handleToggle(GdkEventButton* const& e) {
if (!e || e->type != GdkEventType::GDK_BUTTON_PRESS) {
return false;
}
auto info = getPlayerInfo();
if (!info) return false;
struct ButtonAction {
guint button;
const char* config_key;
std::function<void()> builtin_action;
};
GError* error = nullptr; GError* error = nullptr;
waybar::util::ScopeGuard error_deleter([error]() { waybar::util::ScopeGuard error_deleter([&error]() {
if (error) { if (error) {
g_error_free(error); g_error_free(error);
} }
}); });
auto info = getPlayerInfo(); // Command pattern: encapsulate each button's action
if (!info) return false; const ButtonAction actions[] = {
{1, "on-click", [&]() { playerctl_player_play_pause(player, &error); }},
{2, "on-click-middle", [&]() { playerctl_player_previous(player, &error); }},
{3, "on-click-right", [&]() { playerctl_player_next(player, &error); }},
{8, "on-click-backward", [&]() { playerctl_player_previous(player, &error); }},
{9, "on-click-forward", [&]() { playerctl_player_next(player, &error); }},
};
if (e->type == GdkEventType::GDK_BUTTON_PRESS) { for (const auto& action : actions) {
switch (e->button) { if (e->button == action.button) {
case 1: // left-click if (config_[action.config_key].isString()) {
if (config_["on-click"].isString()) {
return ALabel::handleToggle(e); return ALabel::handleToggle(e);
} }
playerctl_player_play_pause(player, &error); action.builtin_action();
break;
case 2: // middle-click
if (config_["on-click-middle"].isString()) {
return ALabel::handleToggle(e);
}
playerctl_player_previous(player, &error);
break;
case 3: // right-click
if (config_["on-click-right"].isString()) {
return ALabel::handleToggle(e);
}
playerctl_player_next(player, &error);
break; break;
} }
} }
if (error) { if (error) {
spdlog::error("mpris[{}]: error running builtin on-click action: {}", (*info).name, spdlog::error("mpris[{}]: error running builtin on-click action: {}", (*info).name,
error->message); error->message);

View File

@ -80,6 +80,7 @@ waybar::modules::Network::readBandwidthUsage() {
waybar::modules::Network::Network(const std::string &id, const Json::Value &config) waybar::modules::Network::Network(const std::string &id, const Json::Value &config)
: ALabel(config, "network", id, DEFAULT_FORMAT, 60), : ALabel(config, "network", id, DEFAULT_FORMAT, 60),
ifid_(-1), ifid_(-1),
addr_pref_(IPV4),
efd_(-1), efd_(-1),
ev_fd_(-1), ev_fd_(-1),
want_route_dump_(false), want_route_dump_(false),
@ -88,6 +89,7 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf
dump_in_progress_(false), dump_in_progress_(false),
is_p2p_(false), is_p2p_(false),
cidr_(0), cidr_(0),
cidr6_(0),
signal_strength_dbm_(0), signal_strength_dbm_(0),
signal_strength_(0), signal_strength_(0),
#ifdef WANT_RFKILL #ifdef WANT_RFKILL
@ -101,6 +103,12 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf
// the module start with no text, but the event_box_ is shown. // the module start with no text, but the event_box_ is shown.
label_.set_markup("<s></s>"); label_.set_markup("<s></s>");
if (config_["family"] == "ipv6") {
addr_pref_ = IPV6;
} else if (config["family"] == "ipv4_6") {
addr_pref_ = IPV4_6;
}
auto bandwidth = readBandwidthUsage(); auto bandwidth = readBandwidthUsage();
if (bandwidth.has_value()) { if (bandwidth.has_value()) {
bandwidth_down_total_ = (*bandwidth).first; bandwidth_down_total_ = (*bandwidth).first;
@ -215,8 +223,8 @@ void waybar::modules::Network::worker() {
std::lock_guard<std::mutex> lock(mutex_); std::lock_guard<std::mutex> lock(mutex_);
if (ifid_ > 0) { if (ifid_ > 0) {
getInfo(); getInfo();
dp.emit();
} }
dp.emit();
} }
thread_timer_.sleep_for(interval_); thread_timer_.sleep_for(interval_);
}; };
@ -263,14 +271,14 @@ void waybar::modules::Network::worker() {
} }
const std::string waybar::modules::Network::getNetworkState() const { const std::string waybar::modules::Network::getNetworkState() const {
if (ifid_ == -1) {
#ifdef WANT_RFKILL #ifdef WANT_RFKILL
if (rfkill_.getState()) return "disabled"; if (rfkill_.getState()) return "disabled";
#endif #endif
if (ifid_ == -1) {
return "disconnected"; return "disconnected";
} }
if (!carrier_) return "disconnected"; if (!carrier_) return "disconnected";
if (ipaddr_.empty()) return "linked"; if (ipaddr_.empty() && ipaddr6_.empty()) return "linked";
if (essid_.empty()) return "ethernet"; if (essid_.empty()) return "ethernet";
return "wifi"; return "wifi";
} }
@ -316,12 +324,24 @@ auto waybar::modules::Network::update() -> void {
} }
getState(signal_strength_); getState(signal_strength_);
std::string final_ipaddr_;
if (addr_pref_ == ip_addr_pref::IPV4) {
final_ipaddr_ = ipaddr_;
} else if (addr_pref_ == ip_addr_pref::IPV6) {
final_ipaddr_ = ipaddr6_;
} else if (addr_pref_ == ip_addr_pref::IPV4_6) {
final_ipaddr_ = ipaddr_;
final_ipaddr_ += '\n';
final_ipaddr_ += ipaddr6_;
}
auto text = fmt::format( auto text = fmt::format(
fmt::runtime(format_), fmt::arg("essid", essid_), fmt::arg("bssid", bssid_), fmt::runtime(format_), fmt::arg("essid", essid_), fmt::arg("bssid", bssid_),
fmt::arg("signaldBm", signal_strength_dbm_), fmt::arg("signalStrength", signal_strength_), fmt::arg("signaldBm", signal_strength_dbm_), fmt::arg("signalStrength", signal_strength_),
fmt::arg("signalStrengthApp", signal_strength_app_), fmt::arg("ifname", ifname_), fmt::arg("signalStrengthApp", signal_strength_app_), fmt::arg("ifname", ifname_),
fmt::arg("netmask", netmask_), fmt::arg("ipaddr", ipaddr_), fmt::arg("gwaddr", gwaddr_), fmt::arg("netmask", netmask_), fmt::arg("netmask6", netmask6_),
fmt::arg("cidr", cidr_), fmt::arg("frequency", fmt::format("{:.1f}", frequency_)), fmt::arg("ipaddr", final_ipaddr_), fmt::arg("gwaddr", gwaddr_), fmt::arg("cidr", cidr_),
fmt::arg("cidr6", cidr6_), fmt::arg("frequency", fmt::format("{:.1f}", frequency_)),
fmt::arg("icon", getIcon(signal_strength_, state_)), fmt::arg("icon", getIcon(signal_strength_, state_)),
fmt::arg("bandwidthDownBits", pow_format(bandwidth_down * 8ull / interval_.count(), "b/s")), fmt::arg("bandwidthDownBits", pow_format(bandwidth_down * 8ull / interval_.count(), "b/s")),
fmt::arg("bandwidthUpBits", pow_format(bandwidth_up * 8ull / interval_.count(), "b/s")), fmt::arg("bandwidthUpBits", pow_format(bandwidth_up * 8ull / interval_.count(), "b/s")),
@ -352,8 +372,9 @@ auto waybar::modules::Network::update() -> void {
fmt::runtime(tooltip_format), fmt::arg("essid", essid_), fmt::arg("bssid", bssid_), fmt::runtime(tooltip_format), fmt::arg("essid", essid_), fmt::arg("bssid", bssid_),
fmt::arg("signaldBm", signal_strength_dbm_), fmt::arg("signalStrength", signal_strength_), fmt::arg("signaldBm", signal_strength_dbm_), fmt::arg("signalStrength", signal_strength_),
fmt::arg("signalStrengthApp", signal_strength_app_), fmt::arg("ifname", ifname_), fmt::arg("signalStrengthApp", signal_strength_app_), fmt::arg("ifname", ifname_),
fmt::arg("netmask", netmask_), fmt::arg("ipaddr", ipaddr_), fmt::arg("gwaddr", gwaddr_), fmt::arg("netmask", netmask_), fmt::arg("netmask6", netmask6_),
fmt::arg("cidr", cidr_), fmt::arg("frequency", fmt::format("{:.1f}", frequency_)), fmt::arg("ipaddr", final_ipaddr_), fmt::arg("gwaddr", gwaddr_), fmt::arg("cidr", cidr_),
fmt::arg("cidr6", cidr6_), fmt::arg("frequency", fmt::format("{:.1f}", frequency_)),
fmt::arg("icon", getIcon(signal_strength_, state_)), fmt::arg("icon", getIcon(signal_strength_, state_)),
fmt::arg("bandwidthDownBits", fmt::arg("bandwidthDownBits",
pow_format(bandwidth_down * 8ull / interval_.count(), "b/s")), pow_format(bandwidth_down * 8ull / interval_.count(), "b/s")),
@ -394,10 +415,13 @@ void waybar::modules::Network::clearIface() {
essid_.clear(); essid_.clear();
bssid_.clear(); bssid_.clear();
ipaddr_.clear(); ipaddr_.clear();
ipaddr6_.clear();
gwaddr_.clear(); gwaddr_.clear();
netmask_.clear(); netmask_.clear();
netmask6_.clear();
carrier_ = false; carrier_ = false;
cidr_ = 0; cidr_ = 0;
cidr6_ = 0;
signal_strength_dbm_ = 0; signal_strength_dbm_ = 0;
signal_strength_ = 0; signal_strength_ = 0;
signal_strength_app_.clear(); signal_strength_app_.clear();
@ -521,7 +545,6 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
if (ifa->ifa_scope >= RT_SCOPE_LINK) { if (ifa->ifa_scope >= RT_SCOPE_LINK) {
return NL_OK; return NL_OK;
} }
for (; RTA_OK(ifa_rta, attrlen); ifa_rta = RTA_NEXT(ifa_rta, attrlen)) { for (; RTA_OK(ifa_rta, attrlen); ifa_rta = RTA_NEXT(ifa_rta, attrlen)) {
switch (ifa_rta->rta_type) { switch (ifa_rta->rta_type) {
case IFA_ADDRESS: case IFA_ADDRESS:
@ -529,8 +552,20 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
case IFA_LOCAL: case IFA_LOCAL:
char ipaddr[INET6_ADDRSTRLEN]; char ipaddr[INET6_ADDRSTRLEN];
if (!is_del_event) { if (!is_del_event) {
net->ipaddr_ = inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta), ipaddr, sizeof(ipaddr)); if ((net->addr_pref_ == ip_addr_pref::IPV4 ||
net->addr_pref_ == ip_addr_pref::IPV4_6) &&
net->cidr_ == 0 && ifa->ifa_family == AF_INET) {
net->ipaddr_ =
inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta), ipaddr, sizeof(ipaddr));
net->cidr_ = ifa->ifa_prefixlen; net->cidr_ = ifa->ifa_prefixlen;
} else if ((net->addr_pref_ == ip_addr_pref::IPV6 ||
net->addr_pref_ == ip_addr_pref::IPV4_6) &&
net->cidr6_ == 0 && ifa->ifa_family == AF_INET6) {
net->ipaddr6_ =
inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta), ipaddr, sizeof(ipaddr));
net->cidr6_ = ifa->ifa_prefixlen;
}
switch (ifa->ifa_family) { switch (ifa->ifa_family) {
case AF_INET: { case AF_INET: {
struct in_addr netmask; struct in_addr netmask;
@ -538,21 +573,24 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
net->netmask_ = inet_ntop(ifa->ifa_family, &netmask, ipaddr, sizeof(ipaddr)); net->netmask_ = inet_ntop(ifa->ifa_family, &netmask, ipaddr, sizeof(ipaddr));
} }
case AF_INET6: { case AF_INET6: {
struct in6_addr netmask; struct in6_addr netmask6;
for (int i = 0; i < 16; i++) { for (int i = 0; i < 16; i++) {
int v = (i + 1) * 8 - ifa->ifa_prefixlen; int v = (i + 1) * 8 - ifa->ifa_prefixlen;
if (v < 0) v = 0; if (v < 0) v = 0;
if (v > 8) v = 8; if (v > 8) v = 8;
netmask.s6_addr[i] = ~0 << v; netmask6.s6_addr[i] = ~0 << v;
} }
net->netmask_ = inet_ntop(ifa->ifa_family, &netmask, ipaddr, sizeof(ipaddr)); net->netmask6_ = inet_ntop(ifa->ifa_family, &netmask6, ipaddr, sizeof(ipaddr));
} }
} }
spdlog::debug("network: {}, new addr {}/{}", net->ifname_, net->ipaddr_, net->cidr_); spdlog::debug("network: {}, new addr {}/{}", net->ifname_, net->ipaddr_, net->cidr_);
} else { } else {
net->ipaddr_.clear(); net->ipaddr_.clear();
net->ipaddr6_.clear();
net->cidr_ = 0; net->cidr_ = 0;
net->cidr6_ = 0;
net->netmask_.clear(); net->netmask_.clear();
net->netmask6_.clear();
spdlog::debug("network: {} addr deleted {}/{}", net->ifname_, spdlog::debug("network: {} addr deleted {}/{}", net->ifname_,
inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta), ipaddr, sizeof(ipaddr)), inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta), ipaddr, sizeof(ipaddr)),
ifa->ifa_prefixlen); ifa->ifa_prefixlen);

View File

@ -9,7 +9,7 @@
namespace waybar::modules::niri { namespace waybar::modules::niri {
Language::Language(const std::string &id, const Bar &bar, const Json::Value &config) Language::Language(const std::string &id, const Bar &bar, const Json::Value &config)
: ALabel(config, "language", id, "{}", 0, true), bar_(bar) { : ALabel(config, "language", id, "{}", 0, false), bar_(bar) {
label_.hide(); label_.hide();
if (!gIPC) gIPC = std::make_unique<IPC>(); if (!gIPC) gIPC = std::make_unique<IPC>();

View File

@ -166,6 +166,8 @@ std::string Workspaces::getIcon(const std::string &value, const Json::Value &ws)
const auto &icons = config_["format-icons"]; const auto &icons = config_["format-icons"];
if (!icons) return value; if (!icons) return value;
if (ws["active_window_id"].isNull() && icons["empty"]) return icons["empty"].asString();
if (ws["is_focused"].asBool() && icons["focused"]) return icons["focused"].asString(); if (ws["is_focused"].asBool() && icons["focused"]) return icons["focused"].asString();
if (ws["is_active"].asBool() && icons["active"]) return icons["active"].asString(); if (ws["is_active"].asBool() && icons["active"]) return icons["active"].asString();

View File

@ -29,14 +29,14 @@ PowerProfilesDaemon::PowerProfilesDaemon(const std::string& id, const Json::Valu
// method on the proxy to see whether or not something's responding // method on the proxy to see whether or not something's responding
// on the other side. // on the other side.
// NOTE: the DBus adresses are under migration. They should be // NOTE: the DBus addresses are under migration. They should be
// changed to org.freedesktop.UPower.PowerProfiles at some point. // changed to org.freedesktop.UPower.PowerProfiles at some point.
// //
// See // See
// https://gitlab.freedesktop.org/upower/power-profiles-daemon/-/releases/0.20 // https://gitlab.freedesktop.org/upower/power-profiles-daemon/-/releases/0.20
// //
// The old name is still announced for now. Let's rather use the old // The old name is still announced for now. Let's rather use the old
// adresses for compatibility sake. // addresses for compatibility sake.
// //
// Revisit this in 2026, systems should be updated by then. // Revisit this in 2026, systems should be updated by then.

View File

@ -15,13 +15,14 @@ using util::PipewireBackend::PRIVACY_NODE_TYPE_AUDIO_OUTPUT;
using util::PipewireBackend::PRIVACY_NODE_TYPE_NONE; using util::PipewireBackend::PRIVACY_NODE_TYPE_NONE;
using util::PipewireBackend::PRIVACY_NODE_TYPE_VIDEO_INPUT; using util::PipewireBackend::PRIVACY_NODE_TYPE_VIDEO_INPUT;
Privacy::Privacy(const std::string& id, const Json::Value& config, const std::string& pos) Privacy::Privacy(const std::string& id, const Json::Value& config, Gtk::Orientation orientation,
const std::string& pos)
: AModule(config, "privacy", id), : AModule(config, "privacy", id),
nodes_screenshare(), nodes_screenshare(),
nodes_audio_in(), nodes_audio_in(),
nodes_audio_out(), nodes_audio_out(),
visibility_conn(), visibility_conn(),
box_(Gtk::ORIENTATION_HORIZONTAL, 0) { box_(orientation, 0) {
box_.set_name(name_); box_.set_name(name_);
event_box_.add(box_); event_box_.add(box_);
@ -67,12 +68,30 @@ Privacy::Privacy(const std::string& id, const Json::Value& config, const std::st
auto iter = typeMap.find(type); auto iter = typeMap.find(type);
if (iter != typeMap.end()) { if (iter != typeMap.end()) {
auto& [nodePtr, nodeType] = iter->second; auto& [nodePtr, nodeType] = iter->second;
auto* item = Gtk::make_managed<PrivacyItem>(module, nodeType, nodePtr, pos, iconSize, auto* item = Gtk::make_managed<PrivacyItem>(module, nodeType, nodePtr, orientation, pos,
transition_duration); iconSize, transition_duration);
box_.add(*item); box_.add(*item);
} }
} }
for (const auto& ignore_item : config_["ignore"]) {
if (!ignore_item.isObject() || !ignore_item["type"].isString() ||
!ignore_item["name"].isString())
continue;
const std::string type = ignore_item["type"].asString();
const std::string name = ignore_item["name"].asString();
auto iter = typeMap.find(type);
if (iter != typeMap.end()) {
auto& [_, nodeType] = iter->second;
ignore.emplace(nodeType, std::move(name));
}
}
if (config_["ignore-monitor"].isBool()) {
ignore_monitor = config_["ignore-monitor"].asBool();
}
backend = util::PipewireBackend::PipewireBackend::getInstance(); backend = util::PipewireBackend::PipewireBackend::getInstance();
backend->privacy_nodes_changed_signal_event.connect( backend->privacy_nodes_changed_signal_event.connect(
sigc::mem_fun(*this, &Privacy::onPrivacyNodesChanged)); sigc::mem_fun(*this, &Privacy::onPrivacyNodesChanged));
@ -87,6 +106,11 @@ void Privacy::onPrivacyNodesChanged() {
nodes_screenshare.clear(); nodes_screenshare.clear();
for (auto& node : backend->privacy_nodes) { for (auto& node : backend->privacy_nodes) {
if (ignore_monitor && node.second->is_monitor) continue;
auto iter = ignore.find(std::pair(node.second->type, node.second->node_name));
if (iter != ignore.end()) continue;
switch (node.second->state) { switch (node.second->state) {
case PW_NODE_STATE_RUNNING: case PW_NODE_STATE_RUNNING:
switch (node.second->type) { switch (node.second->type) {

View File

@ -1,5 +1,7 @@
#include "modules/privacy/privacy_item.hpp" #include "modules/privacy/privacy_item.hpp"
#include <spdlog/spdlog.h>
#include <string> #include <string>
#include "glibmm/main.h" #include "glibmm/main.h"
@ -11,8 +13,9 @@
namespace waybar::modules::privacy { namespace waybar::modules::privacy {
PrivacyItem::PrivacyItem(const Json::Value &config_, enum PrivacyNodeType privacy_type_, PrivacyItem::PrivacyItem(const Json::Value &config_, enum PrivacyNodeType privacy_type_,
std::list<PrivacyNodeInfo *> *nodes_, const std::string &pos, std::list<PrivacyNodeInfo *> *nodes_, Gtk::Orientation orientation,
const uint icon_size, const uint transition_duration) const std::string &pos, const uint icon_size,
const uint transition_duration)
: Gtk::Revealer(), : Gtk::Revealer(),
privacy_type(privacy_type_), privacy_type(privacy_type_),
nodes(nodes_), nodes(nodes_),
@ -40,16 +43,24 @@ PrivacyItem::PrivacyItem(const Json::Value &config_, enum PrivacyNodeType privac
// Set the reveal transition to not look weird when sliding in // Set the reveal transition to not look weird when sliding in
if (pos == "modules-left") { if (pos == "modules-left") {
set_transition_type(Gtk::REVEALER_TRANSITION_TYPE_SLIDE_RIGHT); set_transition_type(orientation == Gtk::ORIENTATION_HORIZONTAL
? Gtk::REVEALER_TRANSITION_TYPE_SLIDE_RIGHT
: Gtk::REVEALER_TRANSITION_TYPE_SLIDE_DOWN);
} else if (pos == "modules-center") { } else if (pos == "modules-center") {
set_transition_type(Gtk::REVEALER_TRANSITION_TYPE_CROSSFADE); set_transition_type(Gtk::REVEALER_TRANSITION_TYPE_CROSSFADE);
} else if (pos == "modules-right") { } else if (pos == "modules-right") {
set_transition_type(Gtk::REVEALER_TRANSITION_TYPE_SLIDE_LEFT); set_transition_type(orientation == Gtk::ORIENTATION_HORIZONTAL
? Gtk::REVEALER_TRANSITION_TYPE_SLIDE_LEFT
: Gtk::REVEALER_TRANSITION_TYPE_SLIDE_UP);
} }
set_transition_duration(transition_duration); set_transition_duration(transition_duration);
box_.set_name("privacy-item"); box_.set_name("privacy-item");
box_.add(icon_);
// We use `set_center_widget` instead of `add` to make sure the icon is
// centered even if the orientation is vertical
box_.set_center_widget(icon_);
icon_.set_pixel_size(icon_size); icon_.set_pixel_size(icon_size);
add(box_); add(box_);
@ -87,20 +98,22 @@ PrivacyItem::PrivacyItem(const Json::Value &config_, enum PrivacyNodeType privac
void PrivacyItem::update_tooltip() { void PrivacyItem::update_tooltip() {
// Removes all old nodes // Removes all old nodes
for (auto *child : tooltip_window.get_children()) { for (auto *child : tooltip_window.get_children()) {
tooltip_window.remove(*child);
// despite the remove, still needs a delete to prevent memory leak. Speculating that this might
// work differently in GTK4.
delete child; delete child;
} }
for (auto *node : *nodes) { for (auto *node : *nodes) {
Gtk::Box *box = new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 4); auto *box = Gtk::make_managed<Gtk::Box>(Gtk::ORIENTATION_HORIZONTAL, 4);
// Set device icon // Set device icon
Gtk::Image *node_icon = new Gtk::Image(); auto *node_icon = Gtk::make_managed<Gtk::Image>();
node_icon->set_pixel_size(tooltipIconSize); node_icon->set_pixel_size(tooltipIconSize);
node_icon->set_from_icon_name(node->getIconName(), Gtk::ICON_SIZE_INVALID); node_icon->set_from_icon_name(node->getIconName(), Gtk::ICON_SIZE_INVALID);
box->add(*node_icon); box->add(*node_icon);
// Set model // Set model
auto *nodeName = new Gtk::Label(node->getName()); auto *nodeName = Gtk::make_managed<Gtk::Label>(node->getName());
box->add(*nodeName); box->add(*nodeName);
tooltip_window.add(*box); tooltip_window.add(*box);

View File

@ -189,10 +189,20 @@ bool Tags::handle_button_press(GdkEventButton *event_button, uint32_t tag) {
} }
void Tags::handle_focused_tags(uint32_t tags) { void Tags::handle_focused_tags(uint32_t tags) {
auto hide_vacant = config_["hide-vacant"].asBool();
for (size_t i = 0; i < buttons_.size(); ++i) { for (size_t i = 0; i < buttons_.size(); ++i) {
bool visible = buttons_[i].is_visible();
bool occupied = buttons_[i].get_style_context()->has_class("occupied");
bool urgent = buttons_[i].get_style_context()->has_class("urgent");
if ((1 << i) & tags) { if ((1 << i) & tags) {
if (hide_vacant && !visible) {
buttons_[i].set_visible(true);
}
buttons_[i].get_style_context()->add_class("focused"); buttons_[i].get_style_context()->add_class("focused");
} else { } else {
if (hide_vacant && !(occupied || urgent)) {
buttons_[i].set_visible(false);
}
buttons_[i].get_style_context()->remove_class("focused"); buttons_[i].get_style_context()->remove_class("focused");
} }
} }
@ -205,20 +215,40 @@ void Tags::handle_view_tags(struct wl_array *view_tags) {
for (; view_tag < end; ++view_tag) { for (; view_tag < end; ++view_tag) {
tags |= *view_tag; tags |= *view_tag;
} }
auto hide_vacant = config_["hide-vacant"].asBool();
for (size_t i = 0; i < buttons_.size(); ++i) { for (size_t i = 0; i < buttons_.size(); ++i) {
bool visible = buttons_[i].is_visible();
bool focused = buttons_[i].get_style_context()->has_class("focused");
bool urgent = buttons_[i].get_style_context()->has_class("urgent");
if ((1 << i) & tags) { if ((1 << i) & tags) {
if (hide_vacant && !visible) {
buttons_[i].set_visible(true);
}
buttons_[i].get_style_context()->add_class("occupied"); buttons_[i].get_style_context()->add_class("occupied");
} else { } else {
if (hide_vacant && !(focused || urgent)) {
buttons_[i].set_visible(false);
}
buttons_[i].get_style_context()->remove_class("occupied"); buttons_[i].get_style_context()->remove_class("occupied");
} }
} }
} }
void Tags::handle_urgent_tags(uint32_t tags) { void Tags::handle_urgent_tags(uint32_t tags) {
auto hide_vacant = config_["hide-vacant"].asBool();
for (size_t i = 0; i < buttons_.size(); ++i) { for (size_t i = 0; i < buttons_.size(); ++i) {
bool visible = buttons_[i].is_visible();
bool occupied = buttons_[i].get_style_context()->has_class("occupied");
bool focused = buttons_[i].get_style_context()->has_class("focused");
if ((1 << i) & tags) { if ((1 << i) & tags) {
if (hide_vacant && !visible) {
buttons_[i].set_visible(true);
}
buttons_[i].get_style_context()->add_class("urgent"); buttons_[i].get_style_context()->add_class("urgent");
} else { } else {
if (hide_vacant && !(occupied || focused)) {
buttons_[i].set_visible(false);
}
buttons_[i].get_style_context()->remove_class("urgent"); buttons_[i].get_style_context()->remove_class("urgent");
} }
} }

View File

@ -5,10 +5,12 @@
#include <gtkmm/tooltip.h> #include <gtkmm/tooltip.h>
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
#include <filesystem>
#include <fstream> #include <fstream>
#include <map> #include <map>
#include "gdk/gdk.h" #include "gdk/gdk.h"
#include "modules/sni/icon_manager.hpp"
#include "util/format.hpp" #include "util/format.hpp"
#include "util/gtk_icon.hpp" #include "util/gtk_icon.hpp"
@ -124,7 +126,8 @@ 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); auto escapedDescription = Glib::Markup::escape_text(description);
result.text = fmt::format("<b>{}</b>\n{}", result.text, escapedDescription);
} }
return result; return result;
} }
@ -137,6 +140,7 @@ void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) {
category = get_variant<std::string>(value); category = get_variant<std::string>(value);
} else if (name == "Id") { } else if (name == "Id") {
id = get_variant<std::string>(value); id = get_variant<std::string>(value);
setCustomIcon(id);
} else if (name == "Title") { } else if (name == "Title") {
title = get_variant<std::string>(value); title = get_variant<std::string>(value);
if (tooltip.text.empty()) { if (tooltip.text.empty()) {
@ -198,6 +202,19 @@ void Item::setStatus(const Glib::ustring& value) {
style->add_class(lower); style->add_class(lower);
} }
void Item::setCustomIcon(const std::string& id) {
std::string custom_icon = IconManager::instance().getIconForApp(id);
if (!custom_icon.empty()) {
if (std::filesystem::exists(custom_icon)) {
Glib::RefPtr<Gdk::Pixbuf> custom_pixbuf = Gdk::Pixbuf::create_from_file(custom_icon);
icon_name = ""; // icon_name has priority over pixmap
icon_pixmap = custom_pixbuf;
} else { // if file doesn't exist it's most likely an icon_name
icon_name = custom_icon;
}
}
}
void Item::getUpdatedProperties() { void Item::getUpdatedProperties() {
auto params = Glib::VariantContainerBase::create_tuple( auto params = Glib::VariantContainerBase::create_tuple(
{Glib::Variant<Glib::ustring>::create(SNI_INTERFACE_NAME)}); {Glib::Variant<Glib::ustring>::create(SNI_INTERFACE_NAME)});
@ -371,14 +388,17 @@ Glib::RefPtr<Gdk::Pixbuf> Item::getIconPixbuf() {
Glib::RefPtr<Gdk::Pixbuf> Item::getIconByName(const std::string& name, int request_size) { Glib::RefPtr<Gdk::Pixbuf> Item::getIconByName(const std::string& name, int request_size) {
icon_theme->rescan_if_needed(); icon_theme->rescan_if_needed();
if (!icon_theme_path.empty() && if (!icon_theme_path.empty()) {
icon_theme->lookup_icon(name.c_str(), request_size, auto icon_info = icon_theme->lookup_icon(name.c_str(), request_size,
Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE)) {
return icon_theme->load_icon(name.c_str(), request_size,
Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE); Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE);
if (icon_info) {
bool is_sym = false;
return icon_info.load_symbolic(event_box.get_style_context(), is_sym);
}
} }
return DefaultGtkIconThemeWrapper::load_icon(name.c_str(), request_size, return DefaultGtkIconThemeWrapper::load_icon(name.c_str(), request_size,
Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE); Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE,
event_box.get_style_context());
} }
double Item::getScaledIconSize() { double Item::getScaledIconSize() {

View File

@ -2,6 +2,8 @@
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
#include "modules/sni/icon_manager.hpp"
namespace waybar::modules::SNI { namespace waybar::modules::SNI {
Tray::Tray(const std::string& id, const Bar& bar, const Json::Value& config) Tray::Tray(const std::string& id, const Bar& bar, const Json::Value& config)
@ -20,6 +22,9 @@ Tray::Tray(const std::string& id, const Bar& bar, const Json::Value& config)
box_.set_spacing(config_["spacing"].asUInt()); box_.set_spacing(config_["spacing"].asUInt());
} }
nb_hosts_ += 1; nb_hosts_ += 1;
if (config_["icons"].isObject()) {
IconManager::instance().setIconsConfig(config_["icons"]);
}
dp.emit(); dp.emit();
} }

View File

@ -22,10 +22,10 @@ Language::Language(const std::string& id, const Json::Value& config)
hide_single_ = config["hide-single-layout"].isBool() && config["hide-single-layout"].asBool(); hide_single_ = config["hide-single-layout"].isBool() && config["hide-single-layout"].asBool();
is_variant_displayed = format_.find("{variant}") != std::string::npos; is_variant_displayed = format_.find("{variant}") != std::string::npos;
if (format_.find("{}") != std::string::npos || format_.find("{short}") != std::string::npos) { if (format_.find("{}") != std::string::npos || format_.find("{short}") != std::string::npos) {
displayed_short_flag |= static_cast<std::byte>(DispayedShortFlag::ShortName); displayed_short_flag |= static_cast<std::byte>(DisplayedShortFlag::ShortName);
} }
if (format_.find("{shortDescription}") != std::string::npos) { if (format_.find("{shortDescription}") != std::string::npos) {
displayed_short_flag |= static_cast<std::byte>(DispayedShortFlag::ShortDescription); displayed_short_flag |= static_cast<std::byte>(DisplayedShortFlag::ShortDescription);
} }
if (config.isMember("tooltip-format")) { if (config.isMember("tooltip-format")) {
tooltip_format_ = config["tooltip-format"].asString(); tooltip_format_ = config["tooltip-format"].asString();

View File

@ -41,8 +41,8 @@ void Window::onCmd(const struct Ipc::ipc_response& res) {
std::lock_guard<std::mutex> lock(mutex_); std::lock_guard<std::mutex> lock(mutex_);
auto payload = parser_.parse(res.payload); auto payload = parser_.parse(res.payload);
auto output = payload["output"].isString() ? payload["output"].asString() : ""; auto output = payload["output"].isString() ? payload["output"].asString() : "";
std::tie(app_nb_, floating_count_, windowId_, window_, app_id_, app_class_, shell_, layout_) = std::tie(app_nb_, floating_count_, windowId_, window_, app_id_, app_class_, shell_, layout_,
getFocusedNode(payload["nodes"], output); marks_) = getFocusedNode(payload["nodes"], output);
updateAppIconName(app_id_, app_class_); updateAppIconName(app_id_, app_class_);
dp.emit(); dp.emit();
} catch (const std::exception& e) { } catch (const std::exception& e) {
@ -96,7 +96,7 @@ auto Window::update() -> void {
label_.set_markup(waybar::util::rewriteString( label_.set_markup(waybar::util::rewriteString(
fmt::format(fmt::runtime(format_), fmt::arg("title", window_), fmt::arg("app_id", app_id_), fmt::format(fmt::runtime(format_), fmt::arg("title", window_), fmt::arg("app_id", app_id_),
fmt::arg("shell", shell_)), fmt::arg("shell", shell_), fmt::arg("marks", marks_)),
config_["rewrite"])); config_["rewrite"]));
if (tooltipEnabled()) { if (tooltipEnabled()) {
label_.set_tooltip_text(window_); label_.set_tooltip_text(window_);
@ -108,7 +108,7 @@ auto Window::update() -> void {
AAppIconLabel::update(); AAppIconLabel::update();
} }
void Window::setClass(std::string classname, bool enable) { void Window::setClass(const std::string& classname, bool enable) {
if (enable) { if (enable) {
if (!bar_.window.get_style_context()->has_class(classname)) { if (!bar_.window.get_style_context()->has_class(classname)) {
bar_.window.get_style_context()->add_class(classname); bar_.window.get_style_context()->add_class(classname);
@ -169,17 +169,31 @@ std::optional<std::reference_wrapper<const Json::Value>> getSingleChildNode(
return {getSingleChildNode(child)}; return {getSingleChildNode(child)};
} }
std::tuple<std::string, std::string, std::string> getWindowInfo(const Json::Value& node) { std::tuple<std::string, std::string, std::string, std::string> getWindowInfo(
const Json::Value& node, bool showHidden) {
const auto app_id = node["app_id"].isString() ? node["app_id"].asString() const auto app_id = node["app_id"].isString() ? node["app_id"].asString()
: node["window_properties"]["instance"].asString(); : node["window_properties"]["instance"].asString();
const auto app_class = node["window_properties"]["class"].isString() const auto app_class = node["window_properties"]["class"].isString()
? node["window_properties"]["class"].asString() ? node["window_properties"]["class"].asString()
: ""; : "";
const auto shell = node["shell"].isString() ? node["shell"].asString() : ""; const auto shell = node["shell"].isString() ? node["shell"].asString() : "";
return {app_id, app_class, shell}; std::string marks = "";
if (node["marks"].isArray()) {
for (const auto& m : node["marks"]) {
if (!m.isString() || (!showHidden && m.asString().at(0) == '_')) {
continue;
}
if (!marks.empty()) {
marks += ',';
}
marks += m.asString();
}
}
return {app_id, app_class, shell, marks};
} }
std::tuple<std::size_t, int, int, std::string, std::string, std::string, std::string, std::string> std::tuple<std::size_t, int, int, std::string, std::string, std::string, std::string, std::string,
std::string>
gfnWithWorkspace(const Json::Value& nodes, std::string& output, const Json::Value& config_, gfnWithWorkspace(const Json::Value& nodes, std::string& output, const Json::Value& config_,
const Bar& bar_, Json::Value& parentWorkspace, const Bar& bar_, Json::Value& parentWorkspace,
const Json::Value& immediateParent) { const Json::Value& immediateParent) {
@ -207,7 +221,8 @@ gfnWithWorkspace(const Json::Value& nodes, std::string& output, const Json::Valu
"", "",
"", "",
"", "",
node["layout"].asString()}; node["layout"].asString(),
""};
} }
parentWorkspace = node; parentWorkspace = node;
} else if ((node["type"].asString() == "con" || node["type"].asString() == "floating_con") && } else if ((node["type"].asString() == "con" || node["type"].asString() == "floating_con") &&
@ -215,7 +230,8 @@ gfnWithWorkspace(const Json::Value& nodes, std::string& output, const Json::Valu
// found node // found node
spdlog::trace("actual output {}, output found {}, node (focused) found {}", bar_.output->name, spdlog::trace("actual output {}, output found {}, node (focused) found {}", bar_.output->name,
output, node["name"].asString()); output, node["name"].asString());
const auto [app_id, app_class, shell] = getWindowInfo(node); const auto [app_id, app_class, shell, marks] =
getWindowInfo(node, config_["show-hidden-marks"].asBool());
int nb = node.size(); int nb = node.size();
int floating_count = 0; int floating_count = 0;
std::string workspace_layout = ""; std::string workspace_layout = "";
@ -232,20 +248,21 @@ gfnWithWorkspace(const Json::Value& nodes, std::string& output, const Json::Valu
app_id, app_id,
app_class, app_class,
shell, shell,
workspace_layout}; workspace_layout,
marks};
} }
// iterate // iterate
auto [nb, f, id, name, app_id, app_class, shell, workspace_layout] = auto [nb, f, id, name, app_id, app_class, shell, workspace_layout, marks] =
gfnWithWorkspace(node["nodes"], output, config_, bar_, parentWorkspace, node); gfnWithWorkspace(node["nodes"], output, config_, bar_, parentWorkspace, node);
auto [nb2, f2, id2, name2, app_id2, app_class2, shell2, workspace_layout2] = auto [nb2, f2, id2, name2, app_id2, app_class2, shell2, workspace_layout2, marks2] =
gfnWithWorkspace(node["floating_nodes"], output, config_, bar_, parentWorkspace, node); gfnWithWorkspace(node["floating_nodes"], output, config_, bar_, parentWorkspace, node);
// if ((id > 0 || ((id2 < 0 || name2.empty()) && id > -1)) && !name.empty()) { // if ((id > 0 || ((id2 < 0 || name2.empty()) && id > -1)) && !name.empty()) {
if ((id > 0) || (id2 < 0 && id > -1)) { if ((id > 0) || (id2 < 0 && id > -1)) {
return {nb, f, id, name, app_id, app_class, shell, workspace_layout}; return {nb, f, id, name, app_id, app_class, shell, workspace_layout, marks};
} else if (id2 > 0 && !name2.empty()) { } else if (id2 > 0 && !name2.empty()) {
return {nb2, f2, id2, name2, app_id2, app_class, shell2, workspace_layout2}; return {nb2, f2, id2, name2, app_id2, app_class, shell2, workspace_layout2, marks2};
} }
} }
@ -258,10 +275,12 @@ gfnWithWorkspace(const Json::Value& nodes, std::string& output, const Json::Valu
std::string app_id = ""; std::string app_id = "";
std::string app_class = ""; std::string app_class = "";
std::string workspace_layout = ""; std::string workspace_layout = "";
std::string marks = "";
if (all_leaf_nodes.first == 1) { if (all_leaf_nodes.first == 1) {
const auto single_child = getSingleChildNode(immediateParent); const auto single_child = getSingleChildNode(immediateParent);
if (single_child.has_value()) { if (single_child.has_value()) {
std::tie(app_id, app_class, workspace_layout) = getWindowInfo(single_child.value()); std::tie(app_id, app_class, workspace_layout, marks) =
getWindowInfo(single_child.value(), config_["show-hidden-marks"].asBool());
} }
} }
return {all_leaf_nodes.first, return {all_leaf_nodes.first,
@ -273,13 +292,15 @@ gfnWithWorkspace(const Json::Value& nodes, std::string& output, const Json::Valu
app_id, app_id,
app_class, app_class,
workspace_layout, workspace_layout,
immediateParent["layout"].asString()}; immediateParent["layout"].asString(),
marks};
} }
return {0, 0, -1, "", "", "", "", ""}; return {0, 0, -1, "", "", "", "", "", ""};
} }
std::tuple<std::size_t, int, int, std::string, std::string, std::string, std::string, std::string> std::tuple<std::size_t, int, int, std::string, std::string, std::string, std::string, std::string,
std::string>
Window::getFocusedNode(const Json::Value& nodes, std::string& output) { Window::getFocusedNode(const Json::Value& nodes, std::string& output) {
Json::Value placeholder = Json::Value::null; Json::Value placeholder = Json::Value::null;
return gfnWithWorkspace(nodes, output, config_, bar_, placeholder, placeholder); return gfnWithWorkspace(nodes, output, config_, bar_, placeholder, placeholder);

View File

@ -57,9 +57,9 @@ Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value
box_.get_style_context()->add_class(MODULE_CLASS); box_.get_style_context()->add_class(MODULE_CLASS);
event_box_.add(box_); event_box_.add(box_);
if (config_["format-window-separator"].isString()) { if (config_["format-window-separator"].isString()) {
m_formatWindowSeperator = config_["format-window-separator"].asString(); m_formatWindowSeparator = config_["format-window-separator"].asString();
} else { } else {
m_formatWindowSeperator = " "; m_formatWindowSeparator = " ";
} }
const Json::Value &windowRewrite = config["window-rewrite"]; const Json::Value &windowRewrite = config["window-rewrite"];
if (windowRewrite.isObject()) { if (windowRewrite.isObject()) {
@ -271,7 +271,7 @@ void Workspaces::updateWindows(const Json::Value &node, std::string &windows) {
window = fmt::format(fmt::runtime(window), fmt::arg("name", title), window = fmt::format(fmt::runtime(window), fmt::arg("name", title),
fmt::arg("class", windowClass)); fmt::arg("class", windowClass));
windows.append(window); windows.append(window);
windows.append(m_formatWindowSeperator); windows.append(m_formatWindowSeparator);
} }
} }
for (const Json::Value &child : node["nodes"]) { for (const Json::Value &child : node["nodes"]) {
@ -340,7 +340,7 @@ auto Workspaces::update() -> void {
fmt::runtime(format), fmt::arg("icon", getIcon(output, *it)), fmt::arg("value", output), fmt::runtime(format), fmt::arg("icon", getIcon(output, *it)), fmt::arg("value", output),
fmt::arg("name", trimWorkspaceName(output)), fmt::arg("index", (*it)["num"].asString()), fmt::arg("name", trimWorkspaceName(output)), fmt::arg("index", (*it)["num"].asString()),
fmt::arg("windows", fmt::arg("windows",
windows.substr(0, windows.length() - m_formatWindowSeperator.length())), windows.substr(0, windows.length() - m_formatWindowSeparator.length())),
fmt::arg("output", (*it)["output"].asString())); fmt::arg("output", (*it)["output"].asString()));
} }
if (!config_["disable-markup"].asBool()) { if (!config_["disable-markup"].asBool()) {
@ -494,16 +494,34 @@ std::string Workspaces::trimWorkspaceName(std::string name) {
return name; return name;
} }
void Workspaces::onButtonReady(const Json::Value &node, Gtk::Button &button) { bool is_focused_recursive(const Json::Value &node) {
if (config_["current-only"].asBool()) {
// If a workspace has a focused container then get_tree will say // If a workspace has a focused container then get_tree will say
// that the workspace itself isn't focused. Therefore we need to // that the workspace itself isn't focused. Therefore we need to
// check if any of its nodes are focused as well. // check if any of its nodes are focused as well.
bool focused = node["focused"].asBool() || // some layouts like tabbed have many nested nodes
std::any_of(node["nodes"].begin(), node["nodes"].end(), // all nested nodes must be checked for focused flag
[](const auto &child) { return child["focused"].asBool(); }); if (node["focused"].asBool()) {
return true;
}
if (focused) { for (const auto &child : node["nodes"]) {
if (is_focused_recursive(child)) {
return true;
}
}
for (const auto &child : node["floating_nodes"]) {
if (is_focused_recursive(child)) {
return true;
}
}
return false;
}
void Workspaces::onButtonReady(const Json::Value &node, Gtk::Button &button) {
if (config_["current-only"].asBool()) {
if (is_focused_recursive(node)) {
button.show(); button.show();
} else { } else {
button.hide(); button.hide();

View File

@ -16,6 +16,7 @@ SystemdFailedUnits::SystemdFailedUnits(const std::string& id, const Json::Value&
update_pending(false), update_pending(false),
nr_failed_system(0), nr_failed_system(0),
nr_failed_user(0), nr_failed_user(0),
nr_failed(0),
last_status() { last_status() {
if (config["hide-on-ok"].isBool()) { if (config["hide-on-ok"].isBool()) {
hide_on_ok = config["hide-on-ok"].asBool(); hide_on_ok = config["hide-on-ok"].asBool();
@ -67,11 +68,38 @@ auto SystemdFailedUnits::notify_cb(const Glib::ustring& sender_name,
} }
} }
void SystemdFailedUnits::updateData() { void SystemdFailedUnits::RequestSystemState() {
update_pending = false; auto load = [](const char* kind, Glib::RefPtr<Gio::DBus::Proxy>& proxy) -> std::string {
try {
if (!proxy) return "unknown";
auto parameters = Glib::VariantContainerBase(
g_variant_new("(ss)", "org.freedesktop.systemd1.Manager", "SystemState"));
Glib::VariantContainerBase data = proxy->call_sync("Get", parameters);
if (data && data.is_of_type(Glib::VariantType("(v)"))) {
Glib::VariantBase variant;
g_variant_get(data.gobj_copy(), "(v)", &variant);
if (variant && variant.is_of_type(Glib::VARIANT_TYPE_STRING)) {
return g_variant_get_string(variant.gobj_copy(), NULL);
}
}
} catch (Glib::Error& e) {
spdlog::error("Failed to get {} state: {}", kind, e.what().c_str());
}
return "unknown";
};
system_state = load("systemwide", system_proxy);
user_state = load("user", user_proxy);
if (system_state == "running" && user_state == "running")
overall_state = "ok";
else
overall_state = "degraded";
}
void SystemdFailedUnits::RequestFailedUnits() {
auto load = [](const char* kind, Glib::RefPtr<Gio::DBus::Proxy>& proxy) -> uint32_t { auto load = [](const char* kind, Glib::RefPtr<Gio::DBus::Proxy>& proxy) -> uint32_t {
try { try {
if (!proxy) return 0;
auto parameters = Glib::VariantContainerBase( auto parameters = Glib::VariantContainerBase(
g_variant_new("(ss)", "org.freedesktop.systemd1.Manager", "NFailedUnits")); g_variant_new("(ss)", "org.freedesktop.systemd1.Manager", "NFailedUnits"));
Glib::VariantContainerBase data = proxy->call_sync("Get", parameters); Glib::VariantContainerBase data = proxy->call_sync("Get", parameters);
@ -79,9 +107,7 @@ void SystemdFailedUnits::updateData() {
Glib::VariantBase variant; Glib::VariantBase variant;
g_variant_get(data.gobj_copy(), "(v)", &variant); g_variant_get(data.gobj_copy(), "(v)", &variant);
if (variant && variant.is_of_type(Glib::VARIANT_TYPE_UINT32)) { if (variant && variant.is_of_type(Glib::VARIANT_TYPE_UINT32)) {
uint32_t value = 0; return g_variant_get_uint32(variant.gobj_copy());
g_variant_get(variant.gobj_copy(), "u", &value);
return value;
} }
} }
} catch (Glib::Error& e) { } catch (Glib::Error& e) {
@ -90,40 +116,46 @@ void SystemdFailedUnits::updateData() {
return 0; return 0;
}; };
if (system_proxy) {
nr_failed_system = load("systemwide", system_proxy); nr_failed_system = load("systemwide", system_proxy);
}
if (user_proxy) {
nr_failed_user = load("user", user_proxy); nr_failed_user = load("user", user_proxy);
} nr_failed = nr_failed_system + nr_failed_user;
}
void SystemdFailedUnits::updateData() {
update_pending = false;
RequestSystemState();
if (overall_state == "degraded") RequestFailedUnits();
dp.emit(); dp.emit();
} }
auto SystemdFailedUnits::update() -> void { auto SystemdFailedUnits::update() -> void {
uint32_t nr_failed = nr_failed_system + nr_failed_user; if (last_status == overall_state) return;
// Hide if needed. // Hide if needed.
if (nr_failed == 0 && hide_on_ok) { if (overall_state == "ok" && hide_on_ok) {
event_box_.set_visible(false); event_box_.set_visible(false);
return; return;
} }
if (!event_box_.get_visible()) {
event_box_.set_visible(true); event_box_.set_visible(true);
}
// Set state class. // Set state class.
const std::string status = nr_failed == 0 ? "ok" : "degraded";
if (!last_status.empty() && label_.get_style_context()->has_class(last_status)) { if (!last_status.empty() && label_.get_style_context()->has_class(last_status)) {
label_.get_style_context()->remove_class(last_status); label_.get_style_context()->remove_class(last_status);
} }
if (!label_.get_style_context()->has_class(status)) { if (!label_.get_style_context()->has_class(overall_state)) {
label_.get_style_context()->add_class(status); label_.get_style_context()->add_class(overall_state);
} }
last_status = status;
last_status = overall_state;
label_.set_markup(fmt::format( label_.set_markup(fmt::format(
fmt::runtime(nr_failed == 0 ? format_ok : format_), fmt::arg("nr_failed", nr_failed), fmt::runtime(nr_failed == 0 ? format_ok : format_), fmt::arg("nr_failed", nr_failed),
fmt::arg("nr_failed_system", nr_failed_system), fmt::arg("nr_failed_user", nr_failed_user))); fmt::arg("nr_failed_system", nr_failed_system), fmt::arg("nr_failed_user", nr_failed_user),
fmt::arg("system_state", system_state), fmt::arg("user_state", user_state),
fmt::arg("overall_state", overall_state)));
ALabel::update(); ALabel::update();
} }

View File

@ -45,7 +45,7 @@ waybar::modules::Temperature::Temperature(const std::string& id, const Json::Val
file_path_ = fmt::format("/sys/class/thermal/thermal_zone{}/temp", zone); file_path_ = fmt::format("/sys/class/thermal/thermal_zone{}/temp", zone);
} }
// check if file_path_ can be used to retrive the temperature // check if file_path_ can be used to retrieve the temperature
std::ifstream temp(file_path_); std::ifstream temp(file_path_);
if (!temp.is_open()) { if (!temp.is_open()) {
throw std::runtime_error("Can't open " + file_path_); throw std::runtime_error("Can't open " + file_path_);
@ -114,13 +114,17 @@ float waybar::modules::Temperature::getTemperature() {
auto zone = config_["thermal-zone"].isInt() ? config_["thermal-zone"].asInt() : 0; auto zone = config_["thermal-zone"].isInt() ? config_["thermal-zone"].asInt() : 0;
if (sysctlbyname(fmt::format("hw.acpi.thermal.tz{}.temperature", zone).c_str(), &temp, &size, // First, try with dev.cpu
NULL, 0) != 0) { if ((sysctlbyname(fmt::format("dev.cpu.{}.temperature", zone).c_str(), &temp, &size, NULL, 0) ==
throw std::runtime_error(fmt::format( 0) ||
"sysctl hw.acpi.thermal.tz{}.temperature or dev.cpu.{}.temperature failed", zone, zone)); (sysctlbyname(fmt::format("hw.acpi.thermal.tz{}.temperature", zone).c_str(), &temp, &size,
} NULL, 0) == 0)) {
auto temperature_c = ((float)temp - 2732) / 10; auto temperature_c = ((float)temp - 2732) / 10;
return temperature_c; return temperature_c;
}
throw std::runtime_error(fmt::format(
"sysctl hw.acpi.thermal.tz{}.temperature and dev.cpu.{}.temperature failed", zone, zone));
#else // Linux #else // Linux
std::ifstream temp(file_path_); std::ifstream temp(file_path_);

View File

@ -358,10 +358,12 @@ void UPower::resetDevices() {
void UPower::setDisplayDevice() { void UPower::setDisplayDevice() {
std::lock_guard<std::mutex> guard{mutex_}; std::lock_guard<std::mutex> guard{mutex_};
if (nativePath_.empty() && model_.empty()) { if (upDevice_.upDevice != NULL) {
// Unref current upDevice g_object_unref(upDevice_.upDevice);
if (upDevice_.upDevice != NULL) g_object_unref(upDevice_.upDevice); upDevice_.upDevice = NULL;
}
if (nativePath_.empty() && model_.empty()) {
upDevice_.upDevice = up_client_get_display_device(upClient_); upDevice_.upDevice = up_client_get_display_device(upClient_);
getUpDeviceInfo(upDevice_); getUpDeviceInfo(upDevice_);
} else { } else {
@ -386,7 +388,6 @@ void UPower::setDisplayDevice() {
} }
// Unref current upDevice if it exists // Unref current upDevice if it exists
if (displayDevice.upDevice != NULL) { if (displayDevice.upDevice != NULL) {
if (thisPtr->upDevice_.upDevice != NULL) g_object_unref(thisPtr->upDevice_.upDevice);
thisPtr->upDevice_ = displayDevice; thisPtr->upDevice_ = displayDevice;
} }
}, },

Some files were not shown because too many files have changed in this diff Show More