Compare commits
117 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
41de8964f1 | ||
|
|
7e0cbf4a1f | ||
|
|
b3fbe1b5ed | ||
|
|
477f30705c | ||
|
|
07d3a4da10 | ||
|
|
61b3c6d7d1 | ||
|
|
c63e332fb2 | ||
|
|
79c30e77a7 | ||
|
|
b0983e9c37 | ||
|
|
3dce0956ec | ||
|
|
9a545e75e4 | ||
|
|
7cfa708b1a | ||
|
|
f1b7d829c2 | ||
|
|
6679801a9c | ||
|
|
13a4e5d43d | ||
|
|
b9a26f51b6 | ||
|
|
ff69525e29 | ||
|
|
1e0037a873 | ||
|
|
5bbace1d43 | ||
|
|
4c72033309 | ||
|
|
b5093f3e58 | ||
|
|
f824ae9334 | ||
|
|
90f2490407 | ||
|
|
a6ac75983f | ||
|
|
7cb1a0e3d6 | ||
|
|
91e57376ef | ||
|
|
0776e694df | ||
|
|
bb190409de | ||
|
|
39d6d80f64 | ||
|
|
b4d95b405c | ||
|
|
71b01fe58f | ||
|
|
2baa93174b | ||
|
|
321ed85a67 | ||
|
|
e526afb963 | ||
|
|
8dcdd97879 | ||
|
|
2dc2b5ccfd | ||
|
|
a4f200cdb5 | ||
|
|
64ed2cd970 | ||
|
|
e45883088d | ||
|
|
061c561762 | ||
|
|
506fd5c597 | ||
|
|
682fc34c1d | ||
|
|
f91889ce1c | ||
|
|
94777921d9 | ||
|
|
ee9dc6ae05 | ||
|
|
1cb30e0f85 | ||
|
|
50711bfb87 | ||
|
|
19360462ba | ||
|
|
73d9c5f560 | ||
|
|
b85f0c17c3 | ||
|
|
b02694caef | ||
|
|
2dfbaabf31 | ||
|
|
f991af2893 | ||
|
|
0e07c7ac5c | ||
|
|
46a152abc8 | ||
|
|
310a473e65 | ||
|
|
d808c00324 | ||
|
|
0cb605b529 | ||
|
|
04796988e9 | ||
|
|
4d74bb3004 | ||
|
|
fd67002662 | ||
|
|
c98cb7095d | ||
|
|
cae5f9a56f | ||
|
|
10c116e54a | ||
|
|
6d3b93bbf7 | ||
|
|
9ef6dc7380 | ||
|
|
f409f53131 | ||
|
|
88766de1ee | ||
|
|
b6c13ba58b | ||
|
|
e7a4bafede | ||
|
|
6c48db6cee | ||
|
|
984d0de1fc | ||
|
|
29bf5d5da1 | ||
|
|
255c0ebe28 | ||
|
|
8516d457ad | ||
|
|
7505e2c3f3 | ||
|
|
c8dc3e7024 | ||
|
|
b7ff47fb05 | ||
|
|
7463be9b6a | ||
|
|
125b59e23b | ||
|
|
3c7a9bf432 | ||
|
|
aaea814217 | ||
|
|
c336bc5466 | ||
|
|
460b19ba1b | ||
|
|
53d8d3cd2a | ||
|
|
eae22f3273 | ||
|
|
08da7b5282 | ||
|
|
800c4b56cc | ||
|
|
81fb0daad2 | ||
|
|
4ae2b6f1ba | ||
|
|
c9215ad818 | ||
|
|
61c5dad895 | ||
|
|
998fd7a192 | ||
|
|
72404a77f0 | ||
|
|
59c270ec06 | ||
|
|
a816812f81 | ||
|
|
451d458545 | ||
|
|
4222032fa1 | ||
|
|
e541936df9 | ||
|
|
4dbb874f7c | ||
|
|
1a9f5aced7 | ||
|
|
7b854112ed | ||
|
|
b4519c0819 | ||
|
|
e0f3695523 | ||
|
|
42affa4eda | ||
|
|
5ee0d1c7fe | ||
|
|
3948c0d154 | ||
|
|
53ca5a4883 | ||
|
|
e1649b001f | ||
|
|
5e1d6d1cc5 | ||
|
|
fdb9004048 | ||
|
|
1c07ca0099 | ||
|
|
69e2e249a6 | ||
|
|
bc2e143ac5 | ||
|
|
d100e78872 | ||
|
|
cf64b2c88c | ||
|
|
622f22d6b5 |
30
README.md
30
README.md
@@ -116,6 +116,36 @@ sudo apt install \
|
||||
libxkbregistry-dev
|
||||
```
|
||||
|
||||
On Arch, you can use this command:
|
||||
|
||||
```
|
||||
pacman -S \
|
||||
gtkmm3 \
|
||||
jsoncpp \
|
||||
libsigc++ \
|
||||
fmt \
|
||||
wayland \
|
||||
chrono-date \
|
||||
spdlog \
|
||||
gtk3 \
|
||||
gobject-introspection \
|
||||
libgirepository \
|
||||
libpulse \
|
||||
libnl \
|
||||
libappindicator-gtk3 \
|
||||
libdbusmenu-gtk3 \
|
||||
libmpdclient \
|
||||
sndio \
|
||||
libevdev \
|
||||
libxkbcommon \
|
||||
upower \
|
||||
meson \
|
||||
cmake \
|
||||
scdoc \
|
||||
wayland-protocols \
|
||||
glib2-devel
|
||||
```
|
||||
|
||||
|
||||
Contributions welcome!<br>
|
||||
Have fun :)<br>
|
||||
|
||||
6
flake.lock
generated
6
flake.lock
generated
@@ -18,11 +18,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1748460289,
|
||||
"narHash": "sha256-7doLyJBzCllvqX4gszYtmZUToxKvMUrg45EUWaUYmBg=",
|
||||
"lastModified": 1753694789,
|
||||
"narHash": "sha256-cKgvtz6fKuK1Xr5LQW/zOUiAC0oSQoA9nOISB0pJZqM=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "96ec055edbe5ee227f28cdbc3f1ddf1df5965102",
|
||||
"rev": "dc9637876d0dcc8c9e5e22986b857632effeb727",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
#include "AModule.hpp"
|
||||
#include "group.hpp"
|
||||
#include "util/kill_signal.hpp"
|
||||
#include "xdg-output-unstable-v1-client-protocol.h"
|
||||
|
||||
namespace waybar {
|
||||
@@ -68,7 +69,11 @@ class Bar : public sigc::trackable {
|
||||
void setMode(const std::string &mode);
|
||||
void setVisible(bool value);
|
||||
void toggle();
|
||||
void show();
|
||||
void hide();
|
||||
void handleSignal(int);
|
||||
util::KillSignalAction getOnSigusr1Action();
|
||||
util::KillSignalAction getOnSigusr2Action();
|
||||
|
||||
struct waybar_output *output;
|
||||
Json::Value config;
|
||||
@@ -118,6 +123,9 @@ class Bar : public sigc::trackable {
|
||||
std::unique_ptr<BarIpcClient> _ipc_client;
|
||||
#endif
|
||||
std::vector<std::shared_ptr<waybar::AModule>> modules_all_;
|
||||
|
||||
waybar::util::KillSignalAction onSigusr1 = util::SIGNALACTION_DEFAULT_SIGUSR1;
|
||||
waybar::util::KillSignalAction onSigusr2 = util::SIGNALACTION_DEFAULT_SIGUSR2;
|
||||
};
|
||||
|
||||
} // namespace waybar
|
||||
|
||||
@@ -35,6 +35,7 @@ class Config {
|
||||
void setupConfig(Json::Value &dst, const std::string &config_file, int depth);
|
||||
void resolveConfigIncludes(Json::Value &config, int depth);
|
||||
void mergeConfig(Json::Value &a_config_, Json::Value &b_config_);
|
||||
static std::optional<std::string> findIncludePath(const std::string &name);
|
||||
|
||||
std::string config_file_;
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ class Battery : public ALabel {
|
||||
std::tuple<uint8_t, float, std::string, float, uint16_t, float> getInfos();
|
||||
const std::string formatTimeRemaining(float hoursRemaining);
|
||||
void setBarClass(std::string&);
|
||||
void processEvents(std::string& state, std::string& status, uint8_t capacity);
|
||||
|
||||
int global_watch;
|
||||
std::map<fs::path, int> batteries_;
|
||||
@@ -43,6 +44,7 @@ class Battery : public ALabel {
|
||||
int global_watch_fd_;
|
||||
std::mutex battery_list_mutex_;
|
||||
std::string old_status_;
|
||||
std::string last_event_;
|
||||
bool warnFirstTime_{true};
|
||||
const Bar& bar_;
|
||||
|
||||
|
||||
143
include/modules/ext/workspace_manager.hpp
Normal file
143
include/modules/ext/workspace_manager.hpp
Normal file
@@ -0,0 +1,143 @@
|
||||
#pragma once
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <gtkmm/button.h>
|
||||
#include <gtkmm/image.h>
|
||||
#include <gtkmm/label.h>
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "AModule.hpp"
|
||||
#include "bar.hpp"
|
||||
#include "ext-workspace-v1-client-protocol.h"
|
||||
|
||||
namespace waybar::modules::ext {
|
||||
|
||||
class WorkspaceGroup;
|
||||
class Workspace;
|
||||
|
||||
class WorkspaceManager final : public AModule {
|
||||
public:
|
||||
WorkspaceManager(const std::string &id, const waybar::Bar &bar, const Json::Value &config);
|
||||
~WorkspaceManager() override;
|
||||
void register_manager(wl_registry *registry, uint32_t name, uint32_t version);
|
||||
void remove_workspace_group(uint32_t id);
|
||||
void remove_workspace(uint32_t id);
|
||||
void set_needs_sorting() { needs_sorting_ = true; }
|
||||
|
||||
// wl events
|
||||
void handle_workspace_group(ext_workspace_group_handle_v1 *handle);
|
||||
void handle_workspace(ext_workspace_handle_v1 *handle);
|
||||
void handle_done();
|
||||
void handle_finished();
|
||||
|
||||
// wl requests
|
||||
void commit() const;
|
||||
|
||||
private:
|
||||
void update() override;
|
||||
bool has_button(const Gtk::Button *button);
|
||||
void sort_workspaces();
|
||||
void clear_buttons();
|
||||
void update_buttons();
|
||||
|
||||
static uint32_t group_global_id;
|
||||
static uint32_t workspace_global_id;
|
||||
uint32_t workspace_name = 0;
|
||||
|
||||
bool sort_by_id_ = false;
|
||||
bool sort_by_name_ = true;
|
||||
bool sort_by_coordinates_ = false;
|
||||
bool all_outputs_ = false;
|
||||
|
||||
const waybar::Bar &bar_;
|
||||
Gtk::Box box_;
|
||||
|
||||
ext_workspace_manager_v1 *ext_manager_ = nullptr;
|
||||
std::vector<std::unique_ptr<WorkspaceGroup>> groups_;
|
||||
std::vector<std::unique_ptr<Workspace>> workspaces_;
|
||||
|
||||
bool needs_sorting_ = false;
|
||||
};
|
||||
|
||||
class WorkspaceGroup {
|
||||
public:
|
||||
WorkspaceGroup(WorkspaceManager &manager, ext_workspace_group_handle_v1 *handle, uint32_t id);
|
||||
~WorkspaceGroup();
|
||||
|
||||
u_int32_t id() const { return id_; }
|
||||
bool has_output(const wl_output *output);
|
||||
bool has_workspace(const ext_workspace_handle_v1 *workspace);
|
||||
|
||||
// wl events
|
||||
void handle_capabilities(uint32_t capabilities);
|
||||
void handle_output_enter(wl_output *output);
|
||||
void handle_output_leave(wl_output *output);
|
||||
void handle_workspace_enter(ext_workspace_handle_v1 *handle);
|
||||
void handle_workspace_leave(ext_workspace_handle_v1 *handle);
|
||||
void handle_removed();
|
||||
|
||||
private:
|
||||
WorkspaceManager &workspaces_manager_;
|
||||
ext_workspace_group_handle_v1 *ext_handle_;
|
||||
uint32_t id_;
|
||||
std::vector<wl_output *> outputs_;
|
||||
std::vector<ext_workspace_handle_v1 *> workspaces_;
|
||||
};
|
||||
|
||||
class Workspace {
|
||||
public:
|
||||
Workspace(const Json::Value &config, WorkspaceManager &manager, ext_workspace_handle_v1 *handle,
|
||||
uint32_t id, const std::string &name);
|
||||
~Workspace();
|
||||
|
||||
ext_workspace_handle_v1 *handle() const { return ext_handle_; }
|
||||
u_int32_t id() const { return id_; }
|
||||
std::string &workspace_id() { return workspace_id_; }
|
||||
std::string &name() { return name_; }
|
||||
std::vector<u_int32_t> &coordinates() { return coordinates_; }
|
||||
Gtk::Button &button() { return button_; }
|
||||
void update();
|
||||
|
||||
// wl events
|
||||
void handle_id(const std::string &id);
|
||||
void handle_name(const std::string &name);
|
||||
void handle_coordinates(const std::vector<uint32_t> &coordinates);
|
||||
void handle_state(uint32_t state);
|
||||
void handle_capabilities(uint32_t capabilities);
|
||||
void handle_removed();
|
||||
|
||||
// gdk events
|
||||
bool handle_clicked(const GdkEventButton *button) const;
|
||||
|
||||
private:
|
||||
bool has_state(uint32_t state) const { return (state_ & state) == state; }
|
||||
std::string icon();
|
||||
|
||||
WorkspaceManager &workspace_manager_;
|
||||
ext_workspace_handle_v1 *ext_handle_ = nullptr;
|
||||
uint32_t id_;
|
||||
uint32_t state_ = 0;
|
||||
std::string workspace_id_;
|
||||
std::string name_;
|
||||
std::vector<uint32_t> coordinates_;
|
||||
|
||||
bool active_only_ = false;
|
||||
bool ignore_hidden_ = true;
|
||||
std::string format_;
|
||||
bool with_icon_ = false;
|
||||
static std::map<std::string, std::string> icon_map_;
|
||||
std::string on_click_action_;
|
||||
std::string on_click_middle_action_;
|
||||
std::string on_click_right_action_;
|
||||
|
||||
Gtk::Button button_;
|
||||
Gtk::Box content_;
|
||||
Gtk::Label label_;
|
||||
|
||||
bool needs_updating_ = false;
|
||||
};
|
||||
|
||||
} // namespace waybar::modules::ext
|
||||
10
include/modules/ext/workspace_manager_binding.hpp
Normal file
10
include/modules/ext/workspace_manager_binding.hpp
Normal file
@@ -0,0 +1,10 @@
|
||||
#include "ext-workspace-v1-client-protocol.h"
|
||||
|
||||
namespace waybar::modules::ext {
|
||||
void add_registry_listener(void *data);
|
||||
void add_workspace_listener(ext_workspace_handle_v1 *workspace_handle, void *data);
|
||||
void add_workspace_group_listener(ext_workspace_group_handle_v1 *workspace_group_handle,
|
||||
void *data);
|
||||
ext_workspace_manager_v1 *workspace_manager_bind(wl_registry *registry, uint32_t name,
|
||||
uint32_t version, void *data);
|
||||
} // namespace waybar::modules::ext
|
||||
@@ -26,18 +26,31 @@ namespace waybar::modules::hyprland {
|
||||
|
||||
class Workspaces;
|
||||
|
||||
struct WindowRepr {
|
||||
std::string address;
|
||||
std::string window_class;
|
||||
std::string window_title;
|
||||
std::string repr_rewrite;
|
||||
bool isActive = false;
|
||||
|
||||
public:
|
||||
bool empty() const { return address.empty(); }
|
||||
void setActive(bool value) { isActive = value; }
|
||||
};
|
||||
|
||||
class WindowCreationPayload {
|
||||
public:
|
||||
WindowCreationPayload(std::string workspace_name, WindowAddress window_address,
|
||||
std::string window_repr);
|
||||
WindowRepr window_repr);
|
||||
WindowCreationPayload(std::string workspace_name, WindowAddress window_address,
|
||||
std::string window_class, std::string window_title);
|
||||
std::string window_class, std::string window_title, bool is_active);
|
||||
WindowCreationPayload(Json::Value const& client_data);
|
||||
|
||||
int incrementTimeSpentUncreated();
|
||||
bool isEmpty(Workspaces& workspace_manager);
|
||||
bool reprIsReady() const { return std::holds_alternative<Repr>(m_window); }
|
||||
std::string repr(Workspaces& workspace_manager);
|
||||
WindowRepr repr(Workspaces& workspace_manager);
|
||||
void setActive(bool value) { m_isActive = value; }
|
||||
|
||||
std::string getWorkspaceName() const { return m_workspaceName; }
|
||||
WindowAddress getAddress() const { return m_windowAddress; }
|
||||
@@ -48,12 +61,13 @@ class WindowCreationPayload {
|
||||
void clearAddr();
|
||||
void clearWorkspaceName();
|
||||
|
||||
using Repr = std::string;
|
||||
using Repr = WindowRepr;
|
||||
using ClassAndTitle = std::pair<std::string, std::string>;
|
||||
std::variant<Repr, ClassAndTitle> m_window;
|
||||
|
||||
WindowAddress m_windowAddress;
|
||||
std::string m_workspaceName;
|
||||
bool m_isActive = false;
|
||||
|
||||
int m_timeSpentUncreated = 0;
|
||||
};
|
||||
|
||||
@@ -54,15 +54,18 @@ class Workspace {
|
||||
void setWindows(uint value) { m_windows = value; };
|
||||
void setName(std::string const& value) { m_name = 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 std::ranges::any_of(m_windowMap,
|
||||
[&addr](const auto& window) { return window.address == addr; });
|
||||
};
|
||||
void insertWindow(WindowCreationPayload create_window_payload);
|
||||
std::string removeWindow(WindowAddress const& addr);
|
||||
void initializeWindowMap(const Json::Value& clients_data);
|
||||
void setActiveWindow(WindowAddress const& addr);
|
||||
|
||||
bool onWindowOpened(WindowCreationPayload const& create_window_payload);
|
||||
std::optional<std::string> closeWindow(WindowAddress const& addr);
|
||||
std::optional<WindowRepr> closeWindow(WindowAddress const& addr);
|
||||
|
||||
void update(const std::string& format, const std::string& icon);
|
||||
void update(const std::string& workspace_icon);
|
||||
|
||||
private:
|
||||
Workspaces& m_workspaceManager;
|
||||
@@ -78,11 +81,16 @@ class Workspace {
|
||||
bool m_isUrgent = false;
|
||||
bool m_isVisible = false;
|
||||
|
||||
std::map<WindowAddress, std::string> m_windowMap;
|
||||
std::vector<WindowRepr> m_windowMap;
|
||||
|
||||
Gtk::Button m_button;
|
||||
Gtk::Box m_content;
|
||||
Gtk::Label m_label;
|
||||
Gtk::Label m_labelBefore;
|
||||
Gtk::Label m_labelAfter;
|
||||
|
||||
void updateTaskbar(const std::string& workspace_icon);
|
||||
bool handleClick(const GdkEventButton* event_button, WindowAddress const& addr) const;
|
||||
bool shouldSkipWindow(const WindowRepr& window_repr) const;
|
||||
IPC& m_ipc;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <gtkmm/button.h>
|
||||
#include <gtkmm/enums.h>
|
||||
#include <gtkmm/label.h>
|
||||
#include <json/value.h>
|
||||
|
||||
@@ -18,6 +19,7 @@
|
||||
#include "modules/hyprland/windowcreationpayload.hpp"
|
||||
#include "modules/hyprland/workspace.hpp"
|
||||
#include "util/enum.hpp"
|
||||
#include "util/icon_loader.hpp"
|
||||
#include "util/regex_collection.hpp"
|
||||
|
||||
using WindowAddress = std::string;
|
||||
@@ -39,14 +41,25 @@ class Workspaces : public AModule, public EventHandler {
|
||||
auto specialVisibleOnly() const -> bool { return m_specialVisibleOnly; }
|
||||
auto persistentOnly() const -> bool { return m_persistentOnly; }
|
||||
auto moveToMonitor() const -> bool { return m_moveToMonitor; }
|
||||
auto enableTaskbar() const -> bool { return m_enableTaskbar; }
|
||||
auto taskbarWithIcon() const -> bool { return m_taskbarWithIcon; }
|
||||
|
||||
auto getBarOutput() const -> std::string { return m_bar.output->name; }
|
||||
auto formatBefore() const -> std::string { return m_formatBefore; }
|
||||
auto formatAfter() const -> std::string { return m_formatAfter; }
|
||||
auto taskbarFormatBefore() const -> std::string { return m_taskbarFormatBefore; }
|
||||
auto taskbarFormatAfter() const -> std::string { return m_taskbarFormatAfter; }
|
||||
auto taskbarIconSize() const -> int { return m_taskbarIconSize; }
|
||||
auto taskbarOrientation() const -> Gtk::Orientation { return m_taskbarOrientation; }
|
||||
auto onClickWindow() const -> std::string { return m_onClickWindow; }
|
||||
auto getIgnoredWindows() const -> std::vector<std::regex> { return m_ignoreWindows; }
|
||||
|
||||
std::string getRewrite(std::string window_class, std::string window_title);
|
||||
std::string& getWindowSeparator() { return m_formatWindowSeparator; }
|
||||
bool isWorkspaceIgnored(std::string const& workspace_name);
|
||||
|
||||
bool windowRewriteConfigUsesTitle() const { return m_anyWindowRewriteRuleUsesTitle; }
|
||||
const IconLoader& iconLoader() const { return m_iconLoader; }
|
||||
|
||||
private:
|
||||
void onEvent(const std::string& e) override;
|
||||
@@ -70,6 +83,7 @@ class Workspaces : public AModule, public EventHandler {
|
||||
auto populateIgnoreWorkspacesConfig(const Json::Value& config) -> void;
|
||||
auto populateFormatWindowSeparatorConfig(const Json::Value& config) -> void;
|
||||
auto populateWindowRewriteConfig(const Json::Value& config) -> void;
|
||||
auto populateWorkspaceTaskbarConfig(const Json::Value& config) -> void;
|
||||
|
||||
void registerIpc();
|
||||
|
||||
@@ -92,6 +106,7 @@ class Workspaces : public AModule, public EventHandler {
|
||||
void onWindowMoved(std::string const& payload);
|
||||
|
||||
void onWindowTitleEvent(std::string const& payload);
|
||||
void onActiveWindowChanged(WindowAddress const& payload);
|
||||
|
||||
void onConfigReloaded();
|
||||
|
||||
@@ -131,7 +146,7 @@ class Workspaces : public AModule, public EventHandler {
|
||||
// Map for windows stored in workspaces not present in the current bar.
|
||||
// This happens when the user has multiple monitors (hence, multiple bars)
|
||||
// and doesn't share windows across bars (a.k.a `all-outputs` = false)
|
||||
std::map<WindowAddress, std::string> m_orphanWindowMap;
|
||||
std::map<WindowAddress, WindowRepr, std::less<>> m_orphanWindowMap;
|
||||
|
||||
enum class SortMethod { ID, NAME, NUMBER, SPECIAL_CENTERED, DEFAULT };
|
||||
util::EnumParser<SortMethod> m_enumParser;
|
||||
@@ -142,7 +157,8 @@ class Workspaces : public AModule, public EventHandler {
|
||||
{"SPECIAL-CENTERED", SortMethod::SPECIAL_CENTERED},
|
||||
{"DEFAULT", SortMethod::DEFAULT}};
|
||||
|
||||
std::string m_format;
|
||||
std::string m_formatBefore;
|
||||
std::string m_formatAfter;
|
||||
|
||||
std::map<std::string, std::string> m_iconsMap;
|
||||
util::RegexCollection m_windowRewriteRules;
|
||||
@@ -158,7 +174,20 @@ class Workspaces : public AModule, public EventHandler {
|
||||
std::vector<std::string> m_workspacesToRemove;
|
||||
std::vector<WindowCreationPayload> m_windowsToCreate;
|
||||
|
||||
IconLoader m_iconLoader;
|
||||
bool m_enableTaskbar = false;
|
||||
bool m_updateActiveWindow = false;
|
||||
bool m_taskbarWithIcon = false;
|
||||
bool m_taskbarWithTitle = false;
|
||||
std::string m_taskbarFormatBefore;
|
||||
std::string m_taskbarFormatAfter;
|
||||
int m_taskbarIconSize = 16;
|
||||
Gtk::Orientation m_taskbarOrientation = Gtk::ORIENTATION_HORIZONTAL;
|
||||
std::string m_onClickWindow;
|
||||
std::string m_currentActiveWindowAddress;
|
||||
|
||||
std::vector<std::regex> m_ignoreWorkspaces;
|
||||
std::vector<std::regex> m_ignoreWindows;
|
||||
|
||||
std::mutex m_mutex;
|
||||
const Bar& m_bar;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <sys/epoll.h>
|
||||
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#include "ALabel.hpp"
|
||||
#include "util/sleeper_thread.hpp"
|
||||
@@ -27,8 +28,8 @@ class Network : public ALabel {
|
||||
auto update() -> void override;
|
||||
|
||||
private:
|
||||
static const uint8_t MAX_RETRY = 5;
|
||||
static const uint8_t EPOLL_MAX = 200;
|
||||
static const uint8_t MAX_RETRY{5};
|
||||
static const uint8_t EPOLL_MAX{200};
|
||||
|
||||
static int handleEvents(struct nl_msg*, void*);
|
||||
static int handleEventsDone(struct nl_msg*, void*);
|
||||
@@ -44,44 +45,44 @@ class Network : public ALabel {
|
||||
void parseFreq(struct nlattr**);
|
||||
void parseBssid(struct nlattr**);
|
||||
bool associatedOrJoined(struct nlattr**);
|
||||
bool checkInterface(std::string name);
|
||||
bool matchInterface(const std::string& ifname, const std::vector<std::string>& altnames,
|
||||
std::string& matched) const;
|
||||
auto getInfo() -> void;
|
||||
const std::string getNetworkState() const;
|
||||
void clearIface();
|
||||
bool wildcardMatch(const std::string& pattern, const std::string& text) const;
|
||||
std::optional<std::pair<unsigned long long, unsigned long long>> readBandwidthUsage();
|
||||
|
||||
int ifid_;
|
||||
ip_addr_pref addr_pref_;
|
||||
struct sockaddr_nl nladdr_ = {0};
|
||||
struct nl_sock* sock_ = nullptr;
|
||||
struct nl_sock* ev_sock_ = nullptr;
|
||||
int efd_;
|
||||
int ev_fd_;
|
||||
int nl80211_id_;
|
||||
int ifid_{-1};
|
||||
ip_addr_pref addr_pref_{ip_addr_pref::IPV4};
|
||||
struct sockaddr_nl nladdr_{0};
|
||||
struct nl_sock* sock_{nullptr};
|
||||
struct nl_sock* ev_sock_{nullptr};
|
||||
int efd_{-1};
|
||||
int ev_fd_{-1};
|
||||
int nl80211_id_{-1};
|
||||
std::mutex mutex_;
|
||||
|
||||
bool want_route_dump_;
|
||||
bool want_link_dump_;
|
||||
bool want_addr_dump_;
|
||||
bool dump_in_progress_;
|
||||
bool is_p2p_;
|
||||
bool want_route_dump_{false};
|
||||
bool want_link_dump_{false};
|
||||
bool want_addr_dump_{false};
|
||||
bool dump_in_progress_{false};
|
||||
bool is_p2p_{false};
|
||||
|
||||
unsigned long long bandwidth_down_total_;
|
||||
unsigned long long bandwidth_up_total_;
|
||||
unsigned long long bandwidth_down_total_{0};
|
||||
unsigned long long bandwidth_up_total_{0};
|
||||
|
||||
std::string state_;
|
||||
std::string essid_;
|
||||
std::string bssid_;
|
||||
bool carrier_;
|
||||
bool carrier_{false};
|
||||
std::string ifname_;
|
||||
std::string ipaddr_;
|
||||
std::string ipaddr6_;
|
||||
std::string gwaddr_;
|
||||
std::string netmask_;
|
||||
std::string netmask6_;
|
||||
int cidr_;
|
||||
int cidr6_;
|
||||
int cidr_{0};
|
||||
int cidr6_{0};
|
||||
int32_t signal_strength_dbm_;
|
||||
uint8_t signal_strength_;
|
||||
std::string signal_strength_app_;
|
||||
@@ -90,9 +91,9 @@ class Network : public ALabel {
|
||||
util::SleeperThread thread_;
|
||||
util::SleeperThread thread_timer_;
|
||||
#ifdef WANT_RFKILL
|
||||
util::Rfkill rfkill_;
|
||||
util::Rfkill rfkill_{RFKILL_TYPE_WLAN};
|
||||
#endif
|
||||
float frequency_;
|
||||
float frequency_{0};
|
||||
};
|
||||
|
||||
} // namespace waybar::modules
|
||||
|
||||
@@ -24,6 +24,7 @@ class Layout : public waybar::ALabel {
|
||||
|
||||
private:
|
||||
const waybar::Bar &bar_;
|
||||
std::string name_;
|
||||
struct wl_output *output_; // stores the output this module belongs to
|
||||
struct wl_output *focused_output_; // stores the currently focused output
|
||||
struct zriver_output_status_v1 *output_status_;
|
||||
|
||||
@@ -21,6 +21,7 @@ class Tray : public AModule {
|
||||
void onRemove(std::unique_ptr<Item>& item);
|
||||
|
||||
static inline std::size_t nb_hosts_ = 0;
|
||||
bool show_passive_ = false;
|
||||
Gtk::Box box_;
|
||||
SNI::Watcher::singleton watcher_;
|
||||
SNI::Host host_;
|
||||
|
||||
@@ -22,6 +22,8 @@ class Wireplumber : public ALabel {
|
||||
void activatePlugins();
|
||||
static void updateVolume(waybar::modules::Wireplumber* self, uint32_t id);
|
||||
static void updateNodeName(waybar::modules::Wireplumber* self, uint32_t id);
|
||||
static void updateSourceVolume(waybar::modules::Wireplumber* self, uint32_t id);
|
||||
static void updateSourceName(waybar::modules::Wireplumber* self, uint32_t id); // NEW
|
||||
static void onPluginActivated(WpObject* p, GAsyncResult* res, waybar::modules::Wireplumber* self);
|
||||
static void onDefaultNodesApiLoaded(WpObject* p, GAsyncResult* res,
|
||||
waybar::modules::Wireplumber* self);
|
||||
@@ -46,7 +48,12 @@ class Wireplumber : public ALabel {
|
||||
double min_step_;
|
||||
uint32_t node_id_{0};
|
||||
std::string node_name_;
|
||||
std::string source_name_;
|
||||
gchar* type_;
|
||||
uint32_t source_node_id_;
|
||||
bool source_muted_;
|
||||
double source_volume_;
|
||||
gchar* default_source_name_;
|
||||
};
|
||||
|
||||
} // namespace waybar::modules
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "bar.hpp"
|
||||
#include "client.hpp"
|
||||
#include "giomm/desktopappinfo.h"
|
||||
#include "util/icon_loader.hpp"
|
||||
#include "util/json.hpp"
|
||||
#include "wlr-foreign-toplevel-management-unstable-v1-client-protocol.h"
|
||||
|
||||
@@ -89,9 +90,6 @@ class Task {
|
||||
std::string state_string(bool = false) const;
|
||||
void set_minimize_hint();
|
||||
void on_button_size_allocated(Gtk::Allocation &alloc);
|
||||
void set_app_info_from_app_id_list(const std::string &app_id_list);
|
||||
bool image_load_icon(Gtk::Image &image, const Glib::RefPtr<Gtk::IconTheme> &icon_theme,
|
||||
Glib::RefPtr<Gio::DesktopAppInfo> app_info, int size);
|
||||
void hide_if_ignored();
|
||||
|
||||
public:
|
||||
@@ -153,7 +151,7 @@ class Taskbar : public waybar::AModule {
|
||||
Gtk::Box box_;
|
||||
std::vector<TaskPtr> tasks_;
|
||||
|
||||
std::vector<Glib::RefPtr<Gtk::IconTheme>> icon_themes_;
|
||||
IconLoader icon_loader_;
|
||||
std::unordered_set<std::string> ignore_list_;
|
||||
std::map<std::string, std::string> app_ids_replace_map_;
|
||||
|
||||
@@ -178,7 +176,7 @@ class Taskbar : public waybar::AModule {
|
||||
bool show_output(struct wl_output *) const;
|
||||
bool all_outputs() const;
|
||||
|
||||
const std::vector<Glib::RefPtr<Gtk::IconTheme>> &icon_themes() const;
|
||||
const IconLoader &icon_loader() const;
|
||||
const std::unordered_set<std::string> &ignore_list() const;
|
||||
const std::map<std::string, std::string> &app_ids_replace_map() const;
|
||||
};
|
||||
|
||||
@@ -1,172 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <gtkmm/button.h>
|
||||
#include <gtkmm/image.h>
|
||||
#include <gtkmm/label.h>
|
||||
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "AModule.hpp"
|
||||
#include "bar.hpp"
|
||||
#include "ext-workspace-unstable-v1-client-protocol.h"
|
||||
|
||||
namespace waybar::modules::wlr {
|
||||
|
||||
class WorkspaceManager;
|
||||
class WorkspaceGroup;
|
||||
|
||||
class Workspace {
|
||||
public:
|
||||
Workspace(const waybar::Bar &bar, const Json::Value &config, WorkspaceGroup &workspace_group,
|
||||
zext_workspace_handle_v1 *workspace, uint32_t id, std::string name);
|
||||
~Workspace();
|
||||
auto update() -> void;
|
||||
|
||||
auto id() const -> uint32_t { return id_; }
|
||||
auto is_active() const -> bool { return state_ & static_cast<uint32_t>(State::ACTIVE); }
|
||||
auto is_urgent() const -> bool { return state_ & static_cast<uint32_t>(State::URGENT); }
|
||||
auto is_hidden() const -> bool { return state_ & static_cast<uint32_t>(State::HIDDEN); }
|
||||
auto is_empty() const -> bool { return state_ & static_cast<uint32_t>(State::EMPTY); }
|
||||
auto is_persistent() const -> bool { return persistent_; }
|
||||
// wlr stuff
|
||||
auto handle_name(const std::string &name) -> void;
|
||||
auto handle_coordinates(const std::vector<uint32_t> &coordinates) -> void;
|
||||
auto handle_state(const std::vector<uint32_t> &state) -> void;
|
||||
auto handle_remove() -> void;
|
||||
auto make_persistent() -> void;
|
||||
auto handle_duplicate() -> void;
|
||||
|
||||
auto handle_done() -> void;
|
||||
auto handle_clicked(GdkEventButton *bt) -> bool;
|
||||
auto show() -> void;
|
||||
auto hide() -> void;
|
||||
auto get_button_ref() -> Gtk::Button & { return button_; }
|
||||
auto get_name() -> std::string & { return name_; }
|
||||
auto get_coords() -> std::vector<uint32_t> & { return coordinates_; }
|
||||
|
||||
enum class State {
|
||||
ACTIVE = (1 << 0),
|
||||
URGENT = (1 << 1),
|
||||
HIDDEN = (1 << 2),
|
||||
EMPTY = (1 << 3),
|
||||
};
|
||||
|
||||
private:
|
||||
auto get_icon() -> std::string;
|
||||
|
||||
const Bar &bar_;
|
||||
const Json::Value &config_;
|
||||
WorkspaceGroup &workspace_group_;
|
||||
|
||||
// wlr stuff
|
||||
zext_workspace_handle_v1 *workspace_handle_;
|
||||
uint32_t state_ = 0;
|
||||
|
||||
uint32_t id_;
|
||||
std::string name_;
|
||||
std::vector<uint32_t> coordinates_;
|
||||
static std::map<std::string, std::string> icons_map_;
|
||||
std::string format_;
|
||||
bool with_icon_ = false;
|
||||
bool persistent_ = false;
|
||||
|
||||
Gtk::Button button_;
|
||||
Gtk::Box content_;
|
||||
Gtk::Label label_;
|
||||
};
|
||||
|
||||
class WorkspaceGroup {
|
||||
public:
|
||||
WorkspaceGroup(const waybar::Bar &bar, Gtk::Box &box, const Json::Value &config,
|
||||
WorkspaceManager &manager, zext_workspace_group_handle_v1 *workspace_group_handle,
|
||||
uint32_t id);
|
||||
~WorkspaceGroup();
|
||||
auto update() -> void;
|
||||
|
||||
auto id() const -> uint32_t { return id_; }
|
||||
auto is_visible() const -> bool;
|
||||
auto remove_workspace(uint32_t id_) -> void;
|
||||
auto active_only() const -> bool;
|
||||
auto creation_delayed() const -> bool;
|
||||
auto workspaces() -> std::vector<std::unique_ptr<Workspace>> & { return workspaces_; }
|
||||
auto persistent_workspaces() -> std::vector<std::string> & { return persistent_workspaces_; }
|
||||
|
||||
auto sort_workspaces() -> void;
|
||||
auto set_need_to_sort() -> void { need_to_sort = true; }
|
||||
auto add_button(Gtk::Button &button) -> void;
|
||||
auto remove_button(Gtk::Button &button) -> void;
|
||||
auto fill_persistent_workspaces() -> void;
|
||||
auto create_persistent_workspaces() -> void;
|
||||
|
||||
// wlr stuff
|
||||
auto handle_workspace_create(zext_workspace_handle_v1 *workspace_handle) -> void;
|
||||
auto handle_remove() -> void;
|
||||
auto handle_output_enter(wl_output *output) -> void;
|
||||
auto handle_output_leave() -> void;
|
||||
auto handle_done() -> void;
|
||||
auto commit() -> void;
|
||||
|
||||
private:
|
||||
static uint32_t workspace_global_id;
|
||||
const waybar::Bar &bar_;
|
||||
Gtk::Box &box_;
|
||||
const Json::Value &config_;
|
||||
WorkspaceManager &workspace_manager_;
|
||||
|
||||
// wlr stuff
|
||||
zext_workspace_group_handle_v1 *workspace_group_handle_;
|
||||
wl_output *output_ = nullptr;
|
||||
|
||||
uint32_t id_;
|
||||
std::vector<std::unique_ptr<Workspace>> workspaces_;
|
||||
bool need_to_sort = false;
|
||||
std::vector<std::string> persistent_workspaces_;
|
||||
bool persistent_created_ = false;
|
||||
};
|
||||
|
||||
class WorkspaceManager : public AModule {
|
||||
public:
|
||||
WorkspaceManager(const std::string &id, const waybar::Bar &bar, const Json::Value &config);
|
||||
~WorkspaceManager() override;
|
||||
auto update() -> void override;
|
||||
|
||||
auto all_outputs() const -> bool { return all_outputs_; }
|
||||
auto active_only() const -> bool { return active_only_; }
|
||||
auto workspace_comparator() const
|
||||
-> std::function<bool(std::unique_ptr<Workspace> &, std::unique_ptr<Workspace> &)>;
|
||||
auto creation_delayed() const -> bool { return creation_delayed_; }
|
||||
|
||||
auto sort_workspaces() -> void;
|
||||
auto remove_workspace_group(uint32_t id_) -> void;
|
||||
|
||||
// wlr stuff
|
||||
auto register_manager(wl_registry *registry, uint32_t name, uint32_t version) -> void;
|
||||
auto handle_workspace_group_create(zext_workspace_group_handle_v1 *workspace_group_handle)
|
||||
-> void;
|
||||
auto handle_done() -> void;
|
||||
auto handle_finished() -> void;
|
||||
auto commit() -> void;
|
||||
|
||||
private:
|
||||
const waybar::Bar &bar_;
|
||||
Gtk::Box box_;
|
||||
std::vector<std::unique_ptr<WorkspaceGroup>> groups_;
|
||||
|
||||
// wlr stuff
|
||||
zext_workspace_manager_v1 *workspace_manager_ = nullptr;
|
||||
|
||||
static uint32_t group_global_id;
|
||||
|
||||
bool sort_by_name_ = true;
|
||||
bool sort_by_coordinates_ = true;
|
||||
bool sort_by_number_ = false;
|
||||
bool all_outputs_ = false;
|
||||
bool active_only_ = false;
|
||||
bool creation_delayed_ = false;
|
||||
};
|
||||
|
||||
} // namespace waybar::modules::wlr
|
||||
@@ -1,10 +0,0 @@
|
||||
#include "ext-workspace-unstable-v1-client-protocol.h"
|
||||
|
||||
namespace waybar::modules::wlr {
|
||||
void add_registry_listener(void *data);
|
||||
void add_workspace_listener(zext_workspace_handle_v1 *workspace_handle, void *data);
|
||||
void add_workspace_group_listener(zext_workspace_group_handle_v1 *workspace_group_handle,
|
||||
void *data);
|
||||
zext_workspace_manager_v1 *workspace_manager_bind(wl_registry *registry, uint32_t name,
|
||||
uint32_t version, void *data);
|
||||
} // namespace waybar::modules::wlr
|
||||
@@ -11,6 +11,11 @@
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#ifdef __OpenBSD__
|
||||
#define SIGRTMIN SIGUSR1 - 1
|
||||
#define SIGRTMAX SIGUSR1 + 1
|
||||
#endif
|
||||
|
||||
namespace waybar {
|
||||
|
||||
/**
|
||||
|
||||
34
include/util/icon_loader.hpp
Normal file
34
include/util/icon_loader.hpp
Normal file
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include <gdkmm/general.h>
|
||||
#include <gio/gdesktopappinfo.h>
|
||||
#include <giomm/desktopappinfo.h>
|
||||
#include <glibmm/fileutils.h>
|
||||
#include <gtkmm/image.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "util/gtk_icon.hpp"
|
||||
|
||||
class IconLoader {
|
||||
private:
|
||||
std::vector<Glib::RefPtr<Gtk::IconTheme>> custom_icon_themes_;
|
||||
Glib::RefPtr<Gtk::IconTheme> default_icon_theme_ = Gtk::IconTheme::get_default();
|
||||
static std::vector<std::string> search_prefix();
|
||||
static Glib::RefPtr<Gio::DesktopAppInfo> get_app_info_by_name(const std::string &app_id);
|
||||
static Glib::RefPtr<Gio::DesktopAppInfo> get_desktop_app_info(const std::string &app_id);
|
||||
static Glib::RefPtr<Gdk::Pixbuf> load_icon_from_file(std::string const &icon_path, int size);
|
||||
static std::string get_icon_name_from_icon_theme(const Glib::RefPtr<Gtk::IconTheme> &icon_theme,
|
||||
const std::string &app_id);
|
||||
static bool image_load_icon(Gtk::Image &image, const Glib::RefPtr<Gtk::IconTheme> &icon_theme,
|
||||
Glib::RefPtr<Gio::DesktopAppInfo> app_info, int size);
|
||||
|
||||
public:
|
||||
void add_custom_icon_theme(const std::string &theme_name);
|
||||
bool image_load_icon(Gtk::Image &image, Glib::RefPtr<Gio::DesktopAppInfo> app_info,
|
||||
int size) const;
|
||||
static Glib::RefPtr<Gio::DesktopAppInfo> get_app_info_from_app_id_list(
|
||||
const std::string &app_id_list);
|
||||
};
|
||||
26
include/util/kill_signal.hpp
Normal file
26
include/util/kill_signal.hpp
Normal file
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include <json/value.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace waybar::util {
|
||||
|
||||
enum class KillSignalAction : std::uint8_t {
|
||||
TOGGLE,
|
||||
RELOAD,
|
||||
SHOW,
|
||||
HIDE,
|
||||
NOOP,
|
||||
};
|
||||
const std::map<std::string, KillSignalAction> userKillSignalActions = {
|
||||
{"TOGGLE", KillSignalAction::TOGGLE},
|
||||
{"RELOAD", KillSignalAction::RELOAD},
|
||||
{"SHOW", KillSignalAction::SHOW},
|
||||
{"HIDE", KillSignalAction::HIDE},
|
||||
{"NOOP", KillSignalAction::NOOP}};
|
||||
|
||||
const KillSignalAction SIGNALACTION_DEFAULT_SIGUSR1 = KillSignalAction::TOGGLE;
|
||||
const KillSignalAction SIGNALACTION_DEFAULT_SIGUSR2 = KillSignalAction::RELOAD;
|
||||
|
||||
}; // namespace waybar::util
|
||||
@@ -5,6 +5,8 @@
|
||||
#include <sigc++/signal.h>
|
||||
#include <sigc++/trackable.h>
|
||||
|
||||
#include <atomic>
|
||||
|
||||
namespace waybar::util {
|
||||
|
||||
class Rfkill : public sigc::trackable {
|
||||
@@ -17,7 +19,7 @@ class Rfkill : public sigc::trackable {
|
||||
|
||||
private:
|
||||
enum rfkill_type rfkill_type_;
|
||||
bool state_ = false;
|
||||
std::atomic_bool state_ = false;
|
||||
int fd_ = -1;
|
||||
|
||||
bool on_event(Glib::IOCondition cond);
|
||||
|
||||
@@ -23,3 +23,26 @@ inline std::string capitalize(const std::string& str) {
|
||||
[](unsigned char c) { return std::toupper(c); });
|
||||
return result;
|
||||
}
|
||||
|
||||
inline std::string toLower(const std::string& str) {
|
||||
std::string result = str;
|
||||
std::transform(result.begin(), result.end(), result.begin(),
|
||||
[](unsigned char c) { return std::tolower(c); });
|
||||
return result;
|
||||
}
|
||||
|
||||
inline std::vector<std::string> split(std::string_view s, std::string_view delimiter,
|
||||
int max_splits = -1) {
|
||||
std::vector<std::string> result;
|
||||
size_t pos = 0;
|
||||
size_t next_pos = 0;
|
||||
while ((next_pos = s.find(delimiter, pos)) != std::string::npos) {
|
||||
result.push_back(std::string(s.substr(pos, next_pos - pos)));
|
||||
pos = next_pos + delimiter.size();
|
||||
if (max_splits > 0 && result.size() == static_cast<size_t>(max_splits)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
result.push_back(std::string(s.substr(pos)));
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -116,7 +116,7 @@ The *battery* module displays the current capacity and state (eg. charging) of y
|
||||
*menu-file*: ++
|
||||
typeof: string ++
|
||||
Location of the menu descriptor file. There need to be an element of type
|
||||
GtkMenu with id *menu*
|
||||
GtkMenu with id *menu*.
|
||||
|
||||
*menu-actions*: ++
|
||||
typeof: array ++
|
||||
@@ -127,6 +127,10 @@ The *battery* module displays the current capacity and state (eg. charging) of y
|
||||
default: false ++
|
||||
Enables this module to consume all left over space dynamically.
|
||||
|
||||
*events*: ++
|
||||
typeof: object ++
|
||||
Specifies commands to be executed on specific battery states. See *EVENTS* section below.
|
||||
|
||||
# FORMAT REPLACEMENTS
|
||||
|
||||
*{capacity}*: Capacity in percentage
|
||||
@@ -166,6 +170,19 @@ The *battery* module allows one to define custom formats based on up to two fact
|
||||
- The state can be addressed as a CSS class in the *style.css*. The name of the CSS class is the *<name>* of the state. Each class gets activated when the current capacity is equal to or below the configured *<value>*.
|
||||
- Also each state can have its own *format*. Those can be configured via *format-<name>*. Or if you want to differentiate a bit more even as *format-<status>-<state>*. For more information see *custom-formats*.
|
||||
|
||||
# EVENTS
|
||||
|
||||
Every entry in the *events* object consists of a *<event-name>* (typeof: *string*) and a *<command>* (typeof: *string*). ++
|
||||
*<event-name>* can be in one of the following formats:
|
||||
|
||||
- *on-<status>-<state>*
|
||||
- *on-<status>-<capacity>*
|
||||
|
||||
Where:
|
||||
|
||||
- *<status>* is either *charging* or *discharging*,
|
||||
- *<state>* is the name of one of the states specified in the *states* object,
|
||||
- *<capacity>* is a battery level value (between *0-100*).
|
||||
|
||||
|
||||
# EXAMPLES
|
||||
@@ -178,6 +195,11 @@ The *battery* module allows one to define custom formats based on up to two fact
|
||||
"warning": 30,
|
||||
"critical": 15
|
||||
},
|
||||
"events": {
|
||||
"on-discharging-warning": "notify-send -u normal 'Low Battery'",
|
||||
"on-discharging-critical": "notify-send -u critical 'Very Low Battery'",
|
||||
"on-charging-100": "notify-send -u normal 'Battery Full!'"
|
||||
},
|
||||
"format": "{capacity}% {icon}",
|
||||
"format-icons": ["", "", "", "", ""],
|
||||
"max-length": 25
|
||||
|
||||
@@ -10,7 +10,7 @@ The *workspaces* module displays the currently used workspaces in wayland compos
|
||||
|
||||
# CONFIGURATION
|
||||
|
||||
Addressed by *wlr/workspaces*
|
||||
Addressed by *ext/workspaces*
|
||||
|
||||
*format*: ++
|
||||
typeof: string ++
|
||||
@@ -24,18 +24,18 @@ Addressed by *wlr/workspaces*
|
||||
*sort-by-name*: ++
|
||||
typeof: bool ++
|
||||
default: true ++
|
||||
Should workspaces be sorted by name.
|
||||
Should workspaces be sorted by name. Workspace names will be sorted numerically when all names are numbers.
|
||||
|
||||
*sort-by-coordinates*: ++
|
||||
typeof: bool ++
|
||||
default: true ++
|
||||
default: false ++
|
||||
Should workspaces be sorted by coordinates. ++
|
||||
Note that if both *sort-by-name* and *sort-by-coordinates* are true sort-by name will be first. If both are false - sort by id will be performed.
|
||||
|
||||
*sort-by-number*: ++
|
||||
*sort-by-id*: ++
|
||||
typeof: bool ++
|
||||
default: false ++
|
||||
If set to true, workspace names will be sorted numerically. Takes precedence over any other sort-by option.
|
||||
Should workspaces be sorted by ID. Workspace ID will be sorted numerically when all ID are numbers. Takes precedence over any other sort-by option.
|
||||
|
||||
*all-outputs*: ++
|
||||
typeof: bool ++
|
||||
@@ -47,9 +47,16 @@ Addressed by *wlr/workspaces*
|
||||
default: false ++
|
||||
If set to true only active or urgent workspaces will be shown.
|
||||
|
||||
*ignore-hidden*: ++
|
||||
typeof: bool ++
|
||||
default: true ++
|
||||
If set to false hidden workspaces will be shown.
|
||||
|
||||
# FORMAT REPLACEMENTS
|
||||
|
||||
*{name}*: Name of workspace assigned by compositor
|
||||
*{name}*: Name of workspace assigned by compositor.
|
||||
|
||||
*{id}*: ID of workspace assigned by compositor.
|
||||
|
||||
*{icon}*: Icon, as defined in *format-icons*.
|
||||
|
||||
@@ -69,18 +76,18 @@ In addition to workspace name matching, the following *format-icons* can be set.
|
||||
# EXAMPLES
|
||||
|
||||
```
|
||||
"wlr/workspaces": {
|
||||
"ext/workspaces": {
|
||||
"format": "{name}: {icon}",
|
||||
"on-click": "activate",
|
||||
"format-icons": {
|
||||
"1": "",
|
||||
"2": "",
|
||||
"3": "",
|
||||
"4": "",
|
||||
"5": "",
|
||||
"Workspace 1": "",
|
||||
"Workspace 2": "",
|
||||
"Workspace 3": "",
|
||||
"Workspace 4": "",
|
||||
"active": "",
|
||||
"default": ""
|
||||
},
|
||||
"sort-by-number": true
|
||||
"sort-by-id": true
|
||||
}
|
||||
```
|
||||
|
||||
@@ -26,17 +26,49 @@ Addressed by *hyprland/workspaces*
|
||||
Regex rules to map window class to an icon or preferred method of representation for a workspace's window.
|
||||
Keys are the rules, while the values are the methods of representation. Values may use the placeholders {class} and {title} to use the window's original class and/or title respectively.
|
||||
Rules may specify `class<...>`, `title<...>`, or both in order to fine-tune the matching.
|
||||
You may assign an empty value to a rule to have it ignored from generating any representation in workspaces.
|
||||
You may assign an empty value to a rule to have it ignored from generating any representation in workspaces. ++
|
||||
This setting is ignored if *workspace-taskbar.enable* is set to true.
|
||||
|
||||
*window-rewrite-default*:
|
||||
*window-rewrite-default*: ++
|
||||
typeof: string ++
|
||||
default: "?" ++
|
||||
The default method of representation for a workspace's window. This will be used for windows whose classes do not match any of the rules in *window-rewrite*.
|
||||
The default method of representation for a workspace's window. This will be used for windows whose classes do not match any of the rules in *window-rewrite*. ++
|
||||
This setting is ignored if *workspace-taskbar.enable* is set to true.
|
||||
|
||||
*format-window-separator*: ++
|
||||
typeof: string ++
|
||||
default: " " ++
|
||||
The separator to be used between windows in a workspace.
|
||||
The separator to be used between windows in a workspace. ++
|
||||
This setting is ignored if *workspace-taskbar.enable* is set to true.
|
||||
|
||||
*workspace-taskbar*: ++
|
||||
typeof: object ++
|
||||
Contains settings for the workspace taskbar, an alternative mode for the workspaces module which displays the window icons as images instead of text.
|
||||
|
||||
*enable*: ++
|
||||
typeof: bool ++
|
||||
default: false ++
|
||||
Enables the workspace taskbar mode.
|
||||
|
||||
*format*: ++
|
||||
typeof: string ++
|
||||
default: {icon} ++
|
||||
Format to use for each window in the workspace taskbar. Available placeholders are {icon} and {title}.
|
||||
|
||||
*icon-size*: ++
|
||||
typeof: int ++
|
||||
default: 16 ++
|
||||
Size of the icons in the workspace taskbar.
|
||||
|
||||
*icon-theme*: ++
|
||||
typeof: string | array ++
|
||||
default: [] ++
|
||||
Icon theme to use for the workspace taskbar. If an array is provided, the first theme that is found for a given icon will be used. If no theme is found (or the array is empty), the default icon theme is used.
|
||||
|
||||
*orientation*: ++
|
||||
typeof: "horizontal" | "vertical" ++
|
||||
default: horizontal ++
|
||||
Direction in which the workspace taskbar is displayed.
|
||||
|
||||
*show-special*: ++
|
||||
typeof: bool ++
|
||||
@@ -184,3 +216,4 @@ Additional to workspace name matching, the following *format-icons* can be set.
|
||||
- *#workspaces button.special*
|
||||
- *#workspaces button.urgent*
|
||||
- *#workspaces button.hosting-monitor* (gets applied if workspace-monitor == waybar-monitor)
|
||||
- *#workspaces .taskbar-window* (each window in the taskbar)
|
||||
|
||||
@@ -204,6 +204,10 @@ Addressed by *mpd*
|
||||
|
||||
*{queueLength}*: The length of the current queue.
|
||||
|
||||
*{uri}*: The URI of the song relative to the MPD music directory.
|
||||
|
||||
*{filename}* The last part of the URI.
|
||||
|
||||
*{stateIcon}*: The icon corresponding to the playing or paused status of the player (see *state-icons* option)
|
||||
|
||||
*{consumeIcon}*: The icon corresponding the "consume" option (see *consume-icons* option)
|
||||
|
||||
@@ -16,6 +16,11 @@ Addressed by *network*
|
||||
typeof: string ++
|
||||
Use the defined interface instead of auto-detection. Accepts wildcard.
|
||||
|
||||
*rfkill*: ++
|
||||
typeof: bool ++
|
||||
default: true ++
|
||||
If enabled, the *disabled* format will be used when rfkill is blocking wlan interfaces.
|
||||
|
||||
*interval*: ++
|
||||
typeof: integer ++
|
||||
default: 60 ++
|
||||
@@ -49,7 +54,7 @@ Addressed by *network*
|
||||
|
||||
*format-disabled*: ++
|
||||
typeof: string ++
|
||||
This format is used when the displayed interface is disabled.
|
||||
This format is used when rfkill is blocking wlan interfaces.
|
||||
|
||||
*format-icons*: ++
|
||||
typeof: array/object ++
|
||||
@@ -127,7 +132,7 @@ Addressed by *network*
|
||||
|
||||
*tooltip-format-disabled*: ++
|
||||
typeof: string ++
|
||||
This format is used when the displayed interface is disabled.
|
||||
This format is used when rfkill is blocking wlan interfaces.
|
||||
|
||||
*menu*: ++
|
||||
typeof: string ++
|
||||
@@ -157,7 +162,7 @@ Addressed by *network*
|
||||
|
||||
*{netmask}*: The subnetmask corresponding to the IP(V4).
|
||||
|
||||
*{netmask6}*: The subnetmask corresponding to the IP(V6).
|
||||
*{netmask6}*: The subnetmask corresponding to the IP(V6).
|
||||
|
||||
*{cidr}*: The subnetmask corresponding to the IP(V4) in CIDR notation.
|
||||
|
||||
|
||||
@@ -83,6 +83,7 @@ Addressed by *river/layout*
|
||||
|
||||
- *#layout*
|
||||
- *#layout.focused* Applied when the output this module's bar belongs to is focused.
|
||||
- *#layout.<layout>* Applied when the output this module's bar belongs uses this layout.
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
|
||||
@@ -31,6 +31,11 @@ Addressed by *sway/workspaces*
|
||||
default: false ++
|
||||
If set to false, you can scroll to cycle through workspaces. If set to true this behaviour is disabled.
|
||||
|
||||
*reverse-scroll*: ++
|
||||
typeof: bool ++
|
||||
default: false ++
|
||||
If set to false, scrolling up will switch to the previous workspace and scrolling down will switch to the next workspace. If set to true, the behavior will be reversed.
|
||||
|
||||
*disable-click*: ++
|
||||
typeof: bool ++
|
||||
default: false ++
|
||||
|
||||
@@ -151,6 +151,20 @@ The visual display elements for waybar use a CSS stylesheet, see *waybar-styles(
|
||||
default: *false* ++
|
||||
Option to enable reloading the css style if a modification is detected on the style sheet file or any imported css files.
|
||||
|
||||
*on-sigusr1* ++
|
||||
typeof: string ++
|
||||
default: *toggle* ++
|
||||
Action that is performed when receiving SIGUSR1 kill signal. ++
|
||||
Possible values: *show*, *hide*, *toggle*, *reload*, *noop*. ++
|
||||
Default value: *toggle*.
|
||||
|
||||
*on-sigusr2* ++
|
||||
typeof: string ++
|
||||
default: *reload* ++
|
||||
Action that is performed when receiving SIGUSR2 kill signal. ++
|
||||
Possible values: *show*, *hide*, *toggle*, *reload*, *noop*. ++
|
||||
Default value: *reload*.
|
||||
|
||||
# MODULE FORMAT
|
||||
|
||||
You can use PangoMarkupFormat (See https://developer.gnome.org/pango/stable/PangoMarkupFormat.html#PangoMarkupFormat).
|
||||
@@ -206,14 +220,36 @@ A minimal *config* file could look like this:
|
||||
Waybar accepts the following signals:
|
||||
|
||||
*SIGUSR1*
|
||||
Toggles the bar visibility (hides if shown, shows if hidden)
|
||||
By default toggles the bar visibility (hides if shown, shows if hidden)
|
||||
*SIGUSR2*
|
||||
Reloads (resets) the bar
|
||||
By default reloads (resets) the bar
|
||||
*SIGINT*
|
||||
Quits the bar
|
||||
|
||||
For example, to toggle the bar programmatically, you can invoke `killall -SIGUSR1 waybar`.
|
||||
|
||||
## User signal configuration
|
||||
|
||||
Config parameters *on-sigusr1* and *on-sigusr2* change what happens when bars receive
|
||||
*SIGUSR1* and *SIGUSR2* signals.
|
||||
|
||||
This means that commands `killall -SIGUSR1 waybar` and `killall -SIGUSR2 waybar`
|
||||
can perform user-configured action.
|
||||
|
||||
It also means that if an external script has the PID of the bar then it can
|
||||
perform more complex `show`/`hide`/`reload` logic for each instance of Waybar.
|
||||
One can find the PID e.g. by doing `pgrep -a waybar` which could then match
|
||||
by config name or other parameters.
|
||||
|
||||
## Kill parameter meanings
|
||||
|
||||
*show* Switches state to visible (per bar).
|
||||
*hide* Switches state to hidden (per bar).
|
||||
*toggle* Switches state between visible and hidden (per bar).
|
||||
*reload* Reloads all waybars of current waybar process (basically equivalent to
|
||||
restarting with updated config which sets initial visibility values).
|
||||
*noop* Does nothing when the kill signal is received.
|
||||
|
||||
# MULTI OUTPUT CONFIGURATION
|
||||
|
||||
## Limit a configuration to some outputs
|
||||
|
||||
25
meson.build
25
meson.build
@@ -1,6 +1,6 @@
|
||||
project(
|
||||
'waybar', 'cpp', 'c',
|
||||
version: '0.13.0',
|
||||
version: '0.14.0',
|
||||
license: 'MIT',
|
||||
meson_version: '>= 0.59.0',
|
||||
default_options : [
|
||||
@@ -183,6 +183,7 @@ src_files = files(
|
||||
'src/util/sanitize_str.cpp',
|
||||
'src/util/rewrite_string.cpp',
|
||||
'src/util/gtk_icon.cpp',
|
||||
'src/util/icon_loader.cpp',
|
||||
'src/util/regex_collection.cpp',
|
||||
'src/util/css_reload_helper.cpp'
|
||||
)
|
||||
@@ -276,6 +277,17 @@ if true
|
||||
man_files += files('man/waybar-wlr-taskbar.5.scd')
|
||||
endif
|
||||
|
||||
if wayland_protos.version().version_compare('>=1.39')
|
||||
add_project_arguments('-DHAVE_EXT_WORKSPACES', language: 'cpp')
|
||||
src_files += files(
|
||||
'src/modules/ext/workspace_manager.cpp',
|
||||
'src/modules/ext/workspace_manager_binding.cpp',
|
||||
)
|
||||
man_files += files(
|
||||
'man/waybar-ext-workspaces.5.scd',
|
||||
)
|
||||
endif
|
||||
|
||||
if true
|
||||
add_project_arguments('-DHAVE_RIVER', language: 'cpp')
|
||||
src_files += files(
|
||||
@@ -485,17 +497,6 @@ else
|
||||
man_files += files('man/waybar-clock.5.scd')
|
||||
endif
|
||||
|
||||
if get_option('experimental')
|
||||
add_project_arguments('-DHAVE_WLR_WORKSPACES', language: 'cpp')
|
||||
src_files += files(
|
||||
'src/modules/wlr/workspace_manager.cpp',
|
||||
'src/modules/wlr/workspace_manager_binding.cpp',
|
||||
)
|
||||
man_files += files(
|
||||
'man/waybar-wlr-workspaces.5.scd',
|
||||
)
|
||||
endif
|
||||
|
||||
cava = dependency('cava',
|
||||
version : '>=0.10.4',
|
||||
required: get_option('cava'),
|
||||
|
||||
@@ -1,306 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<protocol name="ext_workspace_unstable_v1">
|
||||
<copyright>
|
||||
Copyright © 2019 Christopher Billington
|
||||
Copyright © 2020 Ilia Bozhinov
|
||||
|
||||
Permission to use, copy, modify, distribute, and sell this
|
||||
software and its documentation for any purpose is hereby granted
|
||||
without fee, provided that the above copyright notice appear in
|
||||
all copies and that both that copyright notice and this permission
|
||||
notice appear in supporting documentation, and that the name of
|
||||
the copyright holders not be used in advertising or publicity
|
||||
pertaining to distribution of the software without specific,
|
||||
written prior permission. The copyright holders make no
|
||||
representations about the suitability of this software for any
|
||||
purpose. It is provided "as is" without express or implied
|
||||
warranty.
|
||||
|
||||
THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
|
||||
SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
|
||||
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
|
||||
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
||||
THIS SOFTWARE.
|
||||
</copyright>
|
||||
|
||||
<interface name="zext_workspace_manager_v1" version="1">
|
||||
<description summary="list and control workspaces">
|
||||
Workspaces, also called virtual desktops, are groups of surfaces. A
|
||||
compositor with a concept of workspaces may only show some such groups of
|
||||
surfaces (those of 'active' workspaces) at a time. 'Activating' a
|
||||
workspace is a request for the compositor to display that workspace's
|
||||
surfaces as normal, whereas the compositor may hide or otherwise
|
||||
de-emphasise surfaces that are associated only with 'inactive' workspaces.
|
||||
Workspaces are grouped by which sets of outputs they correspond to, and
|
||||
may contain surfaces only from those outputs. In this way, it is possible
|
||||
for each output to have its own set of workspaces, or for all outputs (or
|
||||
any other arbitrary grouping) to share workspaces. Compositors may
|
||||
optionally conceptually arrange each group of workspaces in an
|
||||
N-dimensional grid.
|
||||
|
||||
The purpose of this protocol is to enable the creation of taskbars and
|
||||
docks by providing them with a list of workspaces and their properties,
|
||||
and allowing them to activate and deactivate workspaces.
|
||||
|
||||
After a client binds the zext_workspace_manager_v1, each workspace will be
|
||||
sent via the workspace event.
|
||||
</description>
|
||||
|
||||
<event name="workspace_group">
|
||||
<description summary="a workspace group has been created">
|
||||
This event is emitted whenever a new workspace group has been created.
|
||||
|
||||
All initial details of the workspace group (workspaces, outputs) will be
|
||||
sent immediately after this event via the corresponding events in
|
||||
zext_workspace_group_handle_v1.
|
||||
</description>
|
||||
<arg name="workspace_group" type="new_id" interface="zext_workspace_group_handle_v1"/>
|
||||
</event>
|
||||
|
||||
<request name="commit">
|
||||
<description summary="all requests about the workspaces have been sent">
|
||||
The client must send this request after it has finished sending other
|
||||
requests. The compositor must process a series of requests preceding a
|
||||
commit request atomically.
|
||||
|
||||
This allows changes to the workspace properties to be seen as atomic,
|
||||
even if they happen via multiple events, and even if they involve
|
||||
multiple zext_workspace_handle_v1 objects, for example, deactivating one
|
||||
workspace and activating another.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<event name="done">
|
||||
<description summary="all information about the workspace groups has been sent">
|
||||
This event is sent after all changes in all workspace groups have been
|
||||
sent.
|
||||
|
||||
This allows changes to one or more zext_workspace_group_handle_v1
|
||||
properties to be seen as atomic, even if they happen via multiple
|
||||
events. In particular, an output moving from one workspace group to
|
||||
another sends an output_enter event and an output_leave event to the two
|
||||
zext_workspace_group_handle_v1 objects in question. The compositor sends
|
||||
the done event only after updating the output information in both
|
||||
workspace groups.
|
||||
</description>
|
||||
</event>
|
||||
|
||||
<event name="finished">
|
||||
<description summary="the compositor has finished with the workspace_manager">
|
||||
This event indicates that the compositor is done sending events to the
|
||||
zext_workspace_manager_v1. The server will destroy the object
|
||||
immediately after sending this request, so it will become invalid and
|
||||
the client should free any resources associated with it.
|
||||
</description>
|
||||
</event>
|
||||
|
||||
<request name="stop">
|
||||
<description summary="stop sending events">
|
||||
Indicates the client no longer wishes to receive events for new
|
||||
workspace groups. However the compositor may emit further workspace
|
||||
events, until the finished event is emitted.
|
||||
|
||||
The client must not send any more requests after this one.
|
||||
</description>
|
||||
</request>
|
||||
</interface>
|
||||
|
||||
<interface name="zext_workspace_group_handle_v1" version="1">
|
||||
<description summary="a workspace group assigned to a set of outputs">
|
||||
A zext_workspace_group_handle_v1 object represents a a workspace group
|
||||
that is assigned a set of outputs and contains a number of workspaces.
|
||||
|
||||
The set of outputs assigned to the workspace group is conveyed to the client via
|
||||
output_enter and output_leave events, and its workspaces are conveyed with
|
||||
workspace events.
|
||||
|
||||
For example, a compositor which has a set of workspaces for each output may
|
||||
advertise a workspace group (and its workspaces) per output, whereas a compositor
|
||||
where a workspace spans all outputs may advertise a single workspace group for all
|
||||
outputs.
|
||||
</description>
|
||||
|
||||
<event name="output_enter">
|
||||
<description summary="output assigned to workspace group">
|
||||
This event is emitted whenever an output is assigned to the workspace
|
||||
group.
|
||||
</description>
|
||||
<arg name="output" type="object" interface="wl_output"/>
|
||||
</event>
|
||||
|
||||
<event name="output_leave">
|
||||
<description summary="output removed from workspace group">
|
||||
This event is emitted whenever an output is removed from the workspace
|
||||
group.
|
||||
</description>
|
||||
<arg name="output" type="object" interface="wl_output"/>
|
||||
</event>
|
||||
|
||||
<event name="workspace">
|
||||
<description summary="workspace added to workspace group">
|
||||
This event is emitted whenever a new workspace has been created.
|
||||
|
||||
All initial details of the workspace (name, coordinates, state) will
|
||||
be sent immediately after this event via the corresponding events in
|
||||
zext_workspace_handle_v1.
|
||||
</description>
|
||||
<arg name="workspace" type="new_id" interface="zext_workspace_handle_v1"/>
|
||||
</event>
|
||||
|
||||
<event name="remove">
|
||||
<description summary="this workspace group has been destroyed">
|
||||
This event means the zext_workspace_group_handle_v1 has been destroyed.
|
||||
It is guaranteed there won't be any more events for this
|
||||
zext_workspace_group_handle_v1. The zext_workspace_group_handle_v1 becomes
|
||||
inert so any requests will be ignored except the destroy request.
|
||||
|
||||
The compositor must remove all workspaces belonging to a workspace group
|
||||
before removing the workspace group.
|
||||
</description>
|
||||
</event>
|
||||
|
||||
<request name="create_workspace">
|
||||
<description summary="create a new workspace">
|
||||
Request that the compositor create a new workspace with the given name.
|
||||
|
||||
There is no guarantee that the compositor will create a new workspace,
|
||||
or that the created workspace will have the provided name.
|
||||
</description>
|
||||
<arg name="workspace" type="string"/>
|
||||
</request>
|
||||
|
||||
<request name="destroy" type="destructor">
|
||||
<description summary="destroy the zext_workspace_handle_v1 object">
|
||||
Destroys the zext_workspace_handle_v1 object.
|
||||
|
||||
This request should be called either when the client does not want to
|
||||
use the workspace object any more or after the remove event to finalize
|
||||
the destruction of the object.
|
||||
</description>
|
||||
</request>
|
||||
</interface>
|
||||
|
||||
<interface name="zext_workspace_handle_v1" version="1">
|
||||
<description summary="a workspace handing a group of surfaces">
|
||||
A zext_workspace_handle_v1 object represents a a workspace that handles a
|
||||
group of surfaces.
|
||||
|
||||
Each workspace has a name, conveyed to the client with the name event; a
|
||||
list of states, conveyed to the client with the state event; and
|
||||
optionally a set of coordinates, conveyed to the client with the
|
||||
coordinates event. The client may request that the compositor activate or
|
||||
deactivate the workspace.
|
||||
|
||||
Each workspace can belong to only a single workspace group.
|
||||
Depepending on the compositor policy, there might be workspaces with
|
||||
the same name in different workspace groups, but these workspaces are still
|
||||
separate (e.g. one of them might be active while the other is not).
|
||||
</description>
|
||||
|
||||
<event name="name">
|
||||
<description summary="workspace name changed">
|
||||
This event is emitted immediately after the zext_workspace_handle_v1 is
|
||||
created and whenever the name of the workspace changes.
|
||||
</description>
|
||||
<arg name="name" type="string"/>
|
||||
</event>
|
||||
|
||||
<event name="coordinates">
|
||||
<description summary="workspace coordinates changed">
|
||||
This event is used to organize workspaces into an N-dimensional grid
|
||||
within a workspace group, and if supported, is emitted immediately after
|
||||
the zext_workspace_handle_v1 is created and whenever the coordinates of
|
||||
the workspace change. Compositors may not send this event if they do not
|
||||
conceptually arrange workspaces in this way. If compositors simply
|
||||
number workspaces, without any geometric interpretation, they may send
|
||||
1D coordinates, which clients should not interpret as implying any
|
||||
geometry. Sending an empty array means that the compositor no longer
|
||||
orders the workspace geometrically.
|
||||
|
||||
Coordinates have an arbitrary number of dimensions N with an uint32
|
||||
position along each dimension. By convention if N > 1, the first
|
||||
dimension is X, the second Y, the third Z, and so on. The compositor may
|
||||
chose to utilize these events for a more novel workspace layout
|
||||
convention, however. No guarantee is made about the grid being filled or
|
||||
bounded; there may be a workspace at coordinate 1 and another at
|
||||
coordinate 1000 and none in between. Within a workspace group, however,
|
||||
workspaces must have unique coordinates of equal dimensionality.
|
||||
</description>
|
||||
<arg name="coordinates" type="array"/>
|
||||
</event>
|
||||
|
||||
<event name="state">
|
||||
<description summary="the state of the workspace changed">
|
||||
This event is emitted immediately after the zext_workspace_handle_v1 is
|
||||
created and each time the workspace state changes, either because of a
|
||||
compositor action or because of a request in this protocol.
|
||||
</description>
|
||||
<arg name="state" type="array"/>
|
||||
</event>
|
||||
|
||||
<enum name="state">
|
||||
<description summary="types of states on the workspace">
|
||||
The different states that a workspace can have.
|
||||
</description>
|
||||
|
||||
<entry name="active" value="0" summary="the workspace is active"/>
|
||||
<entry name="urgent" value="1" summary="the workspace requests attention"/>
|
||||
<entry name="hidden" value="2">
|
||||
<description summary="the workspace is not visible">
|
||||
The workspace is not visible in its workspace group, and clients
|
||||
attempting to visualize the compositor workspace state should not
|
||||
display such workspaces.
|
||||
</description>
|
||||
</entry>
|
||||
</enum>
|
||||
|
||||
<event name="remove">
|
||||
<description summary="this workspace has been destroyed">
|
||||
This event means the zext_workspace_handle_v1 has been destroyed. It is
|
||||
guaranteed there won't be any more events for this
|
||||
zext_workspace_handle_v1. The zext_workspace_handle_v1 becomes inert so
|
||||
any requests will be ignored except the destroy request.
|
||||
</description>
|
||||
</event>
|
||||
|
||||
<request name="destroy" type="destructor">
|
||||
<description summary="destroy the zext_workspace_handle_v1 object">
|
||||
Destroys the zext_workspace_handle_v1 object.
|
||||
|
||||
This request should be called either when the client does not want to
|
||||
use the workspace object any more or after the remove event to finalize
|
||||
the destruction of the object.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<request name="activate">
|
||||
<description summary="activate the workspace">
|
||||
Request that this workspace be activated.
|
||||
|
||||
There is no guarantee the workspace will be actually activated, and
|
||||
behaviour may be compositor-dependent. For example, activating a
|
||||
workspace may or may not deactivate all other workspaces in the same
|
||||
group.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<request name="deactivate">
|
||||
<description summary="activate the workspace">
|
||||
Request that this workspace be deactivated.
|
||||
|
||||
There is no guarantee the workspace will be actually deactivated.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<request name="remove">
|
||||
<description summary="remove the workspace">
|
||||
Request that this workspace be removed.
|
||||
|
||||
There is no guarantee the workspace will be actually removed.
|
||||
</description>
|
||||
</request>
|
||||
</interface>
|
||||
</protocol>
|
||||
@@ -26,12 +26,17 @@ client_protocols = [
|
||||
[wl_protocol_dir, 'unstable/xdg-output/xdg-output-unstable-v1.xml'],
|
||||
[wl_protocol_dir, 'unstable/idle-inhibit/idle-inhibit-unstable-v1.xml'],
|
||||
['wlr-foreign-toplevel-management-unstable-v1.xml'],
|
||||
['ext-workspace-unstable-v1.xml'],
|
||||
['river-status-unstable-v1.xml'],
|
||||
['river-control-unstable-v1.xml'],
|
||||
['dwl-ipc-unstable-v2.xml'],
|
||||
]
|
||||
|
||||
if wayland_protos.version().version_compare('>=1.39')
|
||||
client_protocols += [
|
||||
[wl_protocol_dir, 'staging/ext-workspace/ext-workspace-v1.xml']
|
||||
]
|
||||
endif
|
||||
|
||||
client_protos_src = []
|
||||
client_protos_headers = []
|
||||
|
||||
|
||||
@@ -200,7 +200,7 @@ std::string ALabel::getState(uint8_t value, bool lesser) {
|
||||
}
|
||||
}
|
||||
// Sort states
|
||||
std::sort(states.begin(), states.end(), [&lesser](auto& a, auto& b) {
|
||||
std::ranges::sort(states.begin(), states.end(), [&lesser](auto& a, auto& b) {
|
||||
return lesser ? a.second < b.second : a.second > b.second;
|
||||
});
|
||||
std::string valid_state;
|
||||
|
||||
39
src/bar.cpp
39
src/bar.cpp
@@ -8,6 +8,8 @@
|
||||
#include "client.hpp"
|
||||
#include "factory.hpp"
|
||||
#include "group.hpp"
|
||||
#include "util/enum.hpp"
|
||||
#include "util/kill_signal.hpp"
|
||||
|
||||
#ifdef HAVE_SWAY
|
||||
#include "modules/sway/bar.hpp"
|
||||
@@ -277,6 +279,32 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config)
|
||||
}
|
||||
#endif
|
||||
|
||||
waybar::util::EnumParser<util::KillSignalAction> m_signalActionEnumParser;
|
||||
const auto& configSigusr1 = config["on-sigusr1"];
|
||||
if (configSigusr1.isString()) {
|
||||
auto strSigusr1 = configSigusr1.asString();
|
||||
try {
|
||||
onSigusr1 =
|
||||
m_signalActionEnumParser.parseStringToEnum(strSigusr1, util::userKillSignalActions);
|
||||
} catch (const std::invalid_argument& e) {
|
||||
onSigusr1 = util::SIGNALACTION_DEFAULT_SIGUSR1;
|
||||
spdlog::warn(
|
||||
"Invalid string representation for on-sigusr1. Falling back to default mode (toggle).");
|
||||
}
|
||||
}
|
||||
const auto& configSigusr2 = config["on-sigusr2"];
|
||||
if (configSigusr2.isString()) {
|
||||
auto strSigusr2 = configSigusr2.asString();
|
||||
try {
|
||||
onSigusr2 =
|
||||
m_signalActionEnumParser.parseStringToEnum(strSigusr2, util::userKillSignalActions);
|
||||
} catch (const std::invalid_argument& e) {
|
||||
onSigusr2 = util::SIGNALACTION_DEFAULT_SIGUSR2;
|
||||
spdlog::warn(
|
||||
"Invalid string representation for on-sigusr2. Falling back to default mode (reload).");
|
||||
}
|
||||
}
|
||||
|
||||
setupWidgets();
|
||||
window.show_all();
|
||||
|
||||
@@ -422,6 +450,8 @@ void waybar::Bar::setVisible(bool value) {
|
||||
}
|
||||
|
||||
void waybar::Bar::toggle() { setVisible(!visible); }
|
||||
void waybar::Bar::show() { setVisible(true); }
|
||||
void waybar::Bar::hide() { setVisible(false); }
|
||||
|
||||
// Converting string to button code rn as to avoid doing it later
|
||||
void waybar::Bar::setupAltFormatKeyForModule(const std::string& module_name) {
|
||||
@@ -479,6 +509,9 @@ void waybar::Bar::handleSignal(int signal) {
|
||||
}
|
||||
}
|
||||
|
||||
waybar::util::KillSignalAction waybar::Bar::getOnSigusr1Action() { return this->onSigusr1; }
|
||||
waybar::util::KillSignalAction waybar::Bar::getOnSigusr2Action() { return this->onSigusr2; }
|
||||
|
||||
void waybar::Bar::getModules(const Factory& factory, const std::string& pos,
|
||||
waybar::Group* group = nullptr) {
|
||||
auto module_list = group != nullptr ? config[pos]["modules"] : config[pos];
|
||||
@@ -496,7 +529,11 @@ void waybar::Bar::getModules(const Factory& factory, const std::string& pos,
|
||||
auto vertical = (group != nullptr ? group->getBox().get_orientation()
|
||||
: box_.get_orientation()) == Gtk::ORIENTATION_VERTICAL;
|
||||
|
||||
auto* group_module = new waybar::Group(id_name, class_name, config[ref], vertical);
|
||||
auto group_config = config[ref];
|
||||
if (group_config["modules"].isNull()) {
|
||||
spdlog::warn("Group definition '{}' has not been found, group will be hidden", ref);
|
||||
}
|
||||
auto* group_module = new waybar::Group(id_name, class_name, group_config, vertical);
|
||||
getModules(factory, ref, group_module);
|
||||
module = group_module;
|
||||
} else {
|
||||
|
||||
@@ -151,15 +151,19 @@ void waybar::Client::handleDeferredMonitorRemoval(Glib::RefPtr<Gdk::Monitor> mon
|
||||
|
||||
const std::string waybar::Client::getStyle(const std::string &style,
|
||||
std::optional<Appearance> appearance = std::nullopt) {
|
||||
auto gtk_settings = Gtk::Settings::get_default();
|
||||
std::optional<std::string> css_file;
|
||||
|
||||
if (style.empty()) {
|
||||
std::vector<std::string> search_files;
|
||||
switch (appearance.value_or(portal->getAppearance())) {
|
||||
case waybar::Appearance::LIGHT:
|
||||
search_files.emplace_back("style-light.css");
|
||||
gtk_settings->property_gtk_application_prefer_dark_theme() = false;
|
||||
break;
|
||||
case waybar::Appearance::DARK:
|
||||
search_files.emplace_back("style-dark.css");
|
||||
gtk_settings->property_gtk_application_prefer_dark_theme() = true;
|
||||
break;
|
||||
case waybar::Appearance::UNKNOWN:
|
||||
break;
|
||||
@@ -169,9 +173,11 @@ const std::string waybar::Client::getStyle(const std::string &style,
|
||||
} else {
|
||||
css_file = style;
|
||||
}
|
||||
|
||||
if (!css_file) {
|
||||
throw std::runtime_error("Missing required resource files");
|
||||
}
|
||||
|
||||
spdlog::info("Using CSS file {}", css_file.value());
|
||||
return css_file.value();
|
||||
};
|
||||
|
||||
@@ -2,7 +2,11 @@
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <unistd.h>
|
||||
#ifndef __OpenBSD__
|
||||
#include <wordexp.h>
|
||||
#else
|
||||
#include <glob.h>
|
||||
#endif
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
@@ -34,6 +38,7 @@ std::vector<std::string> Config::tryExpandPath(const std::string &base,
|
||||
spdlog::debug("Try expanding: {}", path.string());
|
||||
|
||||
std::vector<std::string> results;
|
||||
#ifndef __OpenBSD__
|
||||
wordexp_t p;
|
||||
if (wordexp(path.c_str(), &p, 0) == 0) {
|
||||
for (size_t i = 0; i < p.we_wordc; i++) {
|
||||
@@ -44,6 +49,18 @@ std::vector<std::string> Config::tryExpandPath(const std::string &base,
|
||||
}
|
||||
wordfree(&p);
|
||||
}
|
||||
#else
|
||||
glob_t p;
|
||||
if (glob(path.c_str(), 0, NULL, &p) == 0) {
|
||||
for (size_t i = 0; i < p.gl_pathc; i++) {
|
||||
if (access(p.gl_pathv[i], F_OK) == 0) {
|
||||
results.emplace_back(p.gl_pathv[i]);
|
||||
spdlog::debug("Found config file: {}", p.gl_pathv[i]);
|
||||
}
|
||||
}
|
||||
globfree(&p);
|
||||
}
|
||||
#endif
|
||||
|
||||
return results;
|
||||
}
|
||||
@@ -89,19 +106,33 @@ void Config::setupConfig(Json::Value &dst, const std::string &config_file, int d
|
||||
mergeConfig(dst, tmp_config);
|
||||
}
|
||||
|
||||
std::optional<std::string> Config::findIncludePath(const std::string &name) {
|
||||
auto match1 = tryExpandPath(name, "");
|
||||
if (!match1.empty()) {
|
||||
return match1.front();
|
||||
}
|
||||
return findConfigPath({name});
|
||||
}
|
||||
|
||||
void Config::resolveConfigIncludes(Json::Value &config, int depth) {
|
||||
Json::Value includes = config["include"];
|
||||
if (includes.isArray()) {
|
||||
for (const auto &include : includes) {
|
||||
spdlog::info("Including resource file: {}", include.asString());
|
||||
for (const auto &match : tryExpandPath(include.asString(), "")) {
|
||||
setupConfig(config, match, depth + 1);
|
||||
auto match = findIncludePath(include.asString());
|
||||
if (match.has_value()) {
|
||||
setupConfig(config, match.value(), depth + 1);
|
||||
} else {
|
||||
spdlog::warn("Unable to find resource file: {}", include.asString());
|
||||
}
|
||||
}
|
||||
} else if (includes.isString()) {
|
||||
spdlog::info("Including resource file: {}", includes.asString());
|
||||
for (const auto &match : tryExpandPath(includes.asString(), "")) {
|
||||
setupConfig(config, match, depth + 1);
|
||||
auto match = findIncludePath(includes.asString());
|
||||
if (match.has_value()) {
|
||||
setupConfig(config, match.value(), depth + 1);
|
||||
} else {
|
||||
spdlog::warn("Unable to find resource file: {}", includes.asString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
#ifdef HAVE_WLR_TASKBAR
|
||||
#include "modules/wlr/taskbar.hpp"
|
||||
#endif
|
||||
#ifdef HAVE_WLR_WORKSPACES
|
||||
#include "modules/wlr/workspace_manager.hpp"
|
||||
#ifdef HAVE_EXT_WORKSPACES
|
||||
#include "modules/ext/workspace_manager.hpp"
|
||||
#endif
|
||||
#ifdef HAVE_RIVER
|
||||
#include "modules/river/layout.hpp"
|
||||
@@ -178,9 +178,9 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name,
|
||||
return new waybar::modules::wlr::Taskbar(id, bar_, config_[name]);
|
||||
}
|
||||
#endif
|
||||
#ifdef HAVE_WLR_WORKSPACES
|
||||
if (ref == "wlr/workspaces") {
|
||||
return new waybar::modules::wlr::WorkspaceManager(id, bar_, config_[name]);
|
||||
#ifdef HAVE_EXT_WORKSPACES
|
||||
if (ref == "ext/workspaces") {
|
||||
return new waybar::modules::ext::WorkspaceManager(id, bar_, config_[name]);
|
||||
}
|
||||
#endif
|
||||
#ifdef HAVE_RIVER
|
||||
|
||||
50
src/main.cpp
50
src/main.cpp
@@ -7,6 +7,7 @@
|
||||
#include <list>
|
||||
#include <mutex>
|
||||
|
||||
#include "bar.hpp"
|
||||
#include "client.hpp"
|
||||
#include "util/SafeSignal.hpp"
|
||||
|
||||
@@ -71,6 +72,45 @@ static void catchSignals(waybar::SafeSignal<int>& signal_handler) {
|
||||
}
|
||||
}
|
||||
|
||||
waybar::util::KillSignalAction getActionForBar(waybar::Bar* bar, int signal) {
|
||||
switch (signal) {
|
||||
case SIGUSR1:
|
||||
return bar->getOnSigusr1Action();
|
||||
case SIGUSR2:
|
||||
return bar->getOnSigusr2Action();
|
||||
default:
|
||||
return waybar::util::KillSignalAction::NOOP;
|
||||
}
|
||||
}
|
||||
|
||||
void handleUserSignal(int signal, bool& reload) {
|
||||
int i = 0;
|
||||
for (auto& bar : waybar::Client::inst()->bars) {
|
||||
switch (getActionForBar(bar.get(), signal)) {
|
||||
case waybar::util::KillSignalAction::HIDE:
|
||||
spdlog::debug("Visibility 'hide' for bar ", i);
|
||||
bar->hide();
|
||||
break;
|
||||
case waybar::util::KillSignalAction::SHOW:
|
||||
spdlog::debug("Visibility 'show' for bar ", i);
|
||||
bar->show();
|
||||
break;
|
||||
case waybar::util::KillSignalAction::TOGGLE:
|
||||
spdlog::debug("Visibility 'toggle' for bar ", i);
|
||||
bar->toggle();
|
||||
break;
|
||||
case waybar::util::KillSignalAction::RELOAD:
|
||||
spdlog::info("Reloading...");
|
||||
reload = true;
|
||||
waybar::Client::inst()->reset();
|
||||
return;
|
||||
case waybar::util::KillSignalAction::NOOP:
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
// Must be called on the main thread.
|
||||
//
|
||||
// If this signal should restart or close the bar, this function will write
|
||||
@@ -80,21 +120,15 @@ static void handleSignalMainThread(int signum, bool& reload) {
|
||||
for (auto& bar : waybar::Client::inst()->bars) {
|
||||
bar->handleSignal(signum);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
switch (signum) {
|
||||
case SIGUSR1:
|
||||
spdlog::debug("Visibility toggled");
|
||||
for (auto& bar : waybar::Client::inst()->bars) {
|
||||
bar->toggle();
|
||||
}
|
||||
handleUserSignal(SIGUSR1, reload);
|
||||
break;
|
||||
case SIGUSR2:
|
||||
spdlog::info("Reloading...");
|
||||
reload = true;
|
||||
waybar::Client::inst()->reset();
|
||||
handleUserSignal(SIGUSR2, reload);
|
||||
break;
|
||||
case SIGINT:
|
||||
spdlog::info("Quitting.");
|
||||
|
||||
@@ -38,10 +38,20 @@ auto waybar::modules::Backlight::update() -> void {
|
||||
event_box_.show();
|
||||
const uint8_t percent =
|
||||
best->get_max() == 0 ? 100 : round(best->get_actual() * 100.0f / best->get_max());
|
||||
std::string desc = fmt::format(fmt::runtime(format_), fmt::arg("percent", percent),
|
||||
|
||||
// Get the state and apply state-specific format if available
|
||||
auto state = getState(percent);
|
||||
std::string current_format = format_;
|
||||
if (!state.empty()) {
|
||||
std::string state_format_name = "format-" + state;
|
||||
if (config_[state_format_name].isString()) {
|
||||
current_format = config_[state_format_name].asString();
|
||||
}
|
||||
}
|
||||
|
||||
std::string desc = fmt::format(fmt::runtime(current_format), fmt::arg("percent", percent),
|
||||
fmt::arg("icon", getIcon(percent)));
|
||||
label_.set_markup(desc);
|
||||
getState(percent);
|
||||
if (tooltipEnabled()) {
|
||||
std::string tooltip_format;
|
||||
if (config_["tooltip-format"].isString()) {
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
#include "modules/battery.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
|
||||
#include "util/command.hpp"
|
||||
#if defined(__FreeBSD__)
|
||||
#include <sys/sysctl.h>
|
||||
#endif
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <iostream>
|
||||
waybar::modules::Battery::Battery(const std::string& id, const Bar& bar, const Json::Value& config)
|
||||
: ALabel(config, "battery", id, "{capacity}%", 60), bar_(bar) {
|
||||
: ALabel(config, "battery", id, "{capacity}%", 60), last_event_(""), bar_(bar) {
|
||||
#if defined(__linux__)
|
||||
battery_watch_fd_ = inotify_init1(IN_CLOEXEC);
|
||||
if (battery_watch_fd_ == -1) {
|
||||
@@ -26,6 +28,7 @@ waybar::modules::Battery::Battery(const std::string& id, const Bar& bar, const J
|
||||
throw std::runtime_error("Could not watch for battery plug/unplug");
|
||||
}
|
||||
#endif
|
||||
spdlog::debug("battery: worker interval is {}", interval_.count());
|
||||
worker();
|
||||
}
|
||||
|
||||
@@ -677,18 +680,22 @@ auto waybar::modules::Battery::update() -> void {
|
||||
}
|
||||
auto status_pretty = status;
|
||||
// Transform to lowercase and replace space with dash
|
||||
std::transform(status.begin(), status.end(), status.begin(),
|
||||
[](char ch) { return ch == ' ' ? '-' : std::tolower(ch); });
|
||||
std::ranges::transform(status.begin(), status.end(), status.begin(),
|
||||
[](char ch) { return ch == ' ' ? '-' : std::tolower(ch); });
|
||||
auto format = format_;
|
||||
auto state = getState(capacity, true);
|
||||
processEvents(state, status, capacity);
|
||||
setBarClass(state);
|
||||
auto time_remaining_formatted = formatTimeRemaining(time_remaining);
|
||||
if (tooltipEnabled()) {
|
||||
std::string tooltip_text_default;
|
||||
std::string tooltip_format = "{timeTo}";
|
||||
if (time_remaining != 0) {
|
||||
std::string time_to = std::string("Time to ") + ((time_remaining > 0) ? "empty" : "full");
|
||||
tooltip_text_default = time_to + ": " + time_remaining_formatted;
|
||||
if (time_remaining > 0) {
|
||||
tooltip_text_default = std::string("Empty in ") + time_remaining_formatted;
|
||||
} else {
|
||||
tooltip_text_default = std::string("Full in ") + time_remaining_formatted;
|
||||
}
|
||||
} else {
|
||||
tooltip_text_default = status_pretty;
|
||||
}
|
||||
@@ -701,7 +708,7 @@ auto waybar::modules::Battery::update() -> void {
|
||||
} else if (config_["tooltip-format"].isString()) {
|
||||
tooltip_format = config_["tooltip-format"].asString();
|
||||
}
|
||||
label_.set_tooltip_text(
|
||||
label_.set_tooltip_markup(
|
||||
fmt::format(fmt::runtime(tooltip_format), fmt::arg("timeTo", tooltip_text_default),
|
||||
fmt::arg("power", power), fmt::arg("capacity", capacity),
|
||||
fmt::arg("time", time_remaining_formatted), fmt::arg("cycles", cycles),
|
||||
@@ -767,3 +774,25 @@ void waybar::modules::Battery::setBarClass(std::string& state) {
|
||||
bar_.window.get_style_context()->add_class(new_class);
|
||||
}
|
||||
}
|
||||
|
||||
void waybar::modules::Battery::processEvents(std::string& state, std::string& status,
|
||||
uint8_t capacity) {
|
||||
// There are no events specified, skip
|
||||
auto events = config_["events"];
|
||||
if (!events.isObject() || events.empty()) {
|
||||
return;
|
||||
}
|
||||
std::string event_name = fmt::format("on-{}-{}", status == "discharging" ? status : "charging",
|
||||
state.empty() ? std::to_string(capacity) : state);
|
||||
if (last_event_ != event_name) {
|
||||
spdlog::debug("battery: triggering event {}", event_name);
|
||||
if (events[event_name].isString()) {
|
||||
std::string exec = events[event_name].asString();
|
||||
// Execute the command if it is not empty
|
||||
if (!exec.empty()) {
|
||||
util::command::exec(exec, "");
|
||||
}
|
||||
}
|
||||
last_event_ = event_name;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,11 +5,12 @@
|
||||
|
||||
std::vector<float> waybar::modules::CpuFrequency::parseCpuFrequencies() {
|
||||
std::vector<float> frequencies;
|
||||
char buffer[256];
|
||||
size_t len;
|
||||
int32_t freq;
|
||||
uint32_t i = 0;
|
||||
|
||||
#ifndef __OpenBSD__
|
||||
char buffer[256];
|
||||
uint32_t i = 0;
|
||||
while (true) {
|
||||
len = 4;
|
||||
snprintf(buffer, 256, "dev.cpu.%u.freq", i);
|
||||
@@ -17,6 +18,12 @@ std::vector<float> waybar::modules::CpuFrequency::parseCpuFrequencies() {
|
||||
frequencies.push_back(freq);
|
||||
++i;
|
||||
}
|
||||
#else
|
||||
int getMhz[] = {CTL_HW, HW_CPUSPEED};
|
||||
len = sizeof(freq);
|
||||
sysctl(getMhz, 2, &freq, &len, NULL, 0);
|
||||
frequencies.push_back((float)freq);
|
||||
#endif
|
||||
|
||||
if (frequencies.empty()) {
|
||||
spdlog::warn("cpu/bsd: parseCpuFrequencies failed, not found in sysctl");
|
||||
|
||||
@@ -14,6 +14,9 @@ waybar::modules::Custom::Custom(const std::string& name, const std::string& id,
|
||||
percentage_(0),
|
||||
fp_(nullptr),
|
||||
pid_(-1) {
|
||||
if (config.isNull()) {
|
||||
spdlog::warn("There is no configuration for 'custom/{}', element will be hidden", name);
|
||||
}
|
||||
dp.emit();
|
||||
if (!config_["signal"].empty() && config_["interval"].empty() &&
|
||||
config_["restart-interval"].empty()) {
|
||||
|
||||
508
src/modules/ext/workspace_manager.cpp
Normal file
508
src/modules/ext/workspace_manager.cpp
Normal file
@@ -0,0 +1,508 @@
|
||||
#include "modules/ext/workspace_manager.hpp"
|
||||
|
||||
#include <gdk/gdkwayland.h>
|
||||
#include <gtkmm.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
#include "client.hpp"
|
||||
#include "gtkmm/widget.h"
|
||||
#include "modules/ext/workspace_manager_binding.hpp"
|
||||
|
||||
namespace waybar::modules::ext {
|
||||
|
||||
// WorkspaceManager
|
||||
|
||||
uint32_t WorkspaceManager::group_global_id = 0;
|
||||
uint32_t WorkspaceManager::workspace_global_id = 0;
|
||||
std::map<std::string, std::string> Workspace::icon_map_;
|
||||
|
||||
WorkspaceManager::WorkspaceManager(const std::string &id, const waybar::Bar &bar,
|
||||
const Json::Value &config)
|
||||
: waybar::AModule(config, "workspaces", id, false, false), bar_(bar), box_(bar.orientation, 0) {
|
||||
add_registry_listener(this);
|
||||
|
||||
// parse configuration
|
||||
|
||||
const auto config_sort_by_number = config_["sort-by-number"];
|
||||
if (config_sort_by_number.isBool()) {
|
||||
spdlog::warn("[ext/workspaces]: Prefer sort-by-id instead of sort-by-number");
|
||||
sort_by_id_ = config_sort_by_number.asBool();
|
||||
}
|
||||
|
||||
const auto config_sort_by_id = config_["sort-by-id"];
|
||||
if (config_sort_by_id.isBool()) {
|
||||
sort_by_id_ = config_sort_by_id.asBool();
|
||||
}
|
||||
|
||||
const auto config_sort_by_name = config_["sort-by-name"];
|
||||
if (config_sort_by_name.isBool()) {
|
||||
sort_by_name_ = config_sort_by_name.asBool();
|
||||
}
|
||||
|
||||
const auto config_sort_by_coordinates = config_["sort-by-coordinates"];
|
||||
if (config_sort_by_coordinates.isBool()) {
|
||||
sort_by_coordinates_ = config_sort_by_coordinates.asBool();
|
||||
}
|
||||
|
||||
const auto config_all_outputs = config_["all-outputs"];
|
||||
if (config_all_outputs.isBool()) {
|
||||
all_outputs_ = config_all_outputs.asBool();
|
||||
}
|
||||
|
||||
// setup UI
|
||||
|
||||
box_.set_name("workspaces");
|
||||
if (!id.empty()) {
|
||||
box_.get_style_context()->add_class(id);
|
||||
}
|
||||
box_.get_style_context()->add_class(MODULE_CLASS);
|
||||
event_box_.add(box_);
|
||||
|
||||
spdlog::debug("[ext/workspaces]: Workspace manager created");
|
||||
}
|
||||
|
||||
WorkspaceManager::~WorkspaceManager() {
|
||||
workspaces_.clear();
|
||||
groups_.clear();
|
||||
|
||||
if (ext_manager_ != nullptr) {
|
||||
auto *display = Client::inst()->wl_display;
|
||||
// Send `stop` request and wait for one roundtrip.
|
||||
ext_workspace_manager_v1_stop(ext_manager_);
|
||||
wl_display_roundtrip(display);
|
||||
}
|
||||
|
||||
if (ext_manager_ != nullptr) {
|
||||
spdlog::warn("[ext/workspaces]: Destroying workspace manager before .finished event");
|
||||
ext_workspace_manager_v1_destroy(ext_manager_);
|
||||
}
|
||||
|
||||
spdlog::debug("[ext/workspaces]: Workspace manager destroyed");
|
||||
}
|
||||
|
||||
void WorkspaceManager::register_manager(wl_registry *registry, uint32_t name, uint32_t version) {
|
||||
if (ext_manager_ != nullptr) {
|
||||
spdlog::warn("[ext/workspaces]: Register workspace manager again although already registered!");
|
||||
return;
|
||||
}
|
||||
if (version != 1) {
|
||||
spdlog::warn("[ext/workspaces]: Using different workspace manager protocol version: {}",
|
||||
version);
|
||||
}
|
||||
|
||||
ext_manager_ = workspace_manager_bind(registry, name, version, this);
|
||||
}
|
||||
|
||||
void WorkspaceManager::remove_workspace_group(uint32_t id) {
|
||||
const auto it =
|
||||
std::find_if(groups_.begin(), groups_.end(), [id](const auto &g) { return g->id() == id; });
|
||||
|
||||
if (it == groups_.end()) {
|
||||
spdlog::warn("[ext/workspaces]: Can't find workspace group with id {}", id);
|
||||
return;
|
||||
}
|
||||
|
||||
groups_.erase(it);
|
||||
}
|
||||
|
||||
void WorkspaceManager::remove_workspace(uint32_t id) {
|
||||
const auto it = std::find_if(workspaces_.begin(), workspaces_.end(),
|
||||
[id](const auto &w) { return w->id() == id; });
|
||||
|
||||
if (it == workspaces_.end()) {
|
||||
spdlog::warn("[ext/workspaces]: Can't find workspace with id {}", id);
|
||||
return;
|
||||
}
|
||||
|
||||
workspaces_.erase(it);
|
||||
}
|
||||
|
||||
void WorkspaceManager::handle_workspace_group(ext_workspace_group_handle_v1 *handle) {
|
||||
const auto new_id = ++group_global_id;
|
||||
groups_.push_back(std::make_unique<WorkspaceGroup>(*this, handle, new_id));
|
||||
spdlog::debug("[ext/workspaces]: Workspace group {} created", new_id);
|
||||
}
|
||||
|
||||
void WorkspaceManager::handle_workspace(ext_workspace_handle_v1 *handle) {
|
||||
const auto new_id = ++workspace_global_id;
|
||||
const auto new_name = std::to_string(++workspace_name);
|
||||
workspaces_.push_back(std::make_unique<Workspace>(config_, *this, handle, new_id, new_name));
|
||||
set_needs_sorting();
|
||||
spdlog::debug("[ext/workspaces]: Workspace {} created", new_id);
|
||||
}
|
||||
|
||||
void WorkspaceManager::handle_done() { dp.emit(); }
|
||||
|
||||
void WorkspaceManager::handle_finished() {
|
||||
spdlog::debug("[ext/workspaces]: Finishing workspace manager");
|
||||
ext_workspace_manager_v1_destroy(ext_manager_);
|
||||
ext_manager_ = nullptr;
|
||||
}
|
||||
|
||||
void WorkspaceManager::commit() const { ext_workspace_manager_v1_commit(ext_manager_); }
|
||||
|
||||
void WorkspaceManager::update() {
|
||||
spdlog::debug("[ext/workspaces]: Updating state");
|
||||
|
||||
if (needs_sorting_) {
|
||||
clear_buttons();
|
||||
sort_workspaces();
|
||||
needs_sorting_ = false;
|
||||
}
|
||||
|
||||
update_buttons();
|
||||
AModule::update();
|
||||
}
|
||||
|
||||
bool WorkspaceManager::has_button(const Gtk::Button *button) {
|
||||
const auto buttons = box_.get_children();
|
||||
return std::find(buttons.begin(), buttons.end(), button) != buttons.end();
|
||||
}
|
||||
|
||||
void WorkspaceManager::sort_workspaces() {
|
||||
// determine if workspace ID's and names can be sort numerically or literally
|
||||
|
||||
auto is_numeric = [](const std::string &s) {
|
||||
return !s.empty() && std::all_of(s.begin(), s.end(), ::isdigit);
|
||||
};
|
||||
|
||||
auto sort_by_workspace_id_numerically =
|
||||
std::all_of(workspaces_.begin(), workspaces_.end(),
|
||||
[&](const auto &w) { return is_numeric(w->workspace_id()); });
|
||||
|
||||
auto sort_by_name_numerically = std::all_of(workspaces_.begin(), workspaces_.end(),
|
||||
[&](const auto &w) { return is_numeric(w->name()); });
|
||||
|
||||
// sort based on configuration setting with sort-by-id as fallback
|
||||
|
||||
std::sort(workspaces_.begin(), workspaces_.end(), [&](const auto &w1, const auto &w2) {
|
||||
if (sort_by_id_ || (!sort_by_name_ && !sort_by_coordinates_)) {
|
||||
if (w1->workspace_id() == w2->workspace_id()) {
|
||||
return w1->id() < w2->id();
|
||||
}
|
||||
if (sort_by_workspace_id_numerically) {
|
||||
// the idea is that phonetic compare can be applied just to numbers
|
||||
// with same number of digits
|
||||
return w1->workspace_id().size() < w2->workspace_id().size() ||
|
||||
(w1->workspace_id().size() == w2->workspace_id().size() &&
|
||||
w1->workspace_id() < w2->workspace_id());
|
||||
}
|
||||
return w1->workspace_id() < w2->workspace_id();
|
||||
}
|
||||
|
||||
if (sort_by_name_) {
|
||||
if (w1->name() == w2->name()) {
|
||||
return w1->id() < w2->id();
|
||||
}
|
||||
if (sort_by_name_numerically) {
|
||||
// see above about numeric sorting
|
||||
return w1->name().size() < w2->name().size() ||
|
||||
(w1->name().size() == w2->name().size() && w1->name() < w2->name());
|
||||
}
|
||||
return w1->name() < w2->name();
|
||||
}
|
||||
|
||||
if (sort_by_coordinates_) {
|
||||
if (w1->coordinates() == w2->coordinates()) {
|
||||
return w1->id() < w2->id();
|
||||
}
|
||||
return w1->coordinates() < w2->coordinates();
|
||||
}
|
||||
|
||||
return w1->id() < w2->id();
|
||||
});
|
||||
}
|
||||
|
||||
void WorkspaceManager::clear_buttons() {
|
||||
for (const auto &workspace : workspaces_) {
|
||||
if (has_button(&workspace->button())) {
|
||||
box_.remove(workspace->button());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WorkspaceManager::update_buttons() {
|
||||
const auto *output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj());
|
||||
|
||||
// go through all workspace
|
||||
|
||||
for (const auto &workspace : workspaces_) {
|
||||
const bool workspace_on_any_group_for_output =
|
||||
std::any_of(groups_.begin(), groups_.end(), [&](const auto &group) {
|
||||
const bool group_on_output = group->has_output(output) || all_outputs_;
|
||||
const bool workspace_on_group = group->has_workspace(workspace->handle());
|
||||
return group_on_output && workspace_on_group;
|
||||
});
|
||||
const bool bar_contains_button = has_button(&workspace->button());
|
||||
|
||||
// add or remove buttons if needed, update button state
|
||||
|
||||
if (workspace_on_any_group_for_output) {
|
||||
if (!bar_contains_button) {
|
||||
// add button to bar
|
||||
box_.pack_start(workspace->button(), false, false);
|
||||
workspace->button().show_all();
|
||||
}
|
||||
workspace->update();
|
||||
} else {
|
||||
if (bar_contains_button) {
|
||||
// remove button from bar
|
||||
box_.remove(workspace->button());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WorkspaceGroup
|
||||
|
||||
WorkspaceGroup::WorkspaceGroup(WorkspaceManager &manager, ext_workspace_group_handle_v1 *handle,
|
||||
uint32_t id)
|
||||
: workspaces_manager_(manager), ext_handle_(handle), id_(id) {
|
||||
add_workspace_group_listener(ext_handle_, this);
|
||||
}
|
||||
|
||||
WorkspaceGroup::~WorkspaceGroup() {
|
||||
if (ext_handle_ != nullptr) {
|
||||
ext_workspace_group_handle_v1_destroy(ext_handle_);
|
||||
}
|
||||
spdlog::debug("[ext/workspaces]: Workspace group {} destroyed", id_);
|
||||
}
|
||||
|
||||
bool WorkspaceGroup::has_output(const wl_output *output) {
|
||||
return std::find(outputs_.begin(), outputs_.end(), output) != outputs_.end();
|
||||
}
|
||||
|
||||
bool WorkspaceGroup::has_workspace(const ext_workspace_handle_v1 *workspace) {
|
||||
return std::find(workspaces_.begin(), workspaces_.end(), workspace) != workspaces_.end();
|
||||
}
|
||||
|
||||
void WorkspaceGroup::handle_capabilities(uint32_t capabilities) {
|
||||
spdlog::debug("[ext/workspaces]: Capabilities for workspace group {}:", id_);
|
||||
if ((capabilities & EXT_WORKSPACE_GROUP_HANDLE_V1_GROUP_CAPABILITIES_CREATE_WORKSPACE) ==
|
||||
capabilities) {
|
||||
spdlog::debug("[ext/workspaces]: - create-workspace");
|
||||
}
|
||||
}
|
||||
|
||||
void WorkspaceGroup::handle_output_enter(wl_output *output) { outputs_.push_back(output); }
|
||||
|
||||
void WorkspaceGroup::handle_output_leave(wl_output *output) {
|
||||
const auto it = std::find(outputs_.begin(), outputs_.end(), output);
|
||||
if (it != outputs_.end()) {
|
||||
outputs_.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
void WorkspaceGroup::handle_workspace_enter(ext_workspace_handle_v1 *handle) {
|
||||
workspaces_.push_back(handle);
|
||||
}
|
||||
|
||||
void WorkspaceGroup::handle_workspace_leave(ext_workspace_handle_v1 *handle) {
|
||||
const auto it = std::find(workspaces_.begin(), workspaces_.end(), handle);
|
||||
if (it != workspaces_.end()) {
|
||||
workspaces_.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
void WorkspaceGroup::handle_removed() {
|
||||
spdlog::debug("[ext/workspaces]: Removing workspace group {}", id_);
|
||||
workspaces_manager_.remove_workspace_group(id_);
|
||||
}
|
||||
|
||||
// Workspace
|
||||
|
||||
Workspace::Workspace(const Json::Value &config, WorkspaceManager &manager,
|
||||
ext_workspace_handle_v1 *handle, uint32_t id, const std::string &name)
|
||||
: workspace_manager_(manager), ext_handle_(handle), id_(id), workspace_id_(name), name_(name) {
|
||||
add_workspace_listener(ext_handle_, this);
|
||||
|
||||
// parse configuration
|
||||
|
||||
const auto &config_active_only = config["active-only"];
|
||||
if (config_active_only.isBool()) {
|
||||
active_only_ = config_active_only.asBool();
|
||||
}
|
||||
|
||||
const auto &config_ignore_hidden = config["ignore-hidden"];
|
||||
if (config_ignore_hidden.isBool()) {
|
||||
ignore_hidden_ = config_ignore_hidden.asBool();
|
||||
}
|
||||
|
||||
const auto &config_format = config["format"];
|
||||
format_ = config_format.isString() ? config_format.asString() : "{name}";
|
||||
with_icon_ = format_.find("{icon}") != std::string::npos;
|
||||
|
||||
if (with_icon_ && icon_map_.empty()) {
|
||||
const auto &format_icons = config["format-icons"];
|
||||
for (auto &n : format_icons.getMemberNames()) {
|
||||
icon_map_.emplace(n, format_icons[n].asString());
|
||||
}
|
||||
}
|
||||
|
||||
const bool config_on_click = config["on-click"].isString();
|
||||
if (config_on_click) {
|
||||
on_click_action_ = config["on-click"].asString();
|
||||
}
|
||||
const bool config_on_click_middle = config["on-click-middle"].isString();
|
||||
if (config_on_click_middle) {
|
||||
on_click_middle_action_ = config["on-click"].asString();
|
||||
}
|
||||
const bool config_on_click_right = config["on-click-right"].isString();
|
||||
if (config_on_click_right) {
|
||||
on_click_right_action_ = config["on-click"].asString();
|
||||
}
|
||||
|
||||
// setup UI
|
||||
|
||||
if (config_on_click || config_on_click_middle || config_on_click_right) {
|
||||
button_.add_events(Gdk::BUTTON_PRESS_MASK);
|
||||
button_.signal_button_press_event().connect(sigc::mem_fun(*this, &Workspace::handle_clicked),
|
||||
false);
|
||||
}
|
||||
|
||||
button_.set_relief(Gtk::RELIEF_NONE);
|
||||
content_.set_center_widget(label_);
|
||||
button_.add(content_);
|
||||
}
|
||||
|
||||
Workspace::~Workspace() {
|
||||
if (ext_handle_ != nullptr) {
|
||||
ext_workspace_handle_v1_destroy(ext_handle_);
|
||||
}
|
||||
spdlog::debug("[ext/workspaces]: Workspace {} destroyed", id_);
|
||||
}
|
||||
|
||||
void Workspace::update() {
|
||||
if (!needs_updating_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// update style and visibility
|
||||
|
||||
const auto style_context = button_.get_style_context();
|
||||
style_context->remove_class("active");
|
||||
style_context->remove_class("urgent");
|
||||
style_context->remove_class("hidden");
|
||||
|
||||
if (has_state(EXT_WORKSPACE_HANDLE_V1_STATE_ACTIVE)) {
|
||||
button_.set_visible(true);
|
||||
style_context->add_class("active");
|
||||
}
|
||||
if (has_state(EXT_WORKSPACE_HANDLE_V1_STATE_URGENT)) {
|
||||
button_.set_visible(true);
|
||||
style_context->add_class("urgent");
|
||||
}
|
||||
if (has_state(EXT_WORKSPACE_HANDLE_V1_STATE_HIDDEN)) {
|
||||
button_.set_visible(!active_only_ && !ignore_hidden_);
|
||||
style_context->add_class("hidden");
|
||||
}
|
||||
if (state_ == 0) {
|
||||
button_.set_visible(!active_only_);
|
||||
}
|
||||
|
||||
// update label
|
||||
label_.set_markup(fmt::format(fmt::runtime(format_), fmt::arg("name", name_),
|
||||
fmt::arg("id", workspace_id_),
|
||||
fmt::arg("icon", with_icon_ ? icon() : "")));
|
||||
|
||||
needs_updating_ = false;
|
||||
}
|
||||
|
||||
void Workspace::handle_id(const std::string &id) {
|
||||
spdlog::debug("[ext/workspaces]: ID for workspace {}: {}", id_, id);
|
||||
workspace_id_ = id;
|
||||
needs_updating_ = true;
|
||||
workspace_manager_.set_needs_sorting();
|
||||
}
|
||||
|
||||
void Workspace::handle_name(const std::string &name) {
|
||||
spdlog::debug("[ext/workspaces]: Name for workspace {}: {}", id_, name);
|
||||
name_ = name;
|
||||
needs_updating_ = true;
|
||||
workspace_manager_.set_needs_sorting();
|
||||
}
|
||||
|
||||
void Workspace::handle_coordinates(const std::vector<uint32_t> &coordinates) {
|
||||
coordinates_ = coordinates;
|
||||
needs_updating_ = true;
|
||||
workspace_manager_.set_needs_sorting();
|
||||
}
|
||||
|
||||
void Workspace::handle_state(uint32_t state) {
|
||||
state_ = state;
|
||||
needs_updating_ = true;
|
||||
}
|
||||
|
||||
void Workspace::handle_capabilities(uint32_t capabilities) {
|
||||
spdlog::debug("[ext/workspaces]: Capabilities for workspace {}:", id_);
|
||||
if ((capabilities & EXT_WORKSPACE_HANDLE_V1_WORKSPACE_CAPABILITIES_ACTIVATE) == capabilities) {
|
||||
spdlog::debug("[ext/workspaces]: - activate");
|
||||
}
|
||||
if ((capabilities & EXT_WORKSPACE_HANDLE_V1_WORKSPACE_CAPABILITIES_DEACTIVATE) == capabilities) {
|
||||
spdlog::debug("[ext/workspaces]: - deactivate");
|
||||
}
|
||||
if ((capabilities & EXT_WORKSPACE_HANDLE_V1_WORKSPACE_CAPABILITIES_REMOVE) == capabilities) {
|
||||
spdlog::debug("[ext/workspaces]: - remove");
|
||||
}
|
||||
if ((capabilities & EXT_WORKSPACE_HANDLE_V1_WORKSPACE_CAPABILITIES_ASSIGN) == capabilities) {
|
||||
spdlog::debug("[ext/workspaces]: - assign");
|
||||
}
|
||||
needs_updating_ = true;
|
||||
}
|
||||
|
||||
void Workspace::handle_removed() {
|
||||
spdlog::debug("[ext/workspaces]: Removing workspace {}", id_);
|
||||
workspace_manager_.remove_workspace(id_);
|
||||
}
|
||||
|
||||
bool Workspace::handle_clicked(const GdkEventButton *button) const {
|
||||
std::string action;
|
||||
if (button->button == GDK_BUTTON_PRIMARY) {
|
||||
action = on_click_action_;
|
||||
} else if (button->button == GDK_BUTTON_MIDDLE) {
|
||||
action = on_click_middle_action_;
|
||||
} else if (button->button == GDK_BUTTON_SECONDARY) {
|
||||
action = on_click_right_action_;
|
||||
}
|
||||
|
||||
if (action.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (action == "activate") {
|
||||
ext_workspace_handle_v1_activate(ext_handle_);
|
||||
} else if (action == "close") {
|
||||
ext_workspace_handle_v1_remove(ext_handle_);
|
||||
} else {
|
||||
spdlog::warn("[ext/workspaces]: Unknown action {}", action);
|
||||
}
|
||||
workspace_manager_.commit();
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string Workspace::icon() {
|
||||
if (has_state(EXT_WORKSPACE_HANDLE_V1_STATE_ACTIVE)) {
|
||||
const auto active_icon_it = icon_map_.find("active");
|
||||
if (active_icon_it != icon_map_.end()) {
|
||||
return active_icon_it->second;
|
||||
}
|
||||
}
|
||||
|
||||
const auto named_icon_it = icon_map_.find(name_);
|
||||
if (named_icon_it != icon_map_.end()) {
|
||||
return named_icon_it->second;
|
||||
}
|
||||
|
||||
const auto default_icon_it = icon_map_.find("default");
|
||||
if (default_icon_it != icon_map_.end()) {
|
||||
return default_icon_it->second;
|
||||
}
|
||||
|
||||
return name_;
|
||||
}
|
||||
|
||||
} // namespace waybar::modules::ext
|
||||
159
src/modules/ext/workspace_manager_binding.cpp
Normal file
159
src/modules/ext/workspace_manager_binding.cpp
Normal file
@@ -0,0 +1,159 @@
|
||||
#include "modules/ext/workspace_manager_binding.hpp"
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "client.hpp"
|
||||
#include "modules/ext/workspace_manager.hpp"
|
||||
|
||||
namespace waybar::modules::ext {
|
||||
|
||||
static void handle_global(void *data, wl_registry *registry, uint32_t name, const char *interface,
|
||||
uint32_t version) {
|
||||
if (std::strcmp(interface, ext_workspace_manager_v1_interface.name) == 0) {
|
||||
static_cast<WorkspaceManager *>(data)->register_manager(registry, name, version);
|
||||
}
|
||||
}
|
||||
|
||||
static void handle_global_remove(void *data, wl_registry *registry, uint32_t name) {
|
||||
/* Nothing to do here */
|
||||
}
|
||||
|
||||
static const wl_registry_listener registry_listener_impl = {.global = handle_global,
|
||||
.global_remove = handle_global_remove};
|
||||
|
||||
void add_registry_listener(void *data) {
|
||||
wl_display *display = Client::inst()->wl_display;
|
||||
wl_registry *registry = wl_display_get_registry(display);
|
||||
|
||||
wl_registry_add_listener(registry, ®istry_listener_impl, data);
|
||||
wl_display_roundtrip(display);
|
||||
}
|
||||
|
||||
static void workspace_manager_handle_workspace_group(
|
||||
void *data, ext_workspace_manager_v1 *_, ext_workspace_group_handle_v1 *workspace_group) {
|
||||
static_cast<WorkspaceManager *>(data)->handle_workspace_group(workspace_group);
|
||||
}
|
||||
|
||||
static void workspace_manager_handle_workspace(void *data, ext_workspace_manager_v1 *_,
|
||||
ext_workspace_handle_v1 *workspace) {
|
||||
static_cast<WorkspaceManager *>(data)->handle_workspace(workspace);
|
||||
}
|
||||
|
||||
static void workspace_manager_handle_done(void *data, ext_workspace_manager_v1 *_) {
|
||||
static_cast<WorkspaceManager *>(data)->handle_done();
|
||||
}
|
||||
|
||||
static void workspace_manager_handle_finished(void *data, ext_workspace_manager_v1 *_) {
|
||||
static_cast<WorkspaceManager *>(data)->handle_finished();
|
||||
}
|
||||
|
||||
static const ext_workspace_manager_v1_listener workspace_manager_impl = {
|
||||
.workspace_group = workspace_manager_handle_workspace_group,
|
||||
.workspace = workspace_manager_handle_workspace,
|
||||
.done = workspace_manager_handle_done,
|
||||
.finished = workspace_manager_handle_finished,
|
||||
};
|
||||
|
||||
ext_workspace_manager_v1 *workspace_manager_bind(wl_registry *registry, uint32_t name,
|
||||
uint32_t version, void *data) {
|
||||
auto *workspace_manager = static_cast<ext_workspace_manager_v1 *>(
|
||||
wl_registry_bind(registry, name, &ext_workspace_manager_v1_interface, version));
|
||||
|
||||
if (workspace_manager)
|
||||
ext_workspace_manager_v1_add_listener(workspace_manager, &workspace_manager_impl, data);
|
||||
else
|
||||
spdlog::error("Failed to register manager");
|
||||
|
||||
return workspace_manager;
|
||||
}
|
||||
|
||||
static void workspace_group_handle_capabilities(void *data, ext_workspace_group_handle_v1 *_,
|
||||
uint32_t capabilities) {
|
||||
static_cast<WorkspaceGroup *>(data)->handle_capabilities(capabilities);
|
||||
}
|
||||
|
||||
static void workspace_group_handle_output_enter(void *data, ext_workspace_group_handle_v1 *_,
|
||||
wl_output *output) {
|
||||
static_cast<WorkspaceGroup *>(data)->handle_output_enter(output);
|
||||
}
|
||||
|
||||
static void workspace_group_handle_output_leave(void *data, ext_workspace_group_handle_v1 *_,
|
||||
wl_output *output) {
|
||||
static_cast<WorkspaceGroup *>(data)->handle_output_leave(output);
|
||||
}
|
||||
|
||||
static void workspace_group_handle_workspace_enter(void *data, ext_workspace_group_handle_v1 *_,
|
||||
ext_workspace_handle_v1 *workspace) {
|
||||
static_cast<WorkspaceGroup *>(data)->handle_workspace_enter(workspace);
|
||||
}
|
||||
|
||||
static void workspace_group_handle_workspace_leave(void *data, ext_workspace_group_handle_v1 *_,
|
||||
ext_workspace_handle_v1 *workspace) {
|
||||
static_cast<WorkspaceGroup *>(data)->handle_workspace_leave(workspace);
|
||||
}
|
||||
|
||||
static void workspace_group_handle_removed(void *data, ext_workspace_group_handle_v1 *_) {
|
||||
static_cast<WorkspaceGroup *>(data)->handle_removed();
|
||||
}
|
||||
|
||||
static const ext_workspace_group_handle_v1_listener workspace_group_impl = {
|
||||
.capabilities = workspace_group_handle_capabilities,
|
||||
.output_enter = workspace_group_handle_output_enter,
|
||||
.output_leave = workspace_group_handle_output_leave,
|
||||
.workspace_enter = workspace_group_handle_workspace_enter,
|
||||
.workspace_leave = workspace_group_handle_workspace_leave,
|
||||
.removed = workspace_group_handle_removed};
|
||||
|
||||
void add_workspace_group_listener(ext_workspace_group_handle_v1 *workspace_group_handle,
|
||||
void *data) {
|
||||
ext_workspace_group_handle_v1_add_listener(workspace_group_handle, &workspace_group_impl, data);
|
||||
}
|
||||
|
||||
void workspace_handle_name(void *data, struct ext_workspace_handle_v1 *_, const char *name) {
|
||||
static_cast<Workspace *>(data)->handle_name(name);
|
||||
}
|
||||
|
||||
void workspace_handle_id(void *data, struct ext_workspace_handle_v1 *_, const char *id) {
|
||||
static_cast<Workspace *>(data)->handle_id(id);
|
||||
}
|
||||
|
||||
void workspace_handle_coordinates(void *data, struct ext_workspace_handle_v1 *_,
|
||||
struct wl_array *coordinates) {
|
||||
std::vector<uint32_t> coords_vec;
|
||||
auto coords = static_cast<uint32_t *>(coordinates->data);
|
||||
for (size_t i = 0; i < coordinates->size / sizeof(uint32_t); ++i) {
|
||||
coords_vec.push_back(coords[i]);
|
||||
}
|
||||
|
||||
static_cast<Workspace *>(data)->handle_coordinates(coords_vec);
|
||||
}
|
||||
|
||||
void workspace_handle_state(void *data, struct ext_workspace_handle_v1 *workspace_handle,
|
||||
uint32_t state) {
|
||||
static_cast<Workspace *>(data)->handle_state(state);
|
||||
}
|
||||
|
||||
static void workspace_handle_capabilities(void *data,
|
||||
struct ext_workspace_handle_v1 *workspace_handle,
|
||||
uint32_t capabilities) {
|
||||
static_cast<Workspace *>(data)->handle_capabilities(capabilities);
|
||||
}
|
||||
|
||||
void workspace_handle_removed(void *data, struct ext_workspace_handle_v1 *workspace_handle) {
|
||||
static_cast<Workspace *>(data)->handle_removed();
|
||||
}
|
||||
|
||||
static const ext_workspace_handle_v1_listener workspace_impl = {
|
||||
.id = workspace_handle_id,
|
||||
.name = workspace_handle_name,
|
||||
.coordinates = workspace_handle_coordinates,
|
||||
.state = workspace_handle_state,
|
||||
.capabilities = workspace_handle_capabilities,
|
||||
.removed = workspace_handle_removed};
|
||||
|
||||
void add_workspace_listener(ext_workspace_handle_v1 *workspace_handle, void *data) {
|
||||
ext_workspace_handle_v1_add_listener(workspace_handle, &workspace_impl, data);
|
||||
}
|
||||
} // namespace waybar::modules::ext
|
||||
@@ -70,8 +70,14 @@ void Language::onEvent(const std::string& ev) {
|
||||
// Last comma before variants parenthesis, eg:
|
||||
// activelayout>>micro-star-int'l-co.,-ltd.-msi-gk50-elite-gaming-keyboard,English (US, intl.,
|
||||
// with dead keys)
|
||||
std::string beforParenthesis(begin(ev), begin(ev) + ev.find_last_of('('));
|
||||
auto layoutName = ev.substr(beforParenthesis.find_last_of(',') + 1);
|
||||
std::string beforeParenthesis;
|
||||
auto parenthesisPos = ev.find_last_of('(');
|
||||
if (parenthesisPos == std::string::npos) {
|
||||
beforeParenthesis = ev;
|
||||
} else {
|
||||
beforeParenthesis = std::string(begin(ev), begin(ev) + parenthesisPos);
|
||||
}
|
||||
auto layoutName = ev.substr(beforeParenthesis.find_last_of(',') + 1);
|
||||
|
||||
if (config_.isMember("keyboard-name") && kbName != config_["keyboard-name"].asString())
|
||||
return; // ignore
|
||||
|
||||
@@ -20,7 +20,7 @@ WindowCreationPayload::WindowCreationPayload(Json::Value const &client_data)
|
||||
}
|
||||
|
||||
WindowCreationPayload::WindowCreationPayload(std::string workspace_name,
|
||||
WindowAddress window_address, std::string window_repr)
|
||||
WindowAddress window_address, WindowRepr window_repr)
|
||||
: m_window(std::move(window_repr)),
|
||||
m_windowAddress(std::move(window_address)),
|
||||
m_workspaceName(std::move(workspace_name)) {
|
||||
@@ -30,10 +30,11 @@ WindowCreationPayload::WindowCreationPayload(std::string workspace_name,
|
||||
|
||||
WindowCreationPayload::WindowCreationPayload(std::string workspace_name,
|
||||
WindowAddress window_address, std::string window_class,
|
||||
std::string window_title)
|
||||
std::string window_title, bool is_active)
|
||||
: m_window(std::make_pair(std::move(window_class), std::move(window_title))),
|
||||
m_windowAddress(std::move(window_address)),
|
||||
m_workspaceName(std::move(workspace_name)) {
|
||||
m_workspaceName(std::move(workspace_name)),
|
||||
m_isActive(is_active) {
|
||||
clearAddr();
|
||||
clearWorkspaceName();
|
||||
}
|
||||
@@ -92,13 +93,14 @@ void WindowCreationPayload::moveToWorkspace(std::string &new_workspace_name) {
|
||||
m_workspaceName = new_workspace_name;
|
||||
}
|
||||
|
||||
std::string WindowCreationPayload::repr(Workspaces &workspace_manager) {
|
||||
WindowRepr WindowCreationPayload::repr(Workspaces &workspace_manager) {
|
||||
if (std::holds_alternative<Repr>(m_window)) {
|
||||
return std::get<Repr>(m_window);
|
||||
}
|
||||
if (std::holds_alternative<ClassAndTitle>(m_window)) {
|
||||
auto [window_class, window_title] = std::get<ClassAndTitle>(m_window);
|
||||
return workspace_manager.getRewrite(window_class, window_title);
|
||||
auto const &[window_class, window_title] = std::get<ClassAndTitle>(m_window);
|
||||
return {m_windowAddress, window_class, window_title,
|
||||
workspace_manager.getRewrite(window_class, window_title), m_isActive};
|
||||
}
|
||||
// Unreachable
|
||||
spdlog::error("WorkspaceWindow::repr: Unreachable");
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
#include <utility>
|
||||
|
||||
#include "modules/hyprland/workspaces.hpp"
|
||||
#include "util/command.hpp"
|
||||
#include "util/icon_loader.hpp"
|
||||
|
||||
namespace waybar::modules::hyprland {
|
||||
|
||||
@@ -32,7 +34,12 @@ Workspace::Workspace(const Json::Value &workspace_data, Workspaces &workspace_ma
|
||||
false);
|
||||
|
||||
m_button.set_relief(Gtk::RELIEF_NONE);
|
||||
m_content.set_center_widget(m_label);
|
||||
if (m_workspaceManager.enableTaskbar()) {
|
||||
m_content.set_orientation(m_workspaceManager.taskbarOrientation());
|
||||
m_content.pack_start(m_labelBefore, false, false);
|
||||
} else {
|
||||
m_content.set_center_widget(m_labelBefore);
|
||||
}
|
||||
m_button.add(m_content);
|
||||
|
||||
initializeWindowMap(clients_data);
|
||||
@@ -47,9 +54,14 @@ void addOrRemoveClass(const Glib::RefPtr<Gtk::StyleContext> &context, bool condi
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<std::string> Workspace::closeWindow(WindowAddress const &addr) {
|
||||
if (m_windowMap.contains(addr)) {
|
||||
return removeWindow(addr);
|
||||
std::optional<WindowRepr> Workspace::closeWindow(WindowAddress const &addr) {
|
||||
auto it = std::ranges::find_if(m_windowMap,
|
||||
[&addr](const auto &window) { return window.address == addr; });
|
||||
// If the vector contains the address, remove it and return the window representation
|
||||
if (it != m_windowMap.end()) {
|
||||
WindowRepr windowRepr = *it;
|
||||
m_windowMap.erase(it);
|
||||
return windowRepr;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
@@ -91,12 +103,26 @@ void Workspace::initializeWindowMap(const Json::Value &clients_data) {
|
||||
}
|
||||
}
|
||||
|
||||
void Workspace::setActiveWindow(WindowAddress const &addr) {
|
||||
for (auto &window : m_windowMap) {
|
||||
window.setActive(window.address == addr);
|
||||
}
|
||||
}
|
||||
|
||||
void Workspace::insertWindow(WindowCreationPayload create_window_payload) {
|
||||
if (!create_window_payload.isEmpty(m_workspaceManager)) {
|
||||
auto repr = create_window_payload.repr(m_workspaceManager);
|
||||
|
||||
if (!repr.empty()) {
|
||||
m_windowMap[create_window_payload.getAddress()] = repr;
|
||||
if (!repr.empty() || m_workspaceManager.enableTaskbar()) {
|
||||
auto addr = create_window_payload.getAddress();
|
||||
auto it = std::ranges::find_if(
|
||||
m_windowMap, [&addr](const auto &window) { return window.address == addr; });
|
||||
// If the vector contains the address, update the window representation, otherwise insert it
|
||||
if (it != m_windowMap.end()) {
|
||||
*it = repr;
|
||||
} else {
|
||||
m_windowMap.emplace_back(repr);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -109,12 +135,6 @@ bool Workspace::onWindowOpened(WindowCreationPayload const &create_window_payloa
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string Workspace::removeWindow(WindowAddress const &addr) {
|
||||
std::string windowRepr = m_windowMap[addr];
|
||||
m_windowMap.erase(addr);
|
||||
return windowRepr;
|
||||
}
|
||||
|
||||
std::string &Workspace::selectIcon(std::map<std::string, std::string> &icons_map) {
|
||||
spdlog::trace("Selecting icon for workspace {}", name());
|
||||
if (isUrgent()) {
|
||||
@@ -172,7 +192,7 @@ std::string &Workspace::selectIcon(std::map<std::string, std::string> &icons_map
|
||||
return m_name;
|
||||
}
|
||||
|
||||
void Workspace::update(const std::string &format, const std::string &icon) {
|
||||
void Workspace::update(const std::string &workspace_icon) {
|
||||
if (this->m_workspaceManager.persistentOnly() && !this->isPersistent()) {
|
||||
m_button.hide();
|
||||
return;
|
||||
@@ -204,21 +224,122 @@ void Workspace::update(const std::string &format, const std::string &icon) {
|
||||
addOrRemoveClass(styleContext, m_workspaceManager.getBarOutput() == output(), "hosting-monitor");
|
||||
|
||||
std::string windows;
|
||||
auto windowSeparator = m_workspaceManager.getWindowSeparator();
|
||||
// Optimization: The {windows} substitution string is only possible if the taskbar is disabled, no
|
||||
// need to compute this if enableTaskbar() is true
|
||||
if (!m_workspaceManager.enableTaskbar()) {
|
||||
auto windowSeparator = m_workspaceManager.getWindowSeparator();
|
||||
|
||||
bool isNotFirst = false;
|
||||
bool isNotFirst = false;
|
||||
|
||||
for (auto &[_pid, window_repr] : m_windowMap) {
|
||||
if (isNotFirst) {
|
||||
windows.append(windowSeparator);
|
||||
for (const auto &window_repr : m_windowMap) {
|
||||
if (isNotFirst) {
|
||||
windows.append(windowSeparator);
|
||||
}
|
||||
isNotFirst = true;
|
||||
windows.append(window_repr.repr_rewrite);
|
||||
}
|
||||
isNotFirst = true;
|
||||
windows.append(window_repr);
|
||||
}
|
||||
|
||||
m_label.set_markup(fmt::format(fmt::runtime(format), fmt::arg("id", id()),
|
||||
fmt::arg("name", name()), fmt::arg("icon", icon),
|
||||
fmt::arg("windows", windows)));
|
||||
auto formatBefore = m_workspaceManager.formatBefore();
|
||||
m_labelBefore.set_markup(fmt::format(fmt::runtime(formatBefore), fmt::arg("id", id()),
|
||||
fmt::arg("name", name()), fmt::arg("icon", workspace_icon),
|
||||
fmt::arg("windows", windows)));
|
||||
m_labelBefore.get_style_context()->add_class("workspace-label");
|
||||
|
||||
if (m_workspaceManager.enableTaskbar()) {
|
||||
updateTaskbar(workspace_icon);
|
||||
}
|
||||
}
|
||||
|
||||
void Workspace::updateTaskbar(const std::string &workspace_icon) {
|
||||
for (auto child : m_content.get_children()) {
|
||||
if (child != &m_labelBefore) {
|
||||
m_content.remove(*child);
|
||||
}
|
||||
}
|
||||
|
||||
bool isFirst = true;
|
||||
for (const auto &window_repr : m_windowMap) {
|
||||
if (shouldSkipWindow(window_repr)) {
|
||||
continue;
|
||||
}
|
||||
if (isFirst) {
|
||||
isFirst = false;
|
||||
} else if (m_workspaceManager.getWindowSeparator() != "") {
|
||||
auto windowSeparator = Gtk::make_managed<Gtk::Label>(m_workspaceManager.getWindowSeparator());
|
||||
m_content.pack_start(*windowSeparator, false, false);
|
||||
windowSeparator->show();
|
||||
}
|
||||
auto window_box = Gtk::make_managed<Gtk::Box>(Gtk::ORIENTATION_HORIZONTAL);
|
||||
window_box->set_tooltip_text(window_repr.window_title);
|
||||
window_box->get_style_context()->add_class("taskbar-window");
|
||||
if (window_repr.isActive) {
|
||||
window_box->get_style_context()->add_class("active");
|
||||
}
|
||||
auto event_box = Gtk::manage(new Gtk::EventBox());
|
||||
event_box->add(*window_box);
|
||||
if (m_workspaceManager.onClickWindow() != "") {
|
||||
event_box->signal_button_press_event().connect(
|
||||
sigc::bind(sigc::mem_fun(*this, &Workspace::handleClick), window_repr.address));
|
||||
}
|
||||
|
||||
auto text_before = fmt::format(fmt::runtime(m_workspaceManager.taskbarFormatBefore()),
|
||||
fmt::arg("title", window_repr.window_title));
|
||||
if (!text_before.empty()) {
|
||||
auto window_label_before = Gtk::make_managed<Gtk::Label>(text_before);
|
||||
window_box->pack_start(*window_label_before, true, true);
|
||||
}
|
||||
|
||||
if (m_workspaceManager.taskbarWithIcon()) {
|
||||
auto app_info_ = IconLoader::get_app_info_from_app_id_list(window_repr.window_class);
|
||||
int icon_size = m_workspaceManager.taskbarIconSize();
|
||||
auto window_icon = Gtk::make_managed<Gtk::Image>();
|
||||
m_workspaceManager.iconLoader().image_load_icon(*window_icon, app_info_, icon_size);
|
||||
window_box->pack_start(*window_icon, false, false);
|
||||
}
|
||||
|
||||
auto text_after = fmt::format(fmt::runtime(m_workspaceManager.taskbarFormatAfter()),
|
||||
fmt::arg("title", window_repr.window_title));
|
||||
if (!text_after.empty()) {
|
||||
auto window_label_after = Gtk::make_managed<Gtk::Label>(text_after);
|
||||
window_box->pack_start(*window_label_after, true, true);
|
||||
}
|
||||
|
||||
m_content.pack_start(*event_box, true, false);
|
||||
event_box->show_all();
|
||||
}
|
||||
|
||||
auto formatAfter = m_workspaceManager.formatAfter();
|
||||
if (!formatAfter.empty()) {
|
||||
m_labelAfter.set_markup(fmt::format(fmt::runtime(formatAfter), fmt::arg("id", id()),
|
||||
fmt::arg("name", name()),
|
||||
fmt::arg("icon", workspace_icon)));
|
||||
m_content.pack_end(m_labelAfter, false, false);
|
||||
m_labelAfter.show();
|
||||
}
|
||||
}
|
||||
|
||||
bool Workspace::handleClick(const GdkEventButton *event_button, WindowAddress const &addr) const {
|
||||
if (event_button->type == GDK_BUTTON_PRESS) {
|
||||
std::string command = std::regex_replace(m_workspaceManager.onClickWindow(),
|
||||
std::regex("\\{address\\}"), "0x" + addr);
|
||||
command = std::regex_replace(command, std::regex("\\{button\\}"),
|
||||
std::to_string(event_button->button));
|
||||
auto res = util::command::execNoRead(command);
|
||||
if (res.exit_code != 0) {
|
||||
spdlog::error("Failed to execute {}: {}", command, res.out);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Workspace::shouldSkipWindow(const WindowRepr &window_repr) const {
|
||||
auto ignore_list = m_workspaceManager.getIgnoredWindows();
|
||||
auto it = std::ranges::find_if(ignore_list, [&window_repr](const auto &ignoreItem) {
|
||||
return std::regex_match(window_repr.window_class, ignoreItem) ||
|
||||
std::regex_match(window_repr.window_title, ignoreItem);
|
||||
});
|
||||
return it != ignore_list.end();
|
||||
}
|
||||
|
||||
} // namespace waybar::modules::hyprland
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <utility>
|
||||
|
||||
#include "util/regex_collection.hpp"
|
||||
#include "util/string.hpp"
|
||||
|
||||
namespace waybar::modules::hyprland {
|
||||
|
||||
@@ -65,14 +66,17 @@ Json::Value Workspaces::createMonitorWorkspaceData(std::string const &name,
|
||||
void Workspaces::createWorkspace(Json::Value const &workspace_data,
|
||||
Json::Value const &clients_data) {
|
||||
auto workspaceName = workspace_data["name"].asString();
|
||||
auto workspaceId = workspace_data["id"].asInt();
|
||||
spdlog::debug("Creating workspace {}", workspaceName);
|
||||
|
||||
// avoid recreating existing workspaces
|
||||
auto workspace =
|
||||
std::ranges::find_if(m_workspaces, [workspaceName](std::unique_ptr<Workspace> const &w) {
|
||||
return (workspaceName.starts_with("special:") && workspaceName.substr(8) == w->name()) ||
|
||||
workspaceName == w->name();
|
||||
});
|
||||
auto workspace = std::ranges::find_if(m_workspaces, [&](std::unique_ptr<Workspace> const &w) {
|
||||
if (workspaceId > 0) {
|
||||
return w->id() == workspaceId;
|
||||
}
|
||||
return (workspaceName.starts_with("special:") && workspaceName.substr(8) == w->name()) ||
|
||||
workspaceName == w->name();
|
||||
});
|
||||
|
||||
if (workspace != m_workspaces.end()) {
|
||||
// don't recreate workspace, but update persistency if necessary
|
||||
@@ -253,10 +257,8 @@ void Workspaces::loadPersistentWorkspacesFromConfig(Json::Value const &clientsJs
|
||||
// value is an array => create defined workspaces for this monitor
|
||||
if (canCreate) {
|
||||
for (const Json::Value &workspace : value) {
|
||||
if (workspace.isInt()) {
|
||||
spdlog::debug("Creating workspace {} on monitor {}", workspace, currentMonitor);
|
||||
persistentWorkspacesToCreate.emplace_back(std::to_string(workspace.asInt()));
|
||||
}
|
||||
spdlog::debug("Creating workspace {} on monitor {}", workspace, currentMonitor);
|
||||
persistentWorkspacesToCreate.emplace_back(workspace.asString());
|
||||
}
|
||||
} else {
|
||||
// key is the workspace and value is array of monitors to create on
|
||||
@@ -292,8 +294,13 @@ void Workspaces::loadPersistentWorkspacesFromWorkspaceRules(const Json::Value &c
|
||||
if (!rule["persistent"].asBool()) {
|
||||
continue;
|
||||
}
|
||||
auto const &workspace = rule.isMember("defaultName") ? rule["defaultName"].asString()
|
||||
: rule["workspaceString"].asString();
|
||||
auto workspace = rule.isMember("defaultName") ? rule["defaultName"].asString()
|
||||
: rule["workspaceString"].asString();
|
||||
|
||||
// The prefix "name:" cause mismatches with workspace names taken anywhere else.
|
||||
if (workspace.starts_with("name:")) {
|
||||
workspace = workspace.substr(5);
|
||||
}
|
||||
auto const &monitor = rule["monitor"].asString();
|
||||
// create this workspace persistently if:
|
||||
// 1. the allOutputs config option is enabled
|
||||
@@ -340,6 +347,8 @@ void Workspaces::onEvent(const std::string &ev) {
|
||||
onWorkspaceRenamed(payload);
|
||||
} else if (eventName == "windowtitlev2") {
|
||||
onWindowTitleEvent(payload);
|
||||
} else if (eventName == "activewindowv2") {
|
||||
onActiveWindowChanged(payload);
|
||||
} else if (eventName == "configreloaded") {
|
||||
onConfigReloaded();
|
||||
}
|
||||
@@ -489,12 +498,14 @@ void Workspaces::onWindowOpened(std::string const &payload) {
|
||||
|
||||
std::string windowTitle = payload.substr(nextCommaIdx + 1, payload.length() - nextCommaIdx);
|
||||
|
||||
m_windowsToCreate.emplace_back(workspaceName, windowAddress, windowClass, windowTitle);
|
||||
bool isActive = m_currentActiveWindowAddress == windowAddress;
|
||||
m_windowsToCreate.emplace_back(workspaceName, windowAddress, windowClass, windowTitle, isActive);
|
||||
}
|
||||
|
||||
void Workspaces::onWindowClosed(std::string const &addr) {
|
||||
spdlog::trace("Window closed: {}", addr);
|
||||
updateWindowCount();
|
||||
m_orphanWindowMap.erase(addr);
|
||||
for (auto &workspace : m_workspaces) {
|
||||
if (workspace->closeWindow(addr)) {
|
||||
break;
|
||||
@@ -507,7 +518,7 @@ void Workspaces::onWindowMoved(std::string const &payload) {
|
||||
updateWindowCount();
|
||||
auto [windowAddress, _, workspaceName] = splitTriplePayload(payload);
|
||||
|
||||
std::string windowRepr;
|
||||
WindowRepr windowRepr;
|
||||
|
||||
// If the window was still queued to be created, just change its destination
|
||||
// and exit
|
||||
@@ -533,6 +544,7 @@ void Workspaces::onWindowMoved(std::string const &payload) {
|
||||
|
||||
// ...and then add it to the new workspace
|
||||
if (!windowRepr.empty()) {
|
||||
m_orphanWindowMap.erase(windowAddress);
|
||||
m_windowsToCreate.emplace_back(workspaceName, windowAddress, windowRepr);
|
||||
}
|
||||
}
|
||||
@@ -558,9 +570,10 @@ void Workspaces::onWindowTitleEvent(std::string const &payload) {
|
||||
(*windowWorkspace)->insertWindow(std::move(wcp));
|
||||
};
|
||||
} else {
|
||||
auto queuedWindow = std::ranges::find_if(m_windowsToCreate, [payload](auto &windowPayload) {
|
||||
return windowPayload.getAddress() == payload;
|
||||
});
|
||||
auto queuedWindow =
|
||||
std::ranges::find_if(m_windowsToCreate, [&windowAddress](auto &windowPayload) {
|
||||
return windowPayload.getAddress() == windowAddress;
|
||||
});
|
||||
|
||||
// If the window was queued, rename it in the queue
|
||||
if (queuedWindow != m_windowsToCreate.end()) {
|
||||
@@ -571,7 +584,7 @@ void Workspaces::onWindowTitleEvent(std::string const &payload) {
|
||||
|
||||
if (inserter.has_value()) {
|
||||
Json::Value clientsData = m_ipc.getSocket1JsonReply("clients");
|
||||
std::string jsonWindowAddress = fmt::format("0x{}", payload);
|
||||
std::string jsonWindowAddress = fmt::format("0x{}", windowAddress);
|
||||
|
||||
auto client = std::ranges::find_if(clientsData, [jsonWindowAddress](auto &client) {
|
||||
return client["address"].asString() == jsonWindowAddress;
|
||||
@@ -583,6 +596,21 @@ void Workspaces::onWindowTitleEvent(std::string const &payload) {
|
||||
}
|
||||
}
|
||||
|
||||
void Workspaces::onActiveWindowChanged(WindowAddress const &activeWindowAddress) {
|
||||
spdlog::trace("Active window changed: {}", activeWindowAddress);
|
||||
m_currentActiveWindowAddress = activeWindowAddress;
|
||||
|
||||
for (auto &[address, window] : m_orphanWindowMap) {
|
||||
window.setActive(address == activeWindowAddress);
|
||||
}
|
||||
for (auto const &workspace : m_workspaces) {
|
||||
workspace->setActiveWindow(activeWindowAddress);
|
||||
}
|
||||
for (auto &window : m_windowsToCreate) {
|
||||
window.setActive(window.getAddress() == activeWindowAddress);
|
||||
}
|
||||
}
|
||||
|
||||
void Workspaces::onConfigReloaded() {
|
||||
spdlog::info("Hyprland config reloaded, reinitializing hyprland/workspaces module...");
|
||||
init();
|
||||
@@ -590,8 +618,9 @@ void Workspaces::onConfigReloaded() {
|
||||
|
||||
auto Workspaces::parseConfig(const Json::Value &config) -> void {
|
||||
const auto &configFormat = config["format"];
|
||||
m_format = configFormat.isString() ? configFormat.asString() : "{name}";
|
||||
m_withIcon = m_format.find("{icon}") != std::string::npos;
|
||||
m_formatBefore = configFormat.isString() ? configFormat.asString() : "{name}";
|
||||
m_withIcon = m_formatBefore.find("{icon}") != std::string::npos;
|
||||
auto withWindows = m_formatBefore.find("{windows}") != std::string::npos;
|
||||
|
||||
if (m_withIcon && m_iconsMap.empty()) {
|
||||
populateIconsMap(config["format-icons"]);
|
||||
@@ -609,6 +638,15 @@ auto Workspaces::parseConfig(const Json::Value &config) -> void {
|
||||
populateIgnoreWorkspacesConfig(config);
|
||||
populateFormatWindowSeparatorConfig(config);
|
||||
populateWindowRewriteConfig(config);
|
||||
|
||||
if (withWindows) {
|
||||
populateWorkspaceTaskbarConfig(config);
|
||||
}
|
||||
if (m_enableTaskbar) {
|
||||
auto parts = split(m_formatBefore, "{windows}", 1);
|
||||
m_formatBefore = parts[0];
|
||||
m_formatAfter = parts.size() > 1 ? parts[1] : "";
|
||||
}
|
||||
}
|
||||
|
||||
auto Workspaces::populateIconsMap(const Json::Value &formatIcons) -> void {
|
||||
@@ -681,6 +719,64 @@ auto Workspaces::populateWindowRewriteConfig(const Json::Value &config) -> void
|
||||
[this](std::string &window_rule) { return windowRewritePriorityFunction(window_rule); });
|
||||
}
|
||||
|
||||
auto Workspaces::populateWorkspaceTaskbarConfig(const Json::Value &config) -> void {
|
||||
const auto &workspaceTaskbar = config["workspace-taskbar"];
|
||||
if (!workspaceTaskbar.isObject()) {
|
||||
spdlog::debug("workspace-taskbar is not defined or is not an object, using default rules.");
|
||||
return;
|
||||
}
|
||||
|
||||
populateBoolConfig(workspaceTaskbar, "enable", m_enableTaskbar);
|
||||
populateBoolConfig(workspaceTaskbar, "update-active-window", m_updateActiveWindow);
|
||||
|
||||
if (workspaceTaskbar["format"].isString()) {
|
||||
/* The user defined a format string, use it */
|
||||
std::string format = workspaceTaskbar["format"].asString();
|
||||
m_taskbarWithTitle = format.find("{title") != std::string::npos; /* {title} or {title.length} */
|
||||
auto parts = split(format, "{icon}", 1);
|
||||
m_taskbarFormatBefore = parts[0];
|
||||
if (parts.size() > 1) {
|
||||
m_taskbarWithIcon = true;
|
||||
m_taskbarFormatAfter = parts[1];
|
||||
}
|
||||
} else {
|
||||
/* The default is to only show the icon */
|
||||
m_taskbarWithIcon = true;
|
||||
}
|
||||
|
||||
auto iconTheme = workspaceTaskbar["icon-theme"];
|
||||
if (iconTheme.isArray()) {
|
||||
for (auto &c : iconTheme) {
|
||||
m_iconLoader.add_custom_icon_theme(c.asString());
|
||||
}
|
||||
} else if (iconTheme.isString()) {
|
||||
m_iconLoader.add_custom_icon_theme(iconTheme.asString());
|
||||
}
|
||||
|
||||
if (workspaceTaskbar["icon-size"].isInt()) {
|
||||
m_taskbarIconSize = workspaceTaskbar["icon-size"].asInt();
|
||||
}
|
||||
if (workspaceTaskbar["orientation"].isString() &&
|
||||
toLower(workspaceTaskbar["orientation"].asString()) == "vertical") {
|
||||
m_taskbarOrientation = Gtk::ORIENTATION_VERTICAL;
|
||||
}
|
||||
|
||||
if (workspaceTaskbar["on-click-window"].isString()) {
|
||||
m_onClickWindow = workspaceTaskbar["on-click-window"].asString();
|
||||
}
|
||||
|
||||
if (workspaceTaskbar["ignore-list"].isArray()) {
|
||||
for (auto &windowRegex : workspaceTaskbar["ignore-list"]) {
|
||||
std::string ruleString = windowRegex.asString();
|
||||
try {
|
||||
m_ignoreWindows.emplace_back(ruleString, std::regex_constants::icase);
|
||||
} catch (const std::regex_error &e) {
|
||||
spdlog::error("Invalid rule {}: {}", ruleString, e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Workspaces::registerOrphanWindow(WindowCreationPayload create_window_payload) {
|
||||
if (!create_window_payload.isEmpty(*this)) {
|
||||
m_orphanWindowMap[create_window_payload.getAddress()] = create_window_payload.repr(*this);
|
||||
@@ -701,12 +797,18 @@ auto Workspaces::registerIpc() -> void {
|
||||
m_ipc.registerForIPC("urgent", this);
|
||||
m_ipc.registerForIPC("configreloaded", this);
|
||||
|
||||
if (windowRewriteConfigUsesTitle()) {
|
||||
if (windowRewriteConfigUsesTitle() || m_taskbarWithTitle) {
|
||||
spdlog::info(
|
||||
"Registering for Hyprland's 'windowtitlev2' events because a user-defined window "
|
||||
"rewrite rule uses the 'title' field.");
|
||||
m_ipc.registerForIPC("windowtitlev2", this);
|
||||
}
|
||||
if (m_updateActiveWindow) {
|
||||
spdlog::info(
|
||||
"Registering for Hyprland's 'activewindowv2' events because 'update-active-window' is set "
|
||||
"to true.");
|
||||
m_ipc.registerForIPC("activewindowv2", this);
|
||||
}
|
||||
}
|
||||
|
||||
void Workspaces::removeWorkspacesToRemove() {
|
||||
@@ -898,7 +1000,7 @@ auto Workspaces::update() -> void {
|
||||
|
||||
void Workspaces::updateWindowCount() {
|
||||
const Json::Value workspacesJson = m_ipc.getSocket1JsonReply("workspaces");
|
||||
for (auto &workspace : m_workspaces) {
|
||||
for (auto const &workspace : m_workspaces) {
|
||||
auto workspaceJson = std::ranges::find_if(workspacesJson, [&](Json::Value const &x) {
|
||||
return x["name"].asString() == workspace->name() ||
|
||||
(workspace->isSpecial() && x["name"].asString() == "special:" + workspace->name());
|
||||
@@ -944,9 +1046,17 @@ bool Workspaces::updateWindowsToCreate() {
|
||||
void Workspaces::updateWorkspaceStates() {
|
||||
const std::vector<int> visibleWorkspaces = getVisibleWorkspaces();
|
||||
auto updatedWorkspaces = m_ipc.getSocket1JsonReply("workspaces");
|
||||
|
||||
auto currentWorkspace = m_ipc.getSocket1JsonReply("activeworkspace");
|
||||
std::string currentWorkspaceName =
|
||||
currentWorkspace.isMember("name") ? currentWorkspace["name"].asString() : "";
|
||||
|
||||
for (auto &workspace : m_workspaces) {
|
||||
bool isActiveByName =
|
||||
!currentWorkspaceName.empty() && workspace->name() == currentWorkspaceName;
|
||||
|
||||
workspace->setActive(
|
||||
workspace->id() == m_activeWorkspaceId ||
|
||||
workspace->id() == m_activeWorkspaceId || isActiveByName ||
|
||||
(workspace->isSpecial() && workspace->name() == m_activeSpecialWorkspaceName));
|
||||
if (workspace->isActive() && workspace->isUrgent()) {
|
||||
workspace->setUrgent(false);
|
||||
@@ -965,7 +1075,7 @@ void Workspaces::updateWorkspaceStates() {
|
||||
if (updatedWorkspace != updatedWorkspaces.end()) {
|
||||
workspace->setOutput((*updatedWorkspace)["monitor"].asString());
|
||||
}
|
||||
workspace->update(m_format, workspaceIcon);
|
||||
workspace->update(workspaceIcon);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1024,4 +1134,4 @@ std::optional<int> Workspaces::parseWorkspaceId(std::string const &workspaceIdSt
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace waybar::modules::hyprland
|
||||
} // namespace waybar::modules::hyprland
|
||||
@@ -118,7 +118,7 @@ void waybar::modules::MPD::setLabel() {
|
||||
|
||||
auto format = format_;
|
||||
Glib::ustring artist, album_artist, album, title;
|
||||
std::string date, filename;
|
||||
std::string date, filename, uri;
|
||||
int song_pos = 0, queue_length = 0, volume = 0;
|
||||
std::chrono::seconds elapsedTime, totalTime;
|
||||
|
||||
@@ -151,6 +151,7 @@ void waybar::modules::MPD::setLabel() {
|
||||
title = sanitize_string(getTag(MPD_TAG_TITLE));
|
||||
date = sanitize_string(getTag(MPD_TAG_DATE));
|
||||
filename = sanitize_string(getFilename());
|
||||
uri = mpd_song_get_uri(song_.get());
|
||||
song_pos = mpd_status_get_song_pos(status_.get()) + 1;
|
||||
volume = mpd_status_get_volume(status_.get());
|
||||
if (volume < 0) {
|
||||
@@ -184,7 +185,7 @@ void waybar::modules::MPD::setLabel() {
|
||||
fmt::arg("songPosition", song_pos), fmt::arg("queueLength", queue_length),
|
||||
fmt::arg("stateIcon", stateIcon), fmt::arg("consumeIcon", consumeIcon),
|
||||
fmt::arg("randomIcon", randomIcon), fmt::arg("repeatIcon", repeatIcon),
|
||||
fmt::arg("singleIcon", singleIcon), fmt::arg("filename", filename));
|
||||
fmt::arg("singleIcon", singleIcon), fmt::arg("filename", filename), fmt::arg("uri", uri));
|
||||
if (text.empty()) {
|
||||
label_.hide();
|
||||
} else {
|
||||
@@ -200,15 +201,15 @@ void waybar::modules::MPD::setLabel() {
|
||||
tooltip_format = config_["tooltip-format"].isString() ? config_["tooltip-format"].asString()
|
||||
: "MPD (connected)";
|
||||
try {
|
||||
auto tooltip_text =
|
||||
fmt::format(fmt::runtime(tooltip_format), fmt::arg("artist", artist.raw()),
|
||||
fmt::arg("albumArtist", album_artist.raw()), fmt::arg("album", album.raw()),
|
||||
fmt::arg("title", title.raw()), fmt::arg("date", date),
|
||||
fmt::arg("volume", volume), fmt::arg("elapsedTime", elapsedTime),
|
||||
fmt::arg("totalTime", totalTime), fmt::arg("songPosition", song_pos),
|
||||
fmt::arg("queueLength", queue_length), fmt::arg("stateIcon", stateIcon),
|
||||
fmt::arg("consumeIcon", consumeIcon), fmt::arg("randomIcon", randomIcon),
|
||||
fmt::arg("repeatIcon", repeatIcon), fmt::arg("singleIcon", singleIcon));
|
||||
auto tooltip_text = fmt::format(
|
||||
fmt::runtime(tooltip_format), fmt::arg("artist", artist.raw()),
|
||||
fmt::arg("albumArtist", album_artist.raw()), fmt::arg("album", album.raw()),
|
||||
fmt::arg("title", title.raw()), fmt::arg("date", date), fmt::arg("volume", volume),
|
||||
fmt::arg("elapsedTime", elapsedTime), fmt::arg("totalTime", totalTime),
|
||||
fmt::arg("songPosition", song_pos), fmt::arg("queueLength", queue_length),
|
||||
fmt::arg("stateIcon", stateIcon), fmt::arg("consumeIcon", consumeIcon),
|
||||
fmt::arg("randomIcon", randomIcon), fmt::arg("repeatIcon", repeatIcon),
|
||||
fmt::arg("singleIcon", singleIcon), fmt::arg("filename", filename), fmt::arg("uri", uri));
|
||||
label_.set_tooltip_text(tooltip_text);
|
||||
} catch (fmt::format_error const& e) {
|
||||
spdlog::warn("mpd: format error (tooltip): {}", e.what());
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
#include "modules/network.hpp"
|
||||
|
||||
#include <linux/if.h>
|
||||
#include <linux/if_link.h>
|
||||
#include <netlink/netlink.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <sys/eventfd.h>
|
||||
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "util/format.hpp"
|
||||
#ifdef WANT_RFKILL
|
||||
@@ -78,25 +83,7 @@ waybar::modules::Network::readBandwidthUsage() {
|
||||
}
|
||||
|
||||
waybar::modules::Network::Network(const std::string &id, const Json::Value &config)
|
||||
: ALabel(config, "network", id, DEFAULT_FORMAT, 60),
|
||||
ifid_(-1),
|
||||
addr_pref_(IPV4),
|
||||
efd_(-1),
|
||||
ev_fd_(-1),
|
||||
want_route_dump_(false),
|
||||
want_link_dump_(false),
|
||||
want_addr_dump_(false),
|
||||
dump_in_progress_(false),
|
||||
is_p2p_(false),
|
||||
cidr_(0),
|
||||
cidr6_(0),
|
||||
signal_strength_dbm_(0),
|
||||
signal_strength_(0),
|
||||
#ifdef WANT_RFKILL
|
||||
rfkill_{RFKILL_TYPE_WLAN},
|
||||
#endif
|
||||
frequency_(0.0) {
|
||||
|
||||
: ALabel(config, "network", id, DEFAULT_FORMAT, 60) {
|
||||
// Start with some "text" in the module's label_. update() will then
|
||||
// update it. Since the text should be different, update() will be able
|
||||
// to show or hide the event_box_. This is to work around the case where
|
||||
@@ -271,13 +258,16 @@ void waybar::modules::Network::worker() {
|
||||
}
|
||||
|
||||
const std::string waybar::modules::Network::getNetworkState() const {
|
||||
if (ifid_ == -1 || !carrier_) {
|
||||
#ifdef WANT_RFKILL
|
||||
if (rfkill_.getState()) return "disabled";
|
||||
bool display_rfkill = true;
|
||||
if (config_["rfkill"].isBool()) {
|
||||
display_rfkill = config_["rfkill"].asBool();
|
||||
}
|
||||
if (rfkill_.getState() && display_rfkill) return "disabled";
|
||||
#endif
|
||||
if (ifid_ == -1) {
|
||||
return "disconnected";
|
||||
}
|
||||
if (!carrier_) return "disconnected";
|
||||
if (ipaddr_.empty() && ipaddr6_.empty()) return "linked";
|
||||
if (essid_.empty()) return "ethernet";
|
||||
return "wifi";
|
||||
@@ -401,11 +391,65 @@ auto waybar::modules::Network::update() -> void {
|
||||
ALabel::update();
|
||||
}
|
||||
|
||||
bool waybar::modules::Network::checkInterface(std::string name) {
|
||||
if (config_["interface"].isString()) {
|
||||
return config_["interface"].asString() == name ||
|
||||
wildcardMatch(config_["interface"].asString(), name);
|
||||
// https://gist.github.com/rressi/92af77630faf055934c723ce93ae2495
|
||||
static bool wildcardMatch(const std::string &pattern, const std::string &text) {
|
||||
auto P = int(pattern.size());
|
||||
auto T = int(text.size());
|
||||
|
||||
auto p = 0, fallback_p = -1;
|
||||
auto t = 0, fallback_t = -1;
|
||||
|
||||
while (t < T) {
|
||||
// Wildcard match:
|
||||
if (p < P && pattern[p] == '*') {
|
||||
fallback_p = p++; // starting point after failures
|
||||
fallback_t = t; // starting point after failures
|
||||
}
|
||||
|
||||
// Simple match:
|
||||
else if (p < P && (pattern[p] == '?' || pattern[p] == text[t])) {
|
||||
p++;
|
||||
t++;
|
||||
}
|
||||
|
||||
// Failure, fall back just after last matched '*':
|
||||
else if (fallback_p >= 0) {
|
||||
p = fallback_p + 1; // position just after last matched '*"
|
||||
t = ++fallback_t; // re-try to match text from here
|
||||
}
|
||||
|
||||
// There were no '*' before, so we fail here:
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Consume all '*' at the end of pattern:
|
||||
while (p < P && pattern[p] == '*') p++;
|
||||
|
||||
return p == P;
|
||||
}
|
||||
|
||||
bool waybar::modules::Network::matchInterface(const std::string &ifname,
|
||||
const std::vector<std::string> &altnames,
|
||||
std::string &matched) const {
|
||||
if (!config_["interface"].isString()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto config_ifname = config_["interface"].asString();
|
||||
if (config_ifname == ifname || wildcardMatch(config_ifname, ifname)) {
|
||||
matched = ifname;
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const auto &altname : altnames) {
|
||||
if (config_ifname == altname || wildcardMatch(config_ifname, altname)) {
|
||||
matched = altname;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -420,6 +464,7 @@ void waybar::modules::Network::clearIface() {
|
||||
netmask_.clear();
|
||||
netmask6_.clear();
|
||||
carrier_ = false;
|
||||
is_p2p_ = false;
|
||||
cidr_ = 0;
|
||||
cidr6_ = 0;
|
||||
signal_strength_dbm_ = 0;
|
||||
@@ -439,12 +484,16 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
|
||||
is_del_event = true;
|
||||
case RTM_NEWLINK: {
|
||||
struct ifinfomsg *ifi = static_cast<struct ifinfomsg *>(NLMSG_DATA(nh));
|
||||
ssize_t attrlen = IFLA_PAYLOAD(nh);
|
||||
struct rtattr *ifla = IFLA_RTA(ifi);
|
||||
const char *ifname = NULL;
|
||||
size_t ifname_len = 0;
|
||||
struct nlattr *attrs[IFLA_MAX + 1];
|
||||
std::string ifname;
|
||||
std::vector<std::string> altnames;
|
||||
std::optional<bool> carrier;
|
||||
|
||||
if (nlmsg_parse(nh, sizeof(*ifi), attrs, IFLA_MAX, nullptr) < 0) {
|
||||
spdlog::error("network: failed to parse netlink attributes");
|
||||
return NL_SKIP;
|
||||
}
|
||||
|
||||
if (net->ifid_ != -1 && ifi->ifi_index != net->ifid_) {
|
||||
return NL_OK;
|
||||
}
|
||||
@@ -462,26 +511,33 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
|
||||
return NL_OK;
|
||||
}
|
||||
|
||||
for (; RTA_OK(ifla, attrlen); ifla = RTA_NEXT(ifla, attrlen)) {
|
||||
switch (ifla->rta_type) {
|
||||
case IFLA_IFNAME:
|
||||
ifname = static_cast<const char *>(RTA_DATA(ifla));
|
||||
ifname_len = RTA_PAYLOAD(ifla) - 1; // minus \0
|
||||
if (ifi->ifi_flags & IFF_POINTOPOINT && net->checkInterface(ifname))
|
||||
net->is_p2p_ = true;
|
||||
break;
|
||||
case IFLA_CARRIER: {
|
||||
carrier = *(char *)RTA_DATA(ifla) == 1;
|
||||
break;
|
||||
if (attrs[IFLA_IFNAME] != nullptr) {
|
||||
const char *ifname_ptr = nla_get_string(attrs[IFLA_IFNAME]);
|
||||
size_t ifname_len = nla_len(attrs[IFLA_IFNAME]) - 1; // minus \0
|
||||
ifname = std::string(ifname_ptr, ifname_len);
|
||||
}
|
||||
|
||||
if (attrs[IFLA_CARRIER] != nullptr) {
|
||||
carrier = nla_get_u8(attrs[IFLA_CARRIER]) == 1;
|
||||
}
|
||||
|
||||
if (attrs[IFLA_PROP_LIST] != nullptr) {
|
||||
struct nlattr *prop;
|
||||
int rem;
|
||||
|
||||
nla_for_each_nested(prop, attrs[IFLA_PROP_LIST], rem) {
|
||||
if (nla_type(prop) == IFLA_ALT_IFNAME) {
|
||||
const char *altname_ptr = nla_get_string(prop);
|
||||
size_t altname_len = nla_len(prop) - 1; // minus \0
|
||||
altnames.emplace_back(altname_ptr, altname_len);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_del_event && ifi->ifi_index == net->ifid_) {
|
||||
// Update interface information
|
||||
if (net->ifname_.empty() && ifname != NULL) {
|
||||
std::string new_ifname(ifname, ifname_len);
|
||||
net->ifname_ = new_ifname;
|
||||
if (net->ifname_.empty() && !ifname.empty()) {
|
||||
net->ifname_ = ifname;
|
||||
}
|
||||
if (carrier.has_value()) {
|
||||
if (net->carrier_ != *carrier) {
|
||||
@@ -502,13 +558,25 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
|
||||
}
|
||||
} else if (!is_del_event && net->ifid_ == -1) {
|
||||
// Checking if it's an interface we care about.
|
||||
std::string new_ifname(ifname, ifname_len);
|
||||
if (net->checkInterface(new_ifname)) {
|
||||
spdlog::debug("network: selecting new interface {}/{}", new_ifname, ifi->ifi_index);
|
||||
std::string matched;
|
||||
if (net->matchInterface(ifname, altnames, matched)) {
|
||||
if (ifname == matched) {
|
||||
spdlog::debug("network: selecting new interface {}/{}", ifname, ifi->ifi_index);
|
||||
} else {
|
||||
spdlog::debug("network: selecting new interface {}/{} (matched altname {})", ifname,
|
||||
ifi->ifi_index, matched);
|
||||
}
|
||||
|
||||
net->ifname_ = new_ifname;
|
||||
net->ifname_ = ifname;
|
||||
net->ifid_ = ifi->ifi_index;
|
||||
if (ifi->ifi_flags & IFF_POINTOPOINT) net->is_p2p_ = true;
|
||||
if ((ifi->ifi_flags & IFF_POINTOPOINT) != 0) {
|
||||
net->is_p2p_ = true;
|
||||
}
|
||||
if ((ifi->ifi_flags & IFF_UP) == 0) {
|
||||
// With some network drivers (e.g. mt7921e), the interface may
|
||||
// report having a carrier even though interface is down.
|
||||
carrier = false;
|
||||
}
|
||||
if (carrier.has_value()) {
|
||||
net->carrier_ = carrier.value();
|
||||
}
|
||||
@@ -602,7 +670,6 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
|
||||
break;
|
||||
}
|
||||
|
||||
char temp_gw_addr[INET6_ADDRSTRLEN];
|
||||
case RTM_DELROUTE:
|
||||
is_del_event = true;
|
||||
case RTM_NEWROUTE: {
|
||||
@@ -613,6 +680,7 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
|
||||
int family = rtm->rtm_family;
|
||||
ssize_t attrlen = RTM_PAYLOAD(nh);
|
||||
struct rtattr *attr = RTM_RTA(rtm);
|
||||
char gateway_addr[INET6_ADDRSTRLEN];
|
||||
bool has_gateway = false;
|
||||
bool has_destination = false;
|
||||
int temp_idx = -1;
|
||||
@@ -638,7 +706,7 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
|
||||
* If someone ever needs to figure out the gateway address as well,
|
||||
* it's here as the attribute payload.
|
||||
*/
|
||||
inet_ntop(family, RTA_DATA(attr), temp_gw_addr, sizeof(temp_gw_addr));
|
||||
inet_ntop(family, RTA_DATA(attr), gateway_addr, sizeof(gateway_addr));
|
||||
has_gateway = true;
|
||||
break;
|
||||
case RTA_DST: {
|
||||
@@ -683,8 +751,8 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
|
||||
net->clearIface();
|
||||
net->ifid_ = temp_idx;
|
||||
net->route_priority = priority;
|
||||
net->gwaddr_ = temp_gw_addr;
|
||||
spdlog::debug("network: new default route via {} on if{} metric {}", temp_gw_addr,
|
||||
net->gwaddr_ = gateway_addr;
|
||||
spdlog::debug("network: new default route via {} on if{} metric {}", gateway_addr,
|
||||
temp_idx, priority);
|
||||
|
||||
/* Ask ifname associated with temp_idx as well as carrier status */
|
||||
@@ -897,43 +965,3 @@ auto waybar::modules::Network::getInfo() -> void {
|
||||
}
|
||||
nl_send_sync(sock_, nl_msg);
|
||||
}
|
||||
|
||||
// https://gist.github.com/rressi/92af77630faf055934c723ce93ae2495
|
||||
bool waybar::modules::Network::wildcardMatch(const std::string &pattern,
|
||||
const std::string &text) const {
|
||||
auto P = int(pattern.size());
|
||||
auto T = int(text.size());
|
||||
|
||||
auto p = 0, fallback_p = -1;
|
||||
auto t = 0, fallback_t = -1;
|
||||
|
||||
while (t < T) {
|
||||
// Wildcard match:
|
||||
if (p < P && pattern[p] == '*') {
|
||||
fallback_p = p++; // starting point after failures
|
||||
fallback_t = t; // starting point after failures
|
||||
}
|
||||
|
||||
// Simple match:
|
||||
else if (p < P && (pattern[p] == '?' || pattern[p] == text[t])) {
|
||||
p++;
|
||||
t++;
|
||||
}
|
||||
|
||||
// Failure, fall back just after last matched '*':
|
||||
else if (fallback_p >= 0) {
|
||||
p = fallback_p + 1; // position just after last matched '*"
|
||||
t = ++fallback_t; // re-try to match text from here
|
||||
}
|
||||
|
||||
// There were no '*' before, so we fail here:
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Consume all '*' at the end of pattern:
|
||||
while (p < P && pattern[p] == '*') p++;
|
||||
|
||||
return p == P;
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@ auto waybar::modules::Pulseaudio::update() -> void {
|
||||
tooltip_format = config_["tooltip-format"].asString();
|
||||
}
|
||||
if (!tooltip_format.empty()) {
|
||||
label_.set_tooltip_text(fmt::format(
|
||||
label_.set_tooltip_markup(fmt::format(
|
||||
fmt::runtime(tooltip_format), fmt::arg("desc", sink_desc),
|
||||
fmt::arg("volume", sink_volume), fmt::arg("format_source", format_source),
|
||||
fmt::arg("source_volume", source_volume), fmt::arg("source_desc", source_desc),
|
||||
|
||||
@@ -145,9 +145,15 @@ void Layout::handle_name(const char *name) {
|
||||
if (std::strcmp(name, "") == 0 || format_.empty()) {
|
||||
label_.hide(); // hide empty labels or labels with empty format
|
||||
} else {
|
||||
label_.show();
|
||||
if (!name_.empty()) {
|
||||
label_.get_style_context()->remove_class(name_);
|
||||
}
|
||||
|
||||
label_.get_style_context()->add_class(name);
|
||||
label_.set_markup(fmt::format(fmt::runtime(format_), Glib::Markup::escape_text(name).raw()));
|
||||
label_.show();
|
||||
}
|
||||
name_ = name;
|
||||
ALabel::update();
|
||||
}
|
||||
|
||||
|
||||
@@ -140,7 +140,25 @@ void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) {
|
||||
category = get_variant<std::string>(value);
|
||||
} else if (name == "Id") {
|
||||
id = get_variant<std::string>(value);
|
||||
setCustomIcon(id);
|
||||
|
||||
/*
|
||||
* HACK: Electron apps seem to have the same ID, but tooltip seems correct, so use that as ID
|
||||
* to pass as the custom icon option. I'm avoiding being disruptive and setting that to the ID
|
||||
* itself as I've no idea what this would affect.
|
||||
* The tooltip text is converted to lowercase since that's what (most?) themes expect?
|
||||
* I still haven't found a way for it to pick from theme automatically, although
|
||||
* it might be my theme.
|
||||
*/
|
||||
if (id == "chrome_status_icon_1") {
|
||||
Glib::VariantBase value;
|
||||
this->proxy_->get_cached_property(value, "ToolTip");
|
||||
tooltip = get_variant<ToolTip>(value);
|
||||
if (!tooltip.text.empty()) {
|
||||
setCustomIcon(tooltip.text.lowercase());
|
||||
}
|
||||
} else {
|
||||
setCustomIcon(id);
|
||||
}
|
||||
} else if (name == "Title") {
|
||||
title = get_variant<std::string>(value);
|
||||
if (tooltip.text.empty()) {
|
||||
@@ -203,6 +221,8 @@ void Item::setStatus(const Glib::ustring& value) {
|
||||
}
|
||||
|
||||
void Item::setCustomIcon(const std::string& id) {
|
||||
spdlog::debug("SNI tray id: {}", id);
|
||||
|
||||
std::string custom_icon = IconManager::instance().getIconForApp(id);
|
||||
if (!custom_icon.empty()) {
|
||||
if (std::filesystem::exists(custom_icon)) {
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "modules/sni/icon_manager.hpp"
|
||||
|
||||
namespace waybar::modules::SNI {
|
||||
@@ -21,6 +23,9 @@ Tray::Tray(const std::string& id, const Bar& bar, const Json::Value& config)
|
||||
if (config_["spacing"].isUInt()) {
|
||||
box_.set_spacing(config_["spacing"].asUInt());
|
||||
}
|
||||
if (config["show-passive-items"].isBool()) {
|
||||
show_passive_ = config["show-passive-items"].asBool();
|
||||
}
|
||||
nb_hosts_ += 1;
|
||||
if (config_["icons"].isObject()) {
|
||||
IconManager::instance().setIconsConfig(config_["icons"]);
|
||||
@@ -44,7 +49,15 @@ void Tray::onRemove(std::unique_ptr<Item>& item) {
|
||||
|
||||
auto Tray::update() -> void {
|
||||
// Show tray only when items are available
|
||||
event_box_.set_visible(!box_.get_children().empty());
|
||||
std::vector<Gtk::Widget*> children = box_.get_children();
|
||||
if (show_passive_) {
|
||||
event_box_.set_visible(!children.empty());
|
||||
} else {
|
||||
event_box_.set_visible(!std::all_of(children.begin(), children.end(), [](Gtk::Widget* child) {
|
||||
return child->get_style_context()->has_class("passive");
|
||||
}));
|
||||
}
|
||||
|
||||
// Call parent update
|
||||
AModule::update();
|
||||
}
|
||||
|
||||
@@ -443,10 +443,11 @@ bool Workspaces::handleScroll(GdkEventScroll *e) {
|
||||
if (it == workspaces_.end()) {
|
||||
return true;
|
||||
}
|
||||
bool reverse_scroll = config_["reverse-scroll"].isBool() && config_["reverse-scroll"].asBool();
|
||||
if (dir == SCROLL_DIR::DOWN || dir == SCROLL_DIR::RIGHT) {
|
||||
name = getCycleWorkspace(it, false);
|
||||
name = getCycleWorkspace(it, reverse_scroll ? true : false);
|
||||
} else if (dir == SCROLL_DIR::UP || dir == SCROLL_DIR::LEFT) {
|
||||
name = getCycleWorkspace(it, true);
|
||||
name = getCycleWorkspace(it, reverse_scroll ? false : true);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ UPower::UPower(const std::string &id, const Json::Value &config)
|
||||
: AIconLabel(config, "upower", id, "{percentage}", 0, true, true, true), sleeping_{false} {
|
||||
box_.set_name(name_);
|
||||
box_.set_spacing(0);
|
||||
box_.set_has_tooltip(AModule::tooltipEnabled());
|
||||
// Tooltip box
|
||||
contentBox_.set_orientation((box_.get_orientation() == Gtk::ORIENTATION_HORIZONTAL)
|
||||
? Gtk::ORIENTATION_VERTICAL
|
||||
@@ -70,8 +69,10 @@ UPower::UPower(const std::string &id, const Json::Value &config)
|
||||
g_signal_connect(upClient_, "device-removed", G_CALLBACK(deviceRemoved_cb), this);
|
||||
|
||||
// Subscribe tooltip query events
|
||||
box_.set_has_tooltip();
|
||||
box_.signal_query_tooltip().connect(sigc::mem_fun(*this, &UPower::queryTooltipCb), false);
|
||||
box_.set_has_tooltip(AModule::tooltipEnabled());
|
||||
if (AModule::tooltipEnabled()) {
|
||||
box_.signal_query_tooltip().connect(sigc::mem_fun(*this, &UPower::queryTooltipCb), false);
|
||||
}
|
||||
|
||||
resetDevices();
|
||||
setDisplayDevice();
|
||||
|
||||
@@ -62,7 +62,13 @@ long User::uptime_as_seconds() {
|
||||
|
||||
#if HAVE_CPU_BSD
|
||||
struct timespec s_info;
|
||||
if (0 == clock_gettime(CLOCK_UPTIME_PRECISE, &s_info)) {
|
||||
int flags = 0;
|
||||
#ifndef __OpenBSD__
|
||||
flags = CLOCK_UPTIME_PRECISE;
|
||||
#else
|
||||
flags = CLOCK_UPTIME;
|
||||
#endif
|
||||
if (0 == clock_gettime(flags, &s_info)) {
|
||||
uptime = s_info.tv_sec;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -74,9 +74,9 @@ auto Workspaces::handleScroll(GdkEventScroll* e) -> bool {
|
||||
const auto& wset = ipc->get_wsets().at(output.wset_idx);
|
||||
auto n = wset.ws_w * wset.ws_h;
|
||||
auto i = (wset.ws_idx() + delta + n) % n;
|
||||
data["x"] = i % wset.ws_w;
|
||||
data["y"] = i / wset.ws_h;
|
||||
data["output-id"] = output.id;
|
||||
data["x"] = Json::Value((uint64_t)i % wset.ws_w);
|
||||
data["y"] = Json::Value((uint64_t)i / wset.ws_h);
|
||||
data["output-id"] = Json::Value((uint64_t)output.id);
|
||||
}
|
||||
ipc->send("vswitch/set-workspace", std::move(data));
|
||||
|
||||
@@ -108,9 +108,9 @@ auto Workspaces::update_box() -> void {
|
||||
if (!config_["disable-click"].asBool()) {
|
||||
btn.signal_pressed().connect([=, this] {
|
||||
Json::Value data;
|
||||
data["x"] = i % ws_w;
|
||||
data["y"] = i / ws_h;
|
||||
data["output-id"] = output.id;
|
||||
data["x"] = Json::Value((uint64_t)i % ws_w);
|
||||
data["y"] = Json::Value((uint64_t)i / ws_h);
|
||||
data["output-id"] = Json::Value((uint64_t)output.id);
|
||||
ipc->send("vswitch/set-workspace", std::move(data));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -14,11 +14,15 @@ waybar::modules::Wireplumber::Wireplumber(const std::string& id, const Json::Val
|
||||
mixer_api_(nullptr),
|
||||
def_nodes_api_(nullptr),
|
||||
default_node_name_(nullptr),
|
||||
default_source_name_(nullptr),
|
||||
pending_plugins_(0),
|
||||
muted_(false),
|
||||
source_muted_(false),
|
||||
volume_(0.0),
|
||||
source_volume_(0.0),
|
||||
min_step_(0.0),
|
||||
node_id_(0),
|
||||
source_node_id_(0),
|
||||
type_(nullptr) {
|
||||
waybar::modules::Wireplumber::modules.push_back(this);
|
||||
|
||||
@@ -55,6 +59,7 @@ waybar::modules::Wireplumber::~Wireplumber() {
|
||||
g_clear_object(&mixer_api_);
|
||||
g_clear_object(&def_nodes_api_);
|
||||
g_free(default_node_name_);
|
||||
g_free(default_source_name_);
|
||||
g_free(type_);
|
||||
}
|
||||
|
||||
@@ -94,6 +99,43 @@ void waybar::modules::Wireplumber::updateNodeName(waybar::modules::Wireplumber*
|
||||
spdlog::debug("[{}]: Updating '{}' node name to: {}", self->name_, self->type_, self->node_name_);
|
||||
}
|
||||
|
||||
void waybar::modules::Wireplumber::updateSourceName(waybar::modules::Wireplumber* self,
|
||||
uint32_t id) {
|
||||
spdlog::debug("[{}]: updating source name with node.id {}", self->name_, id);
|
||||
|
||||
if (!isValidNodeId(id)) {
|
||||
spdlog::warn("[{}]: '{}' is not a valid source node ID. Ignoring source name update.",
|
||||
self->name_, id);
|
||||
return;
|
||||
}
|
||||
|
||||
auto* proxy = static_cast<WpProxy*>(wp_object_manager_lookup(self->om_, WP_TYPE_GLOBAL_PROXY,
|
||||
WP_CONSTRAINT_TYPE_G_PROPERTY,
|
||||
"bound-id", "=u", id, nullptr));
|
||||
|
||||
if (proxy == nullptr) {
|
||||
auto err = fmt::format("Source object '{}' not found\n", id);
|
||||
spdlog::error("[{}]: {}", self->name_, err);
|
||||
return;
|
||||
}
|
||||
|
||||
g_autoptr(WpProperties) properties =
|
||||
WP_IS_PIPEWIRE_OBJECT(proxy) != 0
|
||||
? wp_pipewire_object_get_properties(WP_PIPEWIRE_OBJECT(proxy))
|
||||
: wp_properties_new_empty();
|
||||
g_autoptr(WpProperties) globalP = wp_global_proxy_get_global_properties(WP_GLOBAL_PROXY(proxy));
|
||||
properties = wp_properties_ensure_unique_owner(properties);
|
||||
wp_properties_add(properties, globalP);
|
||||
wp_properties_set(properties, "object.id", nullptr);
|
||||
const auto* nick = wp_properties_get(properties, "node.nick");
|
||||
const auto* description = wp_properties_get(properties, "node.description");
|
||||
|
||||
self->source_name_ = nick != nullptr ? nick
|
||||
: description != nullptr ? description
|
||||
: "Unknown source name";
|
||||
spdlog::debug("[{}]: Updating source name to: {}", self->name_, self->source_name_);
|
||||
}
|
||||
|
||||
void waybar::modules::Wireplumber::updateVolume(waybar::modules::Wireplumber* self, uint32_t id) {
|
||||
spdlog::debug("[{}]: updating volume", self->name_);
|
||||
GVariant* variant = nullptr;
|
||||
@@ -120,6 +162,31 @@ void waybar::modules::Wireplumber::updateVolume(waybar::modules::Wireplumber* se
|
||||
self->dp.emit();
|
||||
}
|
||||
|
||||
void waybar::modules::Wireplumber::updateSourceVolume(waybar::modules::Wireplumber* self,
|
||||
uint32_t id) {
|
||||
spdlog::debug("[{}]: updating source volume", self->name_);
|
||||
GVariant* variant = nullptr;
|
||||
|
||||
if (!isValidNodeId(id)) {
|
||||
spdlog::error("[{}]: '{}' is not a valid source node ID. Ignoring source volume update.",
|
||||
self->name_, id);
|
||||
return;
|
||||
}
|
||||
|
||||
g_signal_emit_by_name(self->mixer_api_, "get-volume", id, &variant);
|
||||
|
||||
if (variant == nullptr) {
|
||||
spdlog::debug("[{}]: Source node {} does not support volume", self->name_, id);
|
||||
return;
|
||||
}
|
||||
|
||||
g_variant_lookup(variant, "volume", "d", &self->source_volume_);
|
||||
g_variant_lookup(variant, "mute", "b", &self->source_muted_);
|
||||
g_clear_pointer(&variant, g_variant_unref);
|
||||
|
||||
self->dp.emit();
|
||||
}
|
||||
|
||||
void waybar::modules::Wireplumber::onMixerChanged(waybar::modules::Wireplumber* self, uint32_t id) {
|
||||
g_autoptr(WpNode) node = static_cast<WpNode*>(wp_object_manager_lookup(
|
||||
self->om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_G_PROPERTY, "bound-id", "=u", id, nullptr));
|
||||
@@ -127,9 +194,9 @@ void waybar::modules::Wireplumber::onMixerChanged(waybar::modules::Wireplumber*
|
||||
if (node == nullptr) {
|
||||
// log a warning only if no other widget is targeting the id.
|
||||
// this reduces log spam when multiple instances of the module are used on different node types.
|
||||
if (id != self->node_id_) {
|
||||
if (id != self->node_id_ && id != self->source_node_id_) {
|
||||
for (auto const& module : waybar::modules::Wireplumber::modules) {
|
||||
if (module->node_id_ == id) {
|
||||
if (module->node_id_ == id || module->source_node_id_ == id) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -142,68 +209,73 @@ void waybar::modules::Wireplumber::onMixerChanged(waybar::modules::Wireplumber*
|
||||
|
||||
const gchar* name = wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(node), "node.name");
|
||||
|
||||
if (self->node_id_ != id) {
|
||||
spdlog::debug(
|
||||
"[{}]: (onMixerChanged: {}) - ignoring mixer update for node: id: {}, name: {} as it is "
|
||||
"not the default node: {} with id: {}",
|
||||
self->name_, self->type_, id, name, self->default_node_name_, self->node_id_);
|
||||
return;
|
||||
if (self->node_id_ == id) {
|
||||
spdlog::debug("[{}]: (onMixerChanged: {}) - updating sink volume for node: {}", self->name_,
|
||||
self->type_, name);
|
||||
updateVolume(self, id);
|
||||
} else if (self->source_node_id_ == id) {
|
||||
spdlog::debug("[{}]: (onMixerChanged: {}) - updating source volume for node: {}", self->name_,
|
||||
self->type_, name);
|
||||
updateSourceVolume(self, id);
|
||||
}
|
||||
|
||||
spdlog::debug(
|
||||
"[{}]: (onMixerChanged: {}) - Need to update volume for node with id {} and name {}",
|
||||
self->name_, self->type_, id, name);
|
||||
updateVolume(self, id);
|
||||
}
|
||||
|
||||
void waybar::modules::Wireplumber::onDefaultNodesApiChanged(waybar::modules::Wireplumber* self) {
|
||||
spdlog::debug("[{}]: (onDefaultNodesApiChanged: {})", self->name_, self->type_);
|
||||
|
||||
// Handle sink
|
||||
uint32_t defaultNodeId;
|
||||
g_signal_emit_by_name(self->def_nodes_api_, "get-default-node", self->type_, &defaultNodeId);
|
||||
|
||||
if (!isValidNodeId(defaultNodeId)) {
|
||||
spdlog::warn("[{}]: '{}' is not a valid node ID. Ignoring '{}' node change.", self->name_,
|
||||
defaultNodeId, self->type_);
|
||||
return;
|
||||
if (isValidNodeId(defaultNodeId)) {
|
||||
g_autoptr(WpNode) node = static_cast<WpNode*>(
|
||||
wp_object_manager_lookup(self->om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_G_PROPERTY, "bound-id",
|
||||
"=u", defaultNodeId, nullptr));
|
||||
|
||||
if (node != nullptr) {
|
||||
const gchar* defaultNodeName =
|
||||
wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(node), "node.name");
|
||||
|
||||
if (g_strcmp0(self->default_node_name_, defaultNodeName) != 0 ||
|
||||
self->node_id_ != defaultNodeId) {
|
||||
spdlog::debug("[{}]: Default sink changed to -> Node(name: {}, id: {})", self->name_,
|
||||
defaultNodeName, defaultNodeId);
|
||||
|
||||
g_free(self->default_node_name_);
|
||||
self->default_node_name_ = g_strdup(defaultNodeName);
|
||||
self->node_id_ = defaultNodeId;
|
||||
updateVolume(self, defaultNodeId);
|
||||
updateNodeName(self, defaultNodeId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
g_autoptr(WpNode) node = static_cast<WpNode*>(
|
||||
wp_object_manager_lookup(self->om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_G_PROPERTY, "bound-id",
|
||||
"=u", defaultNodeId, nullptr));
|
||||
// Handle source
|
||||
uint32_t defaultSourceId;
|
||||
g_signal_emit_by_name(self->def_nodes_api_, "get-default-node", "Audio/Source", &defaultSourceId);
|
||||
|
||||
if (node == nullptr) {
|
||||
spdlog::warn("[{}]: (onDefaultNodesApiChanged: {}) - Object with id {} not found", self->name_,
|
||||
self->type_, defaultNodeId);
|
||||
return;
|
||||
if (isValidNodeId(defaultSourceId)) {
|
||||
g_autoptr(WpNode) sourceNode = static_cast<WpNode*>(
|
||||
wp_object_manager_lookup(self->om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_G_PROPERTY, "bound-id",
|
||||
"=u", defaultSourceId, nullptr));
|
||||
|
||||
if (sourceNode != nullptr) {
|
||||
const gchar* defaultSourceName =
|
||||
wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(sourceNode), "node.name");
|
||||
|
||||
if (g_strcmp0(self->default_source_name_, defaultSourceName) != 0 ||
|
||||
self->source_node_id_ != defaultSourceId) {
|
||||
spdlog::debug("[{}]: Default source changed to -> Node(name: {}, id: {})", self->name_,
|
||||
defaultSourceName, defaultSourceId);
|
||||
|
||||
g_free(self->default_source_name_);
|
||||
self->default_source_name_ = g_strdup(defaultSourceName);
|
||||
self->source_node_id_ = defaultSourceId;
|
||||
updateSourceVolume(self, defaultSourceId);
|
||||
updateSourceName(self, defaultSourceId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const gchar* defaultNodeName =
|
||||
wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(node), "node.name");
|
||||
|
||||
spdlog::debug(
|
||||
"[{}]: (onDefaultNodesApiChanged: {}) - got the following default node: Node(name: {}, id: "
|
||||
"{})",
|
||||
self->name_, self->type_, defaultNodeName, defaultNodeId);
|
||||
|
||||
if (g_strcmp0(self->default_node_name_, defaultNodeName) == 0 &&
|
||||
self->node_id_ == defaultNodeId) {
|
||||
spdlog::debug(
|
||||
"[{}]: (onDefaultNodesApiChanged: {}) - Default node has not changed. Node(name: {}, id: "
|
||||
"{}). Ignoring.",
|
||||
self->name_, self->type_, self->default_node_name_, defaultNodeId);
|
||||
return;
|
||||
}
|
||||
|
||||
spdlog::debug(
|
||||
"[{}]: (onDefaultNodesApiChanged: {}) - Default node changed to -> Node(name: {}, id: {})",
|
||||
self->name_, self->type_, defaultNodeName, defaultNodeId);
|
||||
|
||||
g_free(self->default_node_name_);
|
||||
self->default_node_name_ = g_strdup(defaultNodeName);
|
||||
self->node_id_ = defaultNodeId;
|
||||
updateVolume(self, defaultNodeId);
|
||||
updateNodeName(self, defaultNodeId);
|
||||
}
|
||||
|
||||
void waybar::modules::Wireplumber::onObjectManagerInstalled(waybar::modules::Wireplumber* self) {
|
||||
@@ -223,18 +295,31 @@ void waybar::modules::Wireplumber::onObjectManagerInstalled(waybar::modules::Wir
|
||||
throw std::runtime_error("Mixer api is not loaded\n");
|
||||
}
|
||||
|
||||
// Get default sink
|
||||
g_signal_emit_by_name(self->def_nodes_api_, "get-default-configured-node-name", self->type_,
|
||||
&self->default_node_name_);
|
||||
g_signal_emit_by_name(self->def_nodes_api_, "get-default-node", self->type_, &self->node_id_);
|
||||
|
||||
// Get default source
|
||||
g_signal_emit_by_name(self->def_nodes_api_, "get-default-configured-node-name", "Audio/Source",
|
||||
&self->default_source_name_);
|
||||
g_signal_emit_by_name(self->def_nodes_api_, "get-default-node", "Audio/Source",
|
||||
&self->source_node_id_);
|
||||
|
||||
if (self->default_node_name_ != nullptr) {
|
||||
spdlog::debug(
|
||||
"[{}]: (onObjectManagerInstalled: {}) - default configured node name: {} and id: {}",
|
||||
self->name_, self->type_, self->default_node_name_, self->node_id_);
|
||||
}
|
||||
if (self->default_source_name_ != nullptr) {
|
||||
spdlog::debug("[{}]: default source: {} (id: {})", self->name_, self->default_source_name_,
|
||||
self->source_node_id_);
|
||||
}
|
||||
|
||||
updateVolume(self, self->node_id_);
|
||||
updateNodeName(self, self->node_id_);
|
||||
updateSourceVolume(self, self->source_node_id_);
|
||||
updateSourceName(self, self->source_node_id_);
|
||||
|
||||
g_signal_connect_swapped(self->mixer_api_, "changed", (GCallback)onMixerChanged, self);
|
||||
g_signal_connect_swapped(self->def_nodes_api_, "changed", (GCallback)onDefaultNodesApiChanged,
|
||||
@@ -271,6 +356,8 @@ void waybar::modules::Wireplumber::prepare(waybar::modules::Wireplumber* self) {
|
||||
spdlog::debug("[{}]: preparing object manager: '{}'", name_, self->type_);
|
||||
wp_object_manager_add_interest(om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_PW_PROPERTY, "media.class",
|
||||
"=s", self->type_, nullptr);
|
||||
wp_object_manager_add_interest(om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_PW_PROPERTY, "media.class",
|
||||
"=s", "Audio/Source", nullptr);
|
||||
}
|
||||
|
||||
void waybar::modules::Wireplumber::onDefaultNodesApiLoaded(WpObject* p, GAsyncResult* res,
|
||||
@@ -332,19 +419,57 @@ auto waybar::modules::Wireplumber::update() -> void {
|
||||
auto format = format_;
|
||||
std::string tooltipFormat;
|
||||
|
||||
// Handle sink mute state
|
||||
if (muted_) {
|
||||
format = config_["format-muted"].isString() ? config_["format-muted"].asString() : format;
|
||||
label_.get_style_context()->add_class("muted");
|
||||
label_.get_style_context()->add_class("sink-muted");
|
||||
} else {
|
||||
label_.get_style_context()->remove_class("muted");
|
||||
label_.get_style_context()->remove_class("sink-muted");
|
||||
}
|
||||
|
||||
// Handle source mute state
|
||||
if (source_muted_) {
|
||||
label_.get_style_context()->add_class("source-muted");
|
||||
} else {
|
||||
label_.get_style_context()->remove_class("source-muted");
|
||||
}
|
||||
|
||||
int vol = round(volume_ * 100.0);
|
||||
std::string markup = fmt::format(fmt::runtime(format), fmt::arg("node_name", node_name_),
|
||||
fmt::arg("volume", vol), fmt::arg("icon", getIcon(vol)));
|
||||
label_.set_markup(markup);
|
||||
int source_vol = round(source_volume_ * 100.0);
|
||||
|
||||
getState(vol);
|
||||
// Get the state and apply state-specific format if available
|
||||
auto state = getState(vol);
|
||||
if (!state.empty()) {
|
||||
std::string format_name = muted_ ? "format-muted" : "format";
|
||||
std::string state_format_name = format_name + "-" + state;
|
||||
if (config_[state_format_name].isString()) {
|
||||
format = config_[state_format_name].asString();
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare source format string (similar to PulseAudio)
|
||||
std::string format_source = "{volume}%";
|
||||
if (source_muted_) {
|
||||
if (config_["format-source-muted"].isString()) {
|
||||
format_source = config_["format-source-muted"].asString();
|
||||
}
|
||||
} else {
|
||||
if (config_["format-source"].isString()) {
|
||||
format_source = config_["format-source"].asString();
|
||||
}
|
||||
}
|
||||
|
||||
// Format the source string with actual volume
|
||||
std::string formatted_source =
|
||||
fmt::format(fmt::runtime(format_source), fmt::arg("volume", source_vol));
|
||||
|
||||
std::string markup =
|
||||
fmt::format(fmt::runtime(format), fmt::arg("node_name", node_name_), fmt::arg("volume", vol),
|
||||
fmt::arg("icon", getIcon(vol)), fmt::arg("format_source", formatted_source),
|
||||
fmt::arg("source_volume", source_vol), fmt::arg("source_desc", source_name_));
|
||||
label_.set_markup(markup);
|
||||
|
||||
if (tooltipEnabled()) {
|
||||
if (tooltipFormat.empty() && config_["tooltip-format"].isString()) {
|
||||
@@ -352,9 +477,10 @@ auto waybar::modules::Wireplumber::update() -> void {
|
||||
}
|
||||
|
||||
if (!tooltipFormat.empty()) {
|
||||
label_.set_tooltip_text(fmt::format(fmt::runtime(tooltipFormat),
|
||||
fmt::arg("node_name", node_name_),
|
||||
fmt::arg("volume", vol), fmt::arg("icon", getIcon(vol))));
|
||||
label_.set_tooltip_text(fmt::format(
|
||||
fmt::runtime(tooltipFormat), fmt::arg("node_name", node_name_), fmt::arg("volume", vol),
|
||||
fmt::arg("icon", getIcon(vol)), fmt::arg("format_source", formatted_source),
|
||||
fmt::arg("source_volume", source_vol), fmt::arg("source_desc", source_name_)));
|
||||
} else {
|
||||
label_.set_tooltip_text(node_name_);
|
||||
}
|
||||
|
||||
@@ -26,194 +26,6 @@
|
||||
|
||||
namespace waybar::modules::wlr {
|
||||
|
||||
/* Icon loading functions */
|
||||
static std::vector<std::string> search_prefix() {
|
||||
std::vector<std::string> prefixes = {""};
|
||||
|
||||
std::string home_dir = std::getenv("HOME");
|
||||
prefixes.push_back(home_dir + "/.local/share/");
|
||||
|
||||
auto xdg_data_dirs = std::getenv("XDG_DATA_DIRS");
|
||||
if (!xdg_data_dirs) {
|
||||
prefixes.emplace_back("/usr/share/");
|
||||
prefixes.emplace_back("/usr/local/share/");
|
||||
} else {
|
||||
std::string xdg_data_dirs_str(xdg_data_dirs);
|
||||
size_t start = 0, end = 0;
|
||||
|
||||
do {
|
||||
end = xdg_data_dirs_str.find(':', start);
|
||||
auto p = xdg_data_dirs_str.substr(start, end - start);
|
||||
prefixes.push_back(trim(p) + "/");
|
||||
|
||||
start = end == std::string::npos ? end : end + 1;
|
||||
} while (end != std::string::npos);
|
||||
}
|
||||
|
||||
for (auto &p : prefixes) spdlog::debug("Using 'desktop' search path prefix: {}", p);
|
||||
|
||||
return prefixes;
|
||||
}
|
||||
|
||||
static Glib::RefPtr<Gdk::Pixbuf> load_icon_from_file(std::string icon_path, int size) {
|
||||
try {
|
||||
auto pb = Gdk::Pixbuf::create_from_file(icon_path, size, size);
|
||||
return pb;
|
||||
} catch (...) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
static Glib::RefPtr<Gio::DesktopAppInfo> get_app_info_by_name(const std::string &app_id) {
|
||||
static std::vector<std::string> prefixes = search_prefix();
|
||||
|
||||
std::vector<std::string> app_folders = {"", "applications/", "applications/kde/",
|
||||
"applications/org.kde."};
|
||||
|
||||
std::vector<std::string> suffixes = {"", ".desktop"};
|
||||
|
||||
for (auto &prefix : prefixes) {
|
||||
for (auto &folder : app_folders) {
|
||||
for (auto &suffix : suffixes) {
|
||||
auto app_info_ =
|
||||
Gio::DesktopAppInfo::create_from_filename(prefix + folder + app_id + suffix);
|
||||
if (!app_info_) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return app_info_;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
Glib::RefPtr<Gio::DesktopAppInfo> get_desktop_app_info(const std::string &app_id) {
|
||||
auto app_info = get_app_info_by_name(app_id);
|
||||
if (app_info) {
|
||||
return app_info;
|
||||
}
|
||||
|
||||
std::string desktop_file = "";
|
||||
|
||||
gchar ***desktop_list = g_desktop_app_info_search(app_id.c_str());
|
||||
if (desktop_list != nullptr && desktop_list[0] != nullptr) {
|
||||
for (size_t i = 0; desktop_list[0][i]; i++) {
|
||||
if (desktop_file == "") {
|
||||
desktop_file = desktop_list[0][i];
|
||||
} else {
|
||||
auto tmp_info = Gio::DesktopAppInfo::create(desktop_list[0][i]);
|
||||
if (!tmp_info)
|
||||
// see https://github.com/Alexays/Waybar/issues/1446
|
||||
continue;
|
||||
|
||||
auto startup_class = tmp_info->get_startup_wm_class();
|
||||
if (startup_class == app_id) {
|
||||
desktop_file = desktop_list[0][i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
g_strfreev(desktop_list[0]);
|
||||
}
|
||||
g_free(desktop_list);
|
||||
|
||||
return get_app_info_by_name(desktop_file);
|
||||
}
|
||||
|
||||
void Task::set_app_info_from_app_id_list(const std::string &app_id_list) {
|
||||
std::string app_id;
|
||||
std::istringstream stream(app_id_list);
|
||||
|
||||
/* Wayfire sends a list of app-id's in space separated format, other compositors
|
||||
* send a single app-id, but in any case this works fine */
|
||||
while (stream >> app_id) {
|
||||
app_info_ = get_desktop_app_info(app_id);
|
||||
if (app_info_) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lower_app_id = app_id;
|
||||
std::transform(lower_app_id.begin(), lower_app_id.end(), lower_app_id.begin(),
|
||||
[](char c) { return std::tolower(c); });
|
||||
app_info_ = get_desktop_app_info(lower_app_id);
|
||||
if (app_info_) {
|
||||
return;
|
||||
}
|
||||
|
||||
size_t start = 0, end = app_id.size();
|
||||
start = app_id.rfind(".", end);
|
||||
std::string app_name = app_id.substr(start + 1, app_id.size());
|
||||
app_info_ = get_desktop_app_info(app_name);
|
||||
if (app_info_) {
|
||||
return;
|
||||
}
|
||||
|
||||
start = app_id.find("-");
|
||||
app_name = app_id.substr(0, start);
|
||||
app_info_ = get_desktop_app_info(app_name);
|
||||
}
|
||||
}
|
||||
|
||||
static std::string get_icon_name_from_icon_theme(const Glib::RefPtr<Gtk::IconTheme> &icon_theme,
|
||||
const std::string &app_id) {
|
||||
if (icon_theme->lookup_icon(app_id, 24)) return app_id;
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
bool Task::image_load_icon(Gtk::Image &image, const Glib::RefPtr<Gtk::IconTheme> &icon_theme,
|
||||
Glib::RefPtr<Gio::DesktopAppInfo> app_info, int size) {
|
||||
std::string ret_icon_name = "unknown";
|
||||
if (app_info) {
|
||||
std::string icon_name =
|
||||
get_icon_name_from_icon_theme(icon_theme, app_info->get_startup_wm_class());
|
||||
if (!icon_name.empty()) {
|
||||
ret_icon_name = icon_name;
|
||||
} else {
|
||||
if (app_info->get_icon()) {
|
||||
ret_icon_name = app_info->get_icon()->to_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Glib::RefPtr<Gdk::Pixbuf> pixbuf;
|
||||
auto scaled_icon_size = size * image.get_scale_factor();
|
||||
|
||||
try {
|
||||
pixbuf = icon_theme->load_icon(ret_icon_name, scaled_icon_size, Gtk::ICON_LOOKUP_FORCE_SIZE);
|
||||
spdlog::debug("{} Loaded icon '{}'", repr(), ret_icon_name);
|
||||
} catch (...) {
|
||||
if (Glib::file_test(ret_icon_name, Glib::FILE_TEST_EXISTS)) {
|
||||
pixbuf = load_icon_from_file(ret_icon_name, scaled_icon_size);
|
||||
spdlog::debug("{} Loaded icon from file '{}'", repr(), ret_icon_name);
|
||||
} else {
|
||||
try {
|
||||
pixbuf = DefaultGtkIconThemeWrapper::load_icon(
|
||||
"image-missing", scaled_icon_size, Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE);
|
||||
spdlog::debug("{} Loaded icon from resource", repr());
|
||||
} catch (...) {
|
||||
pixbuf = {};
|
||||
spdlog::debug("{} Unable to load icon.", repr());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pixbuf) {
|
||||
if (pixbuf->get_width() != scaled_icon_size) {
|
||||
int width = scaled_icon_size * pixbuf->get_width() / pixbuf->get_height();
|
||||
pixbuf = pixbuf->scale_simple(width, scaled_icon_size, Gdk::InterpType::INTERP_BILINEAR);
|
||||
}
|
||||
auto surface = Gdk::Cairo::create_surface_from_pixbuf(pixbuf, image.get_scale_factor(),
|
||||
image.get_window());
|
||||
image.set(surface);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Task class implementation */
|
||||
uint32_t Task::global_id = 0;
|
||||
|
||||
@@ -299,16 +111,11 @@ Task::Task(const waybar::Bar &bar, const Json::Value &config, Taskbar *tbar,
|
||||
with_name_ = true;
|
||||
}
|
||||
|
||||
auto icon_pos = format.find("{icon}");
|
||||
if (icon_pos == 0) {
|
||||
auto parts = split(format, "{icon}", 1);
|
||||
format_before_ = parts[0];
|
||||
if (parts.size() > 1) {
|
||||
with_icon_ = true;
|
||||
format_after_ = format.substr(6);
|
||||
} else if (icon_pos == std::string::npos) {
|
||||
format_before_ = format;
|
||||
} else {
|
||||
with_icon_ = true;
|
||||
format_before_ = format.substr(0, icon_pos);
|
||||
format_after_ = format.substr(icon_pos + 6);
|
||||
format_after_ = parts[1];
|
||||
}
|
||||
} else {
|
||||
/* The default is to only show the icon */
|
||||
@@ -395,7 +202,7 @@ void Task::handle_title(const char *title) {
|
||||
return;
|
||||
}
|
||||
|
||||
set_app_info_from_app_id_list(title_);
|
||||
app_info_ = IconLoader::get_app_info_from_app_id_list(title_);
|
||||
name_ = app_info_ ? app_info_->get_display_name() : title;
|
||||
|
||||
if (!with_icon_) {
|
||||
@@ -403,15 +210,7 @@ void Task::handle_title(const char *title) {
|
||||
}
|
||||
|
||||
int icon_size = config_["icon-size"].isInt() ? config_["icon-size"].asInt() : 16;
|
||||
bool found = false;
|
||||
for (auto &icon_theme : tbar_->icon_themes()) {
|
||||
if (image_load_icon(icon_, icon_theme, app_info_, icon_size)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (found)
|
||||
if (tbar_->icon_loader().image_load_icon(icon_, app_info_, icon_size))
|
||||
icon_.show();
|
||||
else
|
||||
spdlog::debug("Couldn't find icon for {}", title_);
|
||||
@@ -460,7 +259,7 @@ void Task::handle_app_id(const char *app_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
set_app_info_from_app_id_list(app_id_);
|
||||
app_info_ = IconLoader::get_app_info_from_app_id_list(app_id_);
|
||||
name_ = app_info_ ? app_info_->get_display_name() : app_id;
|
||||
|
||||
if (!with_icon_) {
|
||||
@@ -468,15 +267,7 @@ void Task::handle_app_id(const char *app_id) {
|
||||
}
|
||||
|
||||
int icon_size = config_["icon-size"].isInt() ? config_["icon-size"].asInt() : 16;
|
||||
bool found = false;
|
||||
for (auto &icon_theme : tbar_->icon_themes()) {
|
||||
if (image_load_icon(icon_, icon_theme, app_info_, icon_size)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (found)
|
||||
if (tbar_->icon_loader().image_load_icon(icon_, app_info_, icon_size))
|
||||
icon_.show();
|
||||
else
|
||||
spdlog::debug("Couldn't find icon for {}", app_id_);
|
||||
@@ -802,22 +593,10 @@ Taskbar::Taskbar(const std::string &id, const waybar::Bar &bar, const Json::Valu
|
||||
/* Get the configured icon theme if specified */
|
||||
if (config_["icon-theme"].isArray()) {
|
||||
for (auto &c : config_["icon-theme"]) {
|
||||
auto it_name = c.asString();
|
||||
|
||||
auto it = Gtk::IconTheme::create();
|
||||
it->set_custom_theme(it_name);
|
||||
spdlog::debug("Use custom icon theme: {}", it_name);
|
||||
|
||||
icon_themes_.push_back(it);
|
||||
icon_loader_.add_custom_icon_theme(c.asString());
|
||||
}
|
||||
} else if (config_["icon-theme"].isString()) {
|
||||
auto it_name = config_["icon-theme"].asString();
|
||||
|
||||
auto it = Gtk::IconTheme::create();
|
||||
it->set_custom_theme(it_name);
|
||||
spdlog::debug("Use custom icon theme: {}", it_name);
|
||||
|
||||
icon_themes_.push_back(it);
|
||||
icon_loader_.add_custom_icon_theme(config_["icon-theme"].asString());
|
||||
}
|
||||
|
||||
// Load ignore-list
|
||||
@@ -836,8 +615,6 @@ Taskbar::Taskbar(const std::string &id, const waybar::Bar &bar, const Json::Valu
|
||||
}
|
||||
}
|
||||
|
||||
icon_themes_.push_back(Gtk::IconTheme::get_default());
|
||||
|
||||
for (auto &t : tasks_) {
|
||||
t->handle_app_id(t->app_id().c_str());
|
||||
}
|
||||
@@ -972,9 +749,7 @@ bool Taskbar::all_outputs() const {
|
||||
return config_["all-outputs"].isBool() && config_["all-outputs"].asBool();
|
||||
}
|
||||
|
||||
const std::vector<Glib::RefPtr<Gtk::IconTheme>> &Taskbar::icon_themes() const {
|
||||
return icon_themes_;
|
||||
}
|
||||
const IconLoader &Taskbar::icon_loader() const { return icon_loader_; }
|
||||
|
||||
const std::unordered_set<std::string> &Taskbar::ignore_list() const { return ignore_list_; }
|
||||
|
||||
|
||||
@@ -1,585 +0,0 @@
|
||||
#include "modules/wlr/workspace_manager.hpp"
|
||||
|
||||
#include <gdk/gdkwayland.h>
|
||||
#include <gtkmm.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
#include "client.hpp"
|
||||
#include "gtkmm/widget.h"
|
||||
#include "modules/wlr/workspace_manager_binding.hpp"
|
||||
|
||||
namespace waybar::modules::wlr {
|
||||
|
||||
uint32_t WorkspaceGroup::workspace_global_id = 0;
|
||||
uint32_t WorkspaceManager::group_global_id = 0;
|
||||
std::map<std::string, std::string> Workspace::icons_map_;
|
||||
|
||||
WorkspaceManager::WorkspaceManager(const std::string &id, const waybar::Bar &bar,
|
||||
const Json::Value &config)
|
||||
: waybar::AModule(config, "workspaces", id, false, false), bar_(bar), box_(bar.orientation, 0) {
|
||||
auto config_sort_by_name = config_["sort-by-name"];
|
||||
if (config_sort_by_name.isBool()) {
|
||||
sort_by_name_ = config_sort_by_name.asBool();
|
||||
}
|
||||
|
||||
auto config_sort_by_coordinates = config_["sort-by-coordinates"];
|
||||
if (config_sort_by_coordinates.isBool()) {
|
||||
sort_by_coordinates_ = config_sort_by_coordinates.asBool();
|
||||
}
|
||||
|
||||
auto config_sort_by_number = config_["sort-by-number"];
|
||||
if (config_sort_by_number.isBool()) {
|
||||
sort_by_number_ = config_sort_by_number.asBool();
|
||||
}
|
||||
|
||||
auto config_all_outputs = config_["all-outputs"];
|
||||
if (config_all_outputs.isBool()) {
|
||||
all_outputs_ = config_all_outputs.asBool();
|
||||
}
|
||||
|
||||
auto config_active_only = config_["active-only"];
|
||||
if (config_active_only.isBool()) {
|
||||
active_only_ = config_active_only.asBool();
|
||||
creation_delayed_ = active_only_;
|
||||
}
|
||||
|
||||
box_.set_name("workspaces");
|
||||
if (!id.empty()) {
|
||||
box_.get_style_context()->add_class(id);
|
||||
}
|
||||
box_.get_style_context()->add_class(MODULE_CLASS);
|
||||
event_box_.add(box_);
|
||||
|
||||
add_registry_listener(this);
|
||||
if (!workspace_manager_) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto WorkspaceManager::workspace_comparator() const
|
||||
-> std::function<bool(std::unique_ptr<Workspace> &, std::unique_ptr<Workspace> &)> {
|
||||
return [=, this](std::unique_ptr<Workspace> &lhs, std::unique_ptr<Workspace> &rhs) {
|
||||
auto is_name_less = lhs->get_name() < rhs->get_name();
|
||||
auto is_name_eq = lhs->get_name() == rhs->get_name();
|
||||
auto is_coords_less = lhs->get_coords() < rhs->get_coords();
|
||||
|
||||
if (sort_by_number_) {
|
||||
try {
|
||||
auto is_number_less = std::stoi(lhs->get_name()) < std::stoi(rhs->get_name());
|
||||
return is_number_less;
|
||||
} catch (const std::invalid_argument &) {
|
||||
}
|
||||
}
|
||||
|
||||
if (sort_by_name_) {
|
||||
if (sort_by_coordinates_) {
|
||||
return is_name_eq ? is_coords_less : is_name_less;
|
||||
} else {
|
||||
return is_name_less;
|
||||
}
|
||||
}
|
||||
|
||||
if (sort_by_coordinates_) {
|
||||
return is_coords_less;
|
||||
}
|
||||
|
||||
return lhs->id() < rhs->id();
|
||||
};
|
||||
}
|
||||
|
||||
auto WorkspaceManager::sort_workspaces() -> void {
|
||||
std::vector<std::reference_wrapper<std::unique_ptr<Workspace>>> all_workspaces;
|
||||
for (auto &group : groups_) {
|
||||
auto &group_workspaces = group->workspaces();
|
||||
all_workspaces.reserve(all_workspaces.size() +
|
||||
std::distance(group_workspaces.begin(), group_workspaces.end()));
|
||||
if (!active_only()) {
|
||||
all_workspaces.insert(all_workspaces.end(), group_workspaces.begin(), group_workspaces.end());
|
||||
continue;
|
||||
}
|
||||
|
||||
for (auto &workspace : group_workspaces) {
|
||||
if (!workspace->is_active()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
all_workspaces.push_back(workspace);
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(all_workspaces.begin(), all_workspaces.end(), workspace_comparator());
|
||||
for (size_t i = 0; i < all_workspaces.size(); ++i) {
|
||||
box_.reorder_child(all_workspaces[i].get()->get_button_ref(), i);
|
||||
}
|
||||
}
|
||||
|
||||
auto WorkspaceManager::register_manager(wl_registry *registry, uint32_t name, uint32_t version)
|
||||
-> void {
|
||||
if (workspace_manager_) {
|
||||
spdlog::warn("Register workspace manager again although already registered!");
|
||||
return;
|
||||
}
|
||||
if (version != 1) {
|
||||
spdlog::warn("Using different workspace manager protocol version: {}", version);
|
||||
}
|
||||
workspace_manager_ = workspace_manager_bind(registry, name, version, this);
|
||||
}
|
||||
|
||||
auto WorkspaceManager::handle_workspace_group_create(
|
||||
zext_workspace_group_handle_v1 *workspace_group_handle) -> void {
|
||||
auto new_id = ++group_global_id;
|
||||
groups_.push_back(
|
||||
std::make_unique<WorkspaceGroup>(bar_, box_, config_, *this, workspace_group_handle, new_id));
|
||||
spdlog::debug("Workspace group {} created", new_id);
|
||||
}
|
||||
|
||||
auto WorkspaceManager::handle_finished() -> void {
|
||||
zext_workspace_manager_v1_destroy(workspace_manager_);
|
||||
workspace_manager_ = nullptr;
|
||||
}
|
||||
|
||||
auto WorkspaceManager::handle_done() -> void {
|
||||
for (auto &group : groups_) {
|
||||
group->handle_done();
|
||||
}
|
||||
dp.emit();
|
||||
}
|
||||
|
||||
auto WorkspaceManager::update() -> void {
|
||||
for (auto &group : groups_) {
|
||||
group->update();
|
||||
}
|
||||
if (creation_delayed()) {
|
||||
creation_delayed_ = false;
|
||||
sort_workspaces();
|
||||
}
|
||||
AModule::update();
|
||||
}
|
||||
|
||||
WorkspaceManager::~WorkspaceManager() {
|
||||
if (!workspace_manager_) {
|
||||
return;
|
||||
}
|
||||
|
||||
wl_display *display = Client::inst()->wl_display;
|
||||
|
||||
// Send `stop` request and wait for one roundtrip. This is not quite correct as
|
||||
// the protocol encourages us to wait for the .finished event, but it should work
|
||||
// with wlroots workspace manager implementation.
|
||||
zext_workspace_manager_v1_stop(workspace_manager_);
|
||||
wl_display_roundtrip(display);
|
||||
|
||||
// If the .finished handler is still not executed, destroy the workspace manager here.
|
||||
if (workspace_manager_) {
|
||||
spdlog::warn("Foreign toplevel manager destroyed before .finished event");
|
||||
zext_workspace_manager_v1_destroy(workspace_manager_);
|
||||
workspace_manager_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
auto WorkspaceManager::remove_workspace_group(uint32_t id) -> void {
|
||||
auto it = std::find_if(groups_.begin(), groups_.end(),
|
||||
[id](const std::unique_ptr<WorkspaceGroup> &g) { return g->id() == id; });
|
||||
|
||||
if (it == groups_.end()) {
|
||||
spdlog::warn("Can't find group with id {}", id);
|
||||
return;
|
||||
}
|
||||
|
||||
groups_.erase(it);
|
||||
}
|
||||
auto WorkspaceManager::commit() -> void { zext_workspace_manager_v1_commit(workspace_manager_); }
|
||||
|
||||
WorkspaceGroup::WorkspaceGroup(const Bar &bar, Gtk::Box &box, const Json::Value &config,
|
||||
WorkspaceManager &manager,
|
||||
zext_workspace_group_handle_v1 *workspace_group_handle, uint32_t id)
|
||||
: bar_(bar),
|
||||
box_(box),
|
||||
config_(config),
|
||||
workspace_manager_(manager),
|
||||
workspace_group_handle_(workspace_group_handle),
|
||||
id_(id) {
|
||||
add_workspace_group_listener(workspace_group_handle, this);
|
||||
}
|
||||
|
||||
auto WorkspaceGroup::fill_persistent_workspaces() -> void {
|
||||
if (config_["persistent_workspaces"].isObject()) {
|
||||
spdlog::warn(
|
||||
"persistent_workspaces is deprecated. Please change config to use persistent-workspaces.");
|
||||
}
|
||||
|
||||
if ((config_["persistent-workspaces"].isObject() ||
|
||||
config_["persistent_workspaces"].isObject()) &&
|
||||
!workspace_manager_.all_outputs()) {
|
||||
const Json::Value &p_workspaces = config_["persistent-workspaces"].isObject()
|
||||
? config_["persistent-workspaces"]
|
||||
: config_["persistent_workspaces"];
|
||||
const std::vector<std::string> p_workspaces_names = p_workspaces.getMemberNames();
|
||||
|
||||
for (const std::string &p_w_name : p_workspaces_names) {
|
||||
const Json::Value &p_w = p_workspaces[p_w_name];
|
||||
if (p_w.isArray() && !p_w.empty()) {
|
||||
// Adding to target outputs
|
||||
for (const Json::Value &output : p_w) {
|
||||
if (output.asString() == bar_.output->name) {
|
||||
persistent_workspaces_.push_back(p_w_name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Adding to all outputs
|
||||
persistent_workspaces_.push_back(p_w_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto WorkspaceGroup::create_persistent_workspaces() -> void {
|
||||
for (const std::string &p_w_name : persistent_workspaces_) {
|
||||
auto new_id = ++workspace_global_id;
|
||||
workspaces_.push_back(
|
||||
std::make_unique<Workspace>(bar_, config_, *this, nullptr, new_id, p_w_name));
|
||||
spdlog::debug("Workspace {} created", new_id);
|
||||
}
|
||||
}
|
||||
|
||||
auto WorkspaceGroup::active_only() const -> bool { return workspace_manager_.active_only(); }
|
||||
auto WorkspaceGroup::creation_delayed() const -> bool {
|
||||
return workspace_manager_.creation_delayed();
|
||||
}
|
||||
|
||||
auto WorkspaceGroup::add_button(Gtk::Button &button) -> void {
|
||||
box_.pack_start(button, false, false);
|
||||
}
|
||||
|
||||
WorkspaceGroup::~WorkspaceGroup() {
|
||||
if (!workspace_group_handle_) {
|
||||
return;
|
||||
}
|
||||
|
||||
zext_workspace_group_handle_v1_destroy(workspace_group_handle_);
|
||||
workspace_group_handle_ = nullptr;
|
||||
}
|
||||
|
||||
auto WorkspaceGroup::handle_workspace_create(zext_workspace_handle_v1 *workspace) -> void {
|
||||
auto new_id = ++workspace_global_id;
|
||||
workspaces_.push_back(std::make_unique<Workspace>(bar_, config_, *this, workspace, new_id, ""));
|
||||
spdlog::debug("Workspace {} created", new_id);
|
||||
if (!persistent_created_) {
|
||||
fill_persistent_workspaces();
|
||||
create_persistent_workspaces();
|
||||
persistent_created_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
auto WorkspaceGroup::handle_remove() -> void {
|
||||
zext_workspace_group_handle_v1_destroy(workspace_group_handle_);
|
||||
workspace_group_handle_ = nullptr;
|
||||
workspace_manager_.remove_workspace_group(id_);
|
||||
}
|
||||
|
||||
auto WorkspaceGroup::handle_output_enter(wl_output *output) -> void {
|
||||
spdlog::debug("Output {} assigned to {} group", (void *)output, id_);
|
||||
output_ = output;
|
||||
|
||||
if (!is_visible() || workspace_manager_.creation_delayed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto &workspace : workspaces_) {
|
||||
add_button(workspace->get_button_ref());
|
||||
}
|
||||
}
|
||||
|
||||
auto WorkspaceGroup::is_visible() const -> bool {
|
||||
return output_ != nullptr &&
|
||||
(workspace_manager_.all_outputs() ||
|
||||
output_ == gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()));
|
||||
}
|
||||
|
||||
auto WorkspaceGroup::handle_output_leave() -> void {
|
||||
spdlog::debug("Output {} remove from {} group", (void *)output_, id_);
|
||||
output_ = nullptr;
|
||||
|
||||
if (output_ != gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj())) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto &workspace : workspaces_) {
|
||||
remove_button(workspace->get_button_ref());
|
||||
}
|
||||
}
|
||||
|
||||
auto WorkspaceGroup::update() -> void {
|
||||
for (auto &workspace : workspaces_) {
|
||||
if (workspace_manager_.creation_delayed()) {
|
||||
add_button(workspace->get_button_ref());
|
||||
if (is_visible() && (workspace->is_active() || workspace->is_urgent())) {
|
||||
workspace->show();
|
||||
}
|
||||
}
|
||||
|
||||
workspace->update();
|
||||
}
|
||||
}
|
||||
|
||||
auto WorkspaceGroup::remove_workspace(uint32_t id) -> void {
|
||||
auto it = std::find_if(workspaces_.begin(), workspaces_.end(),
|
||||
[id](const std::unique_ptr<Workspace> &w) { return w->id() == id; });
|
||||
|
||||
if (it == workspaces_.end()) {
|
||||
spdlog::warn("Can't find workspace with id {}", id);
|
||||
return;
|
||||
}
|
||||
|
||||
workspaces_.erase(it);
|
||||
}
|
||||
|
||||
auto WorkspaceGroup::handle_done() -> void {
|
||||
need_to_sort = false;
|
||||
if (!is_visible()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto &workspace : workspaces_) {
|
||||
workspace->handle_done();
|
||||
}
|
||||
|
||||
if (creation_delayed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!workspace_manager_.all_outputs()) {
|
||||
sort_workspaces();
|
||||
} else {
|
||||
workspace_manager_.sort_workspaces();
|
||||
}
|
||||
}
|
||||
|
||||
auto WorkspaceGroup::commit() -> void { workspace_manager_.commit(); }
|
||||
|
||||
auto WorkspaceGroup::sort_workspaces() -> void {
|
||||
std::sort(workspaces_.begin(), workspaces_.end(), workspace_manager_.workspace_comparator());
|
||||
for (size_t i = 0; i < workspaces_.size(); ++i) {
|
||||
box_.reorder_child(workspaces_[i]->get_button_ref(), i);
|
||||
}
|
||||
}
|
||||
|
||||
auto WorkspaceGroup::remove_button(Gtk::Button &button) -> void { box_.remove(button); }
|
||||
|
||||
Workspace::Workspace(const Bar &bar, const Json::Value &config, WorkspaceGroup &workspace_group,
|
||||
zext_workspace_handle_v1 *workspace, uint32_t id, std::string name)
|
||||
: bar_(bar),
|
||||
config_(config),
|
||||
workspace_group_(workspace_group),
|
||||
workspace_handle_(workspace),
|
||||
id_(id),
|
||||
name_(name) {
|
||||
if (workspace) {
|
||||
add_workspace_listener(workspace, this);
|
||||
} else {
|
||||
state_ = (uint32_t)State::EMPTY;
|
||||
}
|
||||
|
||||
auto config_format = config["format"];
|
||||
|
||||
format_ = config_format.isString() ? config_format.asString() : "{name}";
|
||||
with_icon_ = format_.find("{icon}") != std::string::npos;
|
||||
|
||||
if (with_icon_ && icons_map_.empty()) {
|
||||
auto format_icons = config["format-icons"];
|
||||
for (auto &name : format_icons.getMemberNames()) {
|
||||
icons_map_.emplace(name, format_icons[name].asString());
|
||||
}
|
||||
}
|
||||
|
||||
/* Handle click events if configured */
|
||||
if (config_["on-click"].isString() || config_["on-click-middle"].isString() ||
|
||||
config_["on-click-right"].isString()) {
|
||||
button_.add_events(Gdk::BUTTON_PRESS_MASK);
|
||||
button_.signal_button_press_event().connect(sigc::mem_fun(*this, &Workspace::handle_clicked),
|
||||
false);
|
||||
}
|
||||
|
||||
button_.set_relief(Gtk::RELIEF_NONE);
|
||||
content_.set_center_widget(label_);
|
||||
button_.add(content_);
|
||||
|
||||
if (!workspace_group.is_visible()) {
|
||||
return;
|
||||
}
|
||||
|
||||
workspace_group.add_button(button_);
|
||||
button_.show_all();
|
||||
}
|
||||
|
||||
Workspace::~Workspace() {
|
||||
workspace_group_.remove_button(button_);
|
||||
if (!workspace_handle_) {
|
||||
return;
|
||||
}
|
||||
|
||||
zext_workspace_handle_v1_destroy(workspace_handle_);
|
||||
workspace_handle_ = nullptr;
|
||||
}
|
||||
|
||||
auto Workspace::update() -> void {
|
||||
label_.set_markup(fmt::format(fmt::runtime(format_), fmt::arg("name", name_),
|
||||
fmt::arg("icon", with_icon_ ? get_icon() : "")));
|
||||
}
|
||||
|
||||
auto Workspace::handle_state(const std::vector<uint32_t> &state) -> void {
|
||||
state_ = 0;
|
||||
for (auto state_entry : state) {
|
||||
switch (state_entry) {
|
||||
case ZEXT_WORKSPACE_HANDLE_V1_STATE_ACTIVE:
|
||||
state_ |= (uint32_t)State::ACTIVE;
|
||||
break;
|
||||
case ZEXT_WORKSPACE_HANDLE_V1_STATE_URGENT:
|
||||
state_ |= (uint32_t)State::URGENT;
|
||||
break;
|
||||
case ZEXT_WORKSPACE_HANDLE_V1_STATE_HIDDEN:
|
||||
state_ |= (uint32_t)State::HIDDEN;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto Workspace::handle_remove() -> void {
|
||||
if (workspace_handle_) {
|
||||
zext_workspace_handle_v1_destroy(workspace_handle_);
|
||||
workspace_handle_ = nullptr;
|
||||
}
|
||||
if (!persistent_) {
|
||||
workspace_group_.remove_workspace(id_);
|
||||
} else {
|
||||
state_ = (uint32_t)State::EMPTY;
|
||||
}
|
||||
}
|
||||
|
||||
auto add_or_remove_class(Glib::RefPtr<Gtk::StyleContext> context, bool condition,
|
||||
const std::string &class_name) {
|
||||
if (condition) {
|
||||
context->add_class(class_name);
|
||||
} else {
|
||||
context->remove_class(class_name);
|
||||
}
|
||||
}
|
||||
|
||||
auto Workspace::handle_done() -> void {
|
||||
spdlog::debug("Workspace {} changed to state {}", id_, state_);
|
||||
auto style_context = button_.get_style_context();
|
||||
add_or_remove_class(style_context, is_active(), "active");
|
||||
add_or_remove_class(style_context, is_urgent(), "urgent");
|
||||
add_or_remove_class(style_context, is_hidden(), "hidden");
|
||||
add_or_remove_class(style_context, is_empty(), "persistent");
|
||||
|
||||
if (workspace_group_.creation_delayed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (workspace_group_.active_only() && (is_active() || is_urgent())) {
|
||||
button_.show_all();
|
||||
} else if (workspace_group_.active_only() && !(is_active() || is_urgent())) {
|
||||
button_.hide();
|
||||
}
|
||||
}
|
||||
|
||||
auto Workspace::get_icon() -> std::string {
|
||||
if (is_active()) {
|
||||
auto active_icon_it = icons_map_.find("active");
|
||||
if (active_icon_it != icons_map_.end()) {
|
||||
return active_icon_it->second;
|
||||
}
|
||||
}
|
||||
|
||||
auto named_icon_it = icons_map_.find(name_);
|
||||
if (named_icon_it != icons_map_.end()) {
|
||||
return named_icon_it->second;
|
||||
}
|
||||
|
||||
if (is_empty()) {
|
||||
auto persistent_icon_it = icons_map_.find("persistent");
|
||||
if (persistent_icon_it != icons_map_.end()) {
|
||||
return persistent_icon_it->second;
|
||||
}
|
||||
}
|
||||
|
||||
auto default_icon_it = icons_map_.find("default");
|
||||
if (default_icon_it != icons_map_.end()) {
|
||||
return default_icon_it->second;
|
||||
}
|
||||
|
||||
return name_;
|
||||
}
|
||||
|
||||
auto Workspace::handle_clicked(GdkEventButton *bt) -> bool {
|
||||
std::string action;
|
||||
if (config_["on-click"].isString() && bt->button == 1) {
|
||||
action = config_["on-click"].asString();
|
||||
} else if (config_["on-click-middle"].isString() && bt->button == 2) {
|
||||
action = config_["on-click-middle"].asString();
|
||||
} else if (config_["on-click-right"].isString() && bt->button == 3) {
|
||||
action = config_["on-click-right"].asString();
|
||||
}
|
||||
|
||||
if (action.empty())
|
||||
return true;
|
||||
else if (action == "activate") {
|
||||
zext_workspace_handle_v1_activate(workspace_handle_);
|
||||
} else if (action == "close") {
|
||||
zext_workspace_handle_v1_remove(workspace_handle_);
|
||||
} else {
|
||||
spdlog::warn("Unknown action {}", action);
|
||||
}
|
||||
|
||||
workspace_group_.commit();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto Workspace::show() -> void { button_.show_all(); }
|
||||
auto Workspace::hide() -> void { button_.hide(); }
|
||||
|
||||
auto Workspace::handle_name(const std::string &name) -> void {
|
||||
if (name_ != name) {
|
||||
workspace_group_.set_need_to_sort();
|
||||
}
|
||||
name_ = name;
|
||||
spdlog::debug("Workspace {} added to group {}", name, workspace_group_.id());
|
||||
|
||||
make_persistent();
|
||||
handle_duplicate();
|
||||
}
|
||||
|
||||
auto Workspace::make_persistent() -> void {
|
||||
auto p_workspaces = workspace_group_.persistent_workspaces();
|
||||
|
||||
if (std::find(p_workspaces.begin(), p_workspaces.end(), name_) != p_workspaces.end()) {
|
||||
persistent_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
auto Workspace::handle_duplicate() -> void {
|
||||
auto duplicate =
|
||||
std::find_if(workspace_group_.workspaces().begin(), workspace_group_.workspaces().end(),
|
||||
[this](const std::unique_ptr<Workspace> &g) {
|
||||
return g->get_name() == name_ && g->id() != id_;
|
||||
});
|
||||
if (duplicate != workspace_group_.workspaces().end()) {
|
||||
workspace_group_.remove_workspace(duplicate->get()->id());
|
||||
}
|
||||
}
|
||||
|
||||
auto Workspace::handle_coordinates(const std::vector<uint32_t> &coordinates) -> void {
|
||||
if (coordinates_ != coordinates) {
|
||||
workspace_group_.set_need_to_sort();
|
||||
}
|
||||
coordinates_ = coordinates;
|
||||
}
|
||||
} // namespace waybar::modules::wlr
|
||||
@@ -1,136 +0,0 @@
|
||||
#include "modules/wlr/workspace_manager_binding.hpp"
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "client.hpp"
|
||||
#include "modules/wlr/workspace_manager.hpp"
|
||||
|
||||
namespace waybar::modules::wlr {
|
||||
|
||||
static void handle_global(void *data, wl_registry *registry, uint32_t name, const char *interface,
|
||||
uint32_t version) {
|
||||
if (std::strcmp(interface, zext_workspace_manager_v1_interface.name) == 0) {
|
||||
static_cast<WorkspaceManager *>(data)->register_manager(registry, name, version);
|
||||
}
|
||||
}
|
||||
|
||||
static void handle_global_remove(void *data, wl_registry *registry, uint32_t name) {
|
||||
/* Nothing to do here */
|
||||
}
|
||||
|
||||
static const wl_registry_listener registry_listener_impl = {.global = handle_global,
|
||||
.global_remove = handle_global_remove};
|
||||
|
||||
void add_registry_listener(void *data) {
|
||||
wl_display *display = Client::inst()->wl_display;
|
||||
wl_registry *registry = wl_display_get_registry(display);
|
||||
|
||||
wl_registry_add_listener(registry, ®istry_listener_impl, data);
|
||||
wl_display_roundtrip(display);
|
||||
wl_display_roundtrip(display);
|
||||
}
|
||||
|
||||
static void workspace_manager_handle_workspace_group(
|
||||
void *data, zext_workspace_manager_v1 *_, zext_workspace_group_handle_v1 *workspace_group) {
|
||||
static_cast<WorkspaceManager *>(data)->handle_workspace_group_create(workspace_group);
|
||||
}
|
||||
|
||||
static void workspace_manager_handle_done(void *data, zext_workspace_manager_v1 *_) {
|
||||
static_cast<WorkspaceManager *>(data)->handle_done();
|
||||
}
|
||||
|
||||
static void workspace_manager_handle_finished(void *data, zext_workspace_manager_v1 *_) {
|
||||
static_cast<WorkspaceManager *>(data)->handle_finished();
|
||||
}
|
||||
|
||||
static const zext_workspace_manager_v1_listener workspace_manager_impl = {
|
||||
.workspace_group = workspace_manager_handle_workspace_group,
|
||||
.done = workspace_manager_handle_done,
|
||||
.finished = workspace_manager_handle_finished,
|
||||
};
|
||||
|
||||
zext_workspace_manager_v1 *workspace_manager_bind(wl_registry *registry, uint32_t name,
|
||||
uint32_t version, void *data) {
|
||||
auto *workspace_manager = static_cast<zext_workspace_manager_v1 *>(
|
||||
wl_registry_bind(registry, name, &zext_workspace_manager_v1_interface, version));
|
||||
|
||||
if (workspace_manager)
|
||||
zext_workspace_manager_v1_add_listener(workspace_manager, &workspace_manager_impl, data);
|
||||
else
|
||||
spdlog::error("Failed to register manager");
|
||||
|
||||
return workspace_manager;
|
||||
}
|
||||
|
||||
static void workspace_group_handle_output_enter(void *data, zext_workspace_group_handle_v1 *_,
|
||||
wl_output *output) {
|
||||
static_cast<WorkspaceGroup *>(data)->handle_output_enter(output);
|
||||
}
|
||||
|
||||
static void workspace_group_handle_output_leave(void *data, zext_workspace_group_handle_v1 *_,
|
||||
wl_output *output) {
|
||||
static_cast<WorkspaceGroup *>(data)->handle_output_leave();
|
||||
}
|
||||
|
||||
static void workspace_group_handle_workspace(void *data, zext_workspace_group_handle_v1 *_,
|
||||
zext_workspace_handle_v1 *workspace) {
|
||||
static_cast<WorkspaceGroup *>(data)->handle_workspace_create(workspace);
|
||||
}
|
||||
|
||||
static void workspace_group_handle_remove(void *data, zext_workspace_group_handle_v1 *_) {
|
||||
static_cast<WorkspaceGroup *>(data)->handle_remove();
|
||||
}
|
||||
|
||||
static const zext_workspace_group_handle_v1_listener workspace_group_impl = {
|
||||
.output_enter = workspace_group_handle_output_enter,
|
||||
.output_leave = workspace_group_handle_output_leave,
|
||||
.workspace = workspace_group_handle_workspace,
|
||||
.remove = workspace_group_handle_remove};
|
||||
|
||||
void add_workspace_group_listener(zext_workspace_group_handle_v1 *workspace_group_handle,
|
||||
void *data) {
|
||||
zext_workspace_group_handle_v1_add_listener(workspace_group_handle, &workspace_group_impl, data);
|
||||
}
|
||||
|
||||
void workspace_handle_name(void *data, struct zext_workspace_handle_v1 *_, const char *name) {
|
||||
static_cast<Workspace *>(data)->handle_name(name);
|
||||
}
|
||||
|
||||
void workspace_handle_coordinates(void *data, struct zext_workspace_handle_v1 *_,
|
||||
struct wl_array *coordinates) {
|
||||
std::vector<uint32_t> coords_vec;
|
||||
auto coords = static_cast<uint32_t *>(coordinates->data);
|
||||
for (size_t i = 0; i < coordinates->size / sizeof(uint32_t); ++i) {
|
||||
coords_vec.push_back(coords[i]);
|
||||
}
|
||||
|
||||
static_cast<Workspace *>(data)->handle_coordinates(coords_vec);
|
||||
}
|
||||
|
||||
void workspace_handle_state(void *data, struct zext_workspace_handle_v1 *workspace_handle,
|
||||
struct wl_array *state) {
|
||||
std::vector<uint32_t> state_vec;
|
||||
auto states = static_cast<uint32_t *>(state->data);
|
||||
for (size_t i = 0; i < state->size / sizeof(uint32_t); ++i) {
|
||||
state_vec.push_back(states[i]);
|
||||
}
|
||||
|
||||
static_cast<Workspace *>(data)->handle_state(state_vec);
|
||||
}
|
||||
|
||||
void workspace_handle_remove(void *data, struct zext_workspace_handle_v1 *_) {
|
||||
static_cast<Workspace *>(data)->handle_remove();
|
||||
}
|
||||
|
||||
static const zext_workspace_handle_v1_listener workspace_impl = {
|
||||
.name = workspace_handle_name,
|
||||
.coordinates = workspace_handle_coordinates,
|
||||
.state = workspace_handle_state,
|
||||
.remove = workspace_handle_remove};
|
||||
|
||||
void add_workspace_listener(zext_workspace_handle_v1 *workspace_handle, void *data) {
|
||||
zext_workspace_handle_v1_add_listener(workspace_handle, &workspace_impl, data);
|
||||
}
|
||||
} // namespace waybar::modules::wlr
|
||||
@@ -2,7 +2,13 @@
|
||||
|
||||
#include <poll.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
#ifndef __OpenBSD__
|
||||
#include <sys/inotify.h>
|
||||
#else
|
||||
#include <sys/event.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/types.h>
|
||||
#endif
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
|
||||
@@ -41,5 +41,6 @@ EnumType EnumParser<EnumType>::parseStringToEnum(const std::string& str,
|
||||
// Explicit instantiations for specific EnumType types you intend to use
|
||||
// Add explicit instantiations for all relevant EnumType types
|
||||
template struct EnumParser<modules::hyprland::Workspaces::SortMethod>;
|
||||
template struct EnumParser<util::KillSignalAction>;
|
||||
|
||||
} // namespace waybar::util
|
||||
|
||||
@@ -25,6 +25,10 @@ Glib::RefPtr<Gdk::Pixbuf> DefaultGtkIconThemeWrapper::load_icon(
|
||||
|
||||
auto icon_info = default_theme->lookup_icon(name, tmp_size, flags);
|
||||
|
||||
if (icon_info == nullptr) {
|
||||
return default_theme->load_icon(name, tmp_size, flags);
|
||||
}
|
||||
|
||||
if (style.get() == nullptr) {
|
||||
return icon_info.load_icon();
|
||||
}
|
||||
|
||||
207
src/util/icon_loader.cpp
Normal file
207
src/util/icon_loader.cpp
Normal file
@@ -0,0 +1,207 @@
|
||||
#include "util/icon_loader.hpp"
|
||||
|
||||
#include "util/string.hpp"
|
||||
|
||||
std::vector<std::string> IconLoader::search_prefix() {
|
||||
std::vector<std::string> prefixes = {""};
|
||||
|
||||
std::string home_dir = std::getenv("HOME");
|
||||
prefixes.push_back(home_dir + "/.local/share/");
|
||||
|
||||
auto xdg_data_dirs = std::getenv("XDG_DATA_DIRS");
|
||||
if (!xdg_data_dirs) {
|
||||
prefixes.emplace_back("/usr/share/");
|
||||
prefixes.emplace_back("/usr/local/share/");
|
||||
} else {
|
||||
std::string xdg_data_dirs_str(xdg_data_dirs);
|
||||
size_t start = 0;
|
||||
size_t end = 0;
|
||||
|
||||
do {
|
||||
end = xdg_data_dirs_str.find(':', start);
|
||||
auto p = xdg_data_dirs_str.substr(start, end - start);
|
||||
prefixes.push_back(trim(p) + "/");
|
||||
|
||||
start = end == std::string::npos ? end : end + 1;
|
||||
} while (end != std::string::npos);
|
||||
}
|
||||
|
||||
for (auto &p : prefixes) spdlog::debug("Using 'desktop' search path prefix: {}", p);
|
||||
|
||||
return prefixes;
|
||||
}
|
||||
|
||||
Glib::RefPtr<Gio::DesktopAppInfo> IconLoader::get_app_info_by_name(const std::string &app_id) {
|
||||
static std::vector<std::string> prefixes = search_prefix();
|
||||
|
||||
std::vector<std::string> app_folders = {"", "applications/", "applications/kde/",
|
||||
"applications/org.kde."};
|
||||
|
||||
std::vector<std::string> suffixes = {"", ".desktop"};
|
||||
|
||||
for (auto const &prefix : prefixes) {
|
||||
for (auto const &folder : app_folders) {
|
||||
for (auto const &suffix : suffixes) {
|
||||
auto app_info_ =
|
||||
Gio::DesktopAppInfo::create_from_filename(prefix + folder + app_id + suffix);
|
||||
if (!app_info_) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return app_info_;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
Glib::RefPtr<Gio::DesktopAppInfo> IconLoader::get_desktop_app_info(const std::string &app_id) {
|
||||
auto app_info = get_app_info_by_name(app_id);
|
||||
if (app_info) {
|
||||
return app_info;
|
||||
}
|
||||
|
||||
std::string desktop_file = "";
|
||||
|
||||
gchar ***desktop_list = g_desktop_app_info_search(app_id.c_str());
|
||||
if (desktop_list != nullptr && desktop_list[0] != nullptr) {
|
||||
for (size_t i = 0; desktop_list[0][i]; i++) {
|
||||
if (desktop_file == "") {
|
||||
desktop_file = desktop_list[0][i];
|
||||
} else {
|
||||
auto tmp_info = Gio::DesktopAppInfo::create(desktop_list[0][i]);
|
||||
if (!tmp_info)
|
||||
// see https://github.com/Alexays/Waybar/issues/1446
|
||||
continue;
|
||||
|
||||
auto startup_class = tmp_info->get_startup_wm_class();
|
||||
if (startup_class == app_id) {
|
||||
desktop_file = desktop_list[0][i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
g_strfreev(desktop_list[0]);
|
||||
}
|
||||
g_free(desktop_list);
|
||||
|
||||
return get_app_info_by_name(desktop_file);
|
||||
}
|
||||
|
||||
Glib::RefPtr<Gdk::Pixbuf> IconLoader::load_icon_from_file(std::string const &icon_path, int size) {
|
||||
try {
|
||||
auto pb = Gdk::Pixbuf::create_from_file(icon_path, size, size);
|
||||
return pb;
|
||||
} catch (...) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
std::string IconLoader::get_icon_name_from_icon_theme(
|
||||
const Glib::RefPtr<Gtk::IconTheme> &icon_theme, const std::string &app_id) {
|
||||
if (icon_theme->lookup_icon(app_id, 24)) return app_id;
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
bool IconLoader::image_load_icon(Gtk::Image &image, const Glib::RefPtr<Gtk::IconTheme> &icon_theme,
|
||||
Glib::RefPtr<Gio::DesktopAppInfo> app_info, int size) {
|
||||
std::string ret_icon_name = "unknown";
|
||||
if (app_info) {
|
||||
std::string icon_name =
|
||||
get_icon_name_from_icon_theme(icon_theme, app_info->get_startup_wm_class());
|
||||
if (!icon_name.empty()) {
|
||||
ret_icon_name = icon_name;
|
||||
} else {
|
||||
if (app_info->get_icon()) {
|
||||
ret_icon_name = app_info->get_icon()->to_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Glib::RefPtr<Gdk::Pixbuf> pixbuf;
|
||||
auto scaled_icon_size = size * image.get_scale_factor();
|
||||
|
||||
try {
|
||||
pixbuf = icon_theme->load_icon(ret_icon_name, scaled_icon_size, Gtk::ICON_LOOKUP_FORCE_SIZE);
|
||||
} catch (...) {
|
||||
if (Glib::file_test(ret_icon_name, Glib::FILE_TEST_EXISTS)) {
|
||||
pixbuf = load_icon_from_file(ret_icon_name, scaled_icon_size);
|
||||
} else {
|
||||
try {
|
||||
pixbuf = DefaultGtkIconThemeWrapper::load_icon(
|
||||
"image-missing", scaled_icon_size, Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE);
|
||||
} catch (...) {
|
||||
pixbuf = {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pixbuf) {
|
||||
if (pixbuf->get_width() != scaled_icon_size) {
|
||||
int width = scaled_icon_size * pixbuf->get_width() / pixbuf->get_height();
|
||||
pixbuf = pixbuf->scale_simple(width, scaled_icon_size, Gdk::InterpType::INTERP_BILINEAR);
|
||||
}
|
||||
auto surface = Gdk::Cairo::create_surface_from_pixbuf(pixbuf, image.get_scale_factor(),
|
||||
image.get_window());
|
||||
image.set(surface);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void IconLoader::add_custom_icon_theme(const std::string &theme_name) {
|
||||
auto icon_theme = Gtk::IconTheme::create();
|
||||
icon_theme->set_custom_theme(theme_name);
|
||||
custom_icon_themes_.push_back(icon_theme);
|
||||
spdlog::debug("Use custom icon theme: {}", theme_name);
|
||||
}
|
||||
|
||||
bool IconLoader::image_load_icon(Gtk::Image &image, Glib::RefPtr<Gio::DesktopAppInfo> app_info,
|
||||
int size) const {
|
||||
for (auto &icon_theme : custom_icon_themes_) {
|
||||
if (image_load_icon(image, icon_theme, app_info, size)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return image_load_icon(image, default_icon_theme_, app_info, size);
|
||||
}
|
||||
|
||||
Glib::RefPtr<Gio::DesktopAppInfo> IconLoader::get_app_info_from_app_id_list(
|
||||
const std::string &app_id_list) {
|
||||
std::string app_id;
|
||||
std::istringstream stream(app_id_list);
|
||||
Glib::RefPtr<Gio::DesktopAppInfo> app_info_;
|
||||
|
||||
/* Wayfire sends a list of app-id's in space separated format, other compositors
|
||||
* send a single app-id, but in any case this works fine */
|
||||
while (stream >> app_id) {
|
||||
app_info_ = get_desktop_app_info(app_id);
|
||||
if (app_info_) {
|
||||
return app_info_;
|
||||
}
|
||||
|
||||
auto lower_app_id = app_id;
|
||||
std::ranges::transform(lower_app_id, lower_app_id.begin(),
|
||||
[](char c) { return std::tolower(c); });
|
||||
app_info_ = get_desktop_app_info(lower_app_id);
|
||||
if (app_info_) {
|
||||
return app_info_;
|
||||
}
|
||||
|
||||
size_t start = 0, end = app_id.size();
|
||||
start = app_id.rfind(".", end);
|
||||
std::string app_name = app_id.substr(start + 1, app_id.size());
|
||||
app_info_ = get_desktop_app_info(app_name);
|
||||
if (app_info_) {
|
||||
return app_info_;
|
||||
}
|
||||
|
||||
start = app_id.find("-");
|
||||
app_name = app_id.substr(0, start);
|
||||
app_info_ = get_desktop_app_info(app_name);
|
||||
}
|
||||
return app_info_;
|
||||
}
|
||||
Reference in New Issue
Block a user